Zenn Tech Blog

iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦕

Migrating Docusaurus Documentation from App Engine to Cloud Run

に公開

This article is for the 19th day of the Google Cloud Champion Innovators Advent Calendar 2024.

Hello. In the Zenn team, we have internal documentation primarily for development members, where we record specifications and architecture. We use Docusaurus because we don't want to spend too much effort, but we are happy to be able to manage versions on GitHub along with the source code.

https://docusaurus.io/docs

If you write Markdown files according to the conventions, you can build them as HTML for manuals, which can be served using Node.js.

Want to migrate from App Engine to Cloud Run

Docusaurus handles everything from building to server startup via the docusaurus command, so we don't have any trouble in this regard. One point is that since it's internal documentation, there was a requirement to restrict access to specific members only. To address this, we used to deploy to App Engine and protect it with IAP.

https://cloud.google.com/iap/docs/enabling-app-engine?hl=ja#enabling_iap_console

Since the viewing timing is limited and the users are development members, we have configured it to scale to zero. Naturally, it results in a cold start when accessed, and the App Engine standard environment VM has a slight lag in startup (about 5 seconds), which caused a tiny bit of stress. Because of this, I wanted to migrate to a container-based environment, specifically Cloud Run. However, to protect an app running on Cloud Run with IAP, you must include Cloud Load Balancing. We decided to postpone the migration as it felt like too much for internal documentation.

However, I wanted to try running it on Cloud Run so that I'm ready whenever we decide to migrate. In this article, I will create a Docker image and deploy it to Cloud Run.

Changing the build method to Docker

Regarding the documentation, on App Engine, we were configuring the application in app.yml, building with yarn build, and starting it with yarn start. In migrating to Cloud Run, I decided to build a Docker image and deploy from that image. So, first, let's prepare the Dockerfile.

Dockerfile
FROM node:20-slim AS builder

WORKDIR /app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install --frozen-lockfile --production=false
COPY . .
RUN yarn build

FROM node:20-slim AS runner
ENV NODE_ENV=production

WORKDIR /app

# We use a whitelist to ensure unnecessary files are not copied
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.docusaurus ./.docusaurus
COPY --from=builder /app/build ./build
COPY --from=builder /app/public ./public
COPY --from=builder /app/docs-custom.css ./
COPY --from=builder /app/docs-sidebars.js ./
COPY --from=builder /app/docusaurus.config.js ./
COPY --from=builder /app/package.json ./

CMD ["yarn", "start"]

It might not be necessary to use multi-stage builds, but I am moving only the necessary files to keep the deployed image size as small as possible. Note that for the Dockerfile, guidelines are provided in the official documentation, and you can follow those if you prefer. In the official Dockerfile, they don't explicitly move files one by one but use build artifacts by doing something like FROM prod AS serve.

https://docusaurus.community/knowledge/deployment/docker/?package-managers=yarn

package.json also requires attention. Since it needs to start on the port number determined by Cloud Run, we write the startup script to use the PORT environment variable.

package.json (partial)
{
  "scripts": {
    "docusaurus": "docusaurus",
    "build": "docusaurus build",
    "start": "docusaurus serve -p $PORT",
  },
  ...
}

Creating cloudbuild.yml

I will use Cloud Build to build the image and deploy it to Cloud Run. It is assumed that the source code will be integrated from GitHub. Let's use the prepared Dockerfile.

cloudbuild.yml
steps:
  - id: build
    name: gcr.io/cloud-builders/docker
    args:
      - build
      - --file=Dockerfile
      - --tag=$_ARTIFACT_REPOSITORY_IMAGE_TAG
      - .
    dir: "docs"
  - id: push
    name: gcr.io/cloud-builders/docker
    args:
      - push
      - $_ARTIFACT_REPOSITORY_IMAGE_TAG
    dir: "docs"
    waitFor: ["build"]
  - id: deploy
    name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine
    entrypoint: gcloud
    args:
      - run
      - deploy
      - docs
      - --quiet
      - --platform=managed
      - --project=$PROJECT_ID
      - --region=$_REGION
      - --image=$_ARTIFACT_REPOSITORY_IMAGE_TAG
      - --service-account=$_CLOUD_RUN_SERVICE_ACCOUNT
      - --revision-suffix=${SHORT_SHA}-${_SHORT_BUILD_ID}
      - --tag=latest
      - --concurrency=80
      - --cpu=1
      - --memory=512Mi
      - --max-instances=1
      - --min-instances=0
      - --no-use-http2
      - --no-allow-unauthenticated
    dir: "docs"
    waitFor: ["push"]
substitutions:
  _REGION: asia-northeast1
  _CLOUD_RUN_SERVICE_ACCOUNT: terraform
  _ARTIFACT_REPOSITORY_IMAGE_NAME: terraform
  _ARTIFACT_REPOSITORY_IMAGE_TAG: "${_ARTIFACT_REPOSITORY_IMAGE_NAME}:${SHORT_SHA}"

# Display image information generated in the build result
# https://cloud.google.com/build/docs/building/build-containers
images:
  - $_ARTIFACT_REPOSITORY_IMAGE_TAG
options:
  logging: CLOUD_LOGGING_ONLY

Please fill in the Artifact Registry image name and other details to suit your environment. Now we are ready.

Deploying to Cloud Run

Using the cloudbuild.yml we just prepared, I will deploy the documentation to Cloud Run. Depending on your Cloud Build trigger configuration, if you are using the GitHub App trigger, you can push to a branch to start the build.

I was able to view the documentation built by Docusaurus. (It is Forbidden by default because of --no-allow-unauthenticated, but I temporarily connected it to Cloud Load Balancing to display it after IAP authentication.)

Conclusion

I migrated the documentation built with Docusaurus so that it can run on Cloud Run. You can enable IAP by connecting it with Cloud Load Balancing. If the time comes to fully transition to Cloud Run, I would like to add those steps as well.

Zenn Tech Blog
Zenn Tech Blog

Discussion