📑

Next.jsをCloud Runにデプロイする際にビルド時の環境変数を設定する

2024/09/15に公開2

1. はじめに

Next.js(App Router)をCloud Runにデプロイしようとしたのですが、Next.jsのビルド時に環境変数を設定するのに苦労したので備忘録です。
実はVercelを使えば即効で終わるのですが、それでもCloud Runを使いたい人向けの記事です。

2. 前提

  • Next.jsの環境変数はCloud Runに設定済みとします
  • Cloud RunにNext.jsをビルドしようとする際はCloud Buildが走りnext buildを行います
  • Cloud Runに環境変数を設定してもCloud Buildは環境変数を知らないので正常にビルドできません
  • Next.jsが正常にビルドできるようにするには.envファイルなどで環境変数を教えてあげる必要があります
  • Dockerfilecloudbuild.yaml(つまりコード内)に機密情報をハードコーディングすることもできますが無論悪手なので違う手を使いました

3. 現象

Google CloudのCloud Runにデプロイする際にビルドエラーが出るのでCloud Buildを覗いてみると下記のようなエラーが出ます。
最初はNextAuthのエラーだと思っていましたが、ローカル環境では正常にnext buildできること等から、環境変数が設定されていないことで起こるエラーだと突き止めました。

Step #0 - "Build": ▲ Next.js 14.2.10
Step #0 - "Build":
Step #0 - "Build": Creating an optimized production build ...
Step #0 - "Build": ✓ Compiled successfully
Step #0 - "Build": Linting and checking validity of types ...
Step #0 - "Build": Collecting page data ...
Step #0 - "Build": [91mTypeError: Cannot read properties of undefined (reading 'replace')
Step #0 - "Build": at /usr/src/app/.next/server/app/api/auth/[...nextauth]/route.js:1:4632
Step #0 - "Build": [0m[91m
Step #0 - "Build": [0m[91m> Build error occurred
Step #0 - "Build": [0m[91mError: Failed to collect page data for /api/auth/[...nextauth]
Step #0 - "Build": at /usr/src/app/node_modules/next/dist/build/utils.js:1268:15 {
Step #0 - "Build": type: 'Error'
Step #0 - "Build": }
Step #0 - "Build": The command '/bin/sh -c npm run build' returned a non-zero code: 1
Finished Step #0 - "Build"
ERROR
ERROR: build step 0 "gcr.io/cloud-builders/docker" failed: step exited with non-zero status: 1

4. 解決方法

(1) Cloud Buildの代入変数に値を設定する

Google CloudのCloud Build > トリガー > 該当のトリガーを「編集」でメニューの下の方に代入変数の設定画面があります。ここに環境変数を設定します。

命名ルールに迷ったら「NEXT_PUBLIC_API_URL」なら「_NEXT_PUBLIC_API_URL」にするなど_を先頭につけるのが分かりやすいでしょう。

設定が終わったら変更を保存します。

Cloud Buildの代入変数の設定画面

(2) cloudbuild.yamlを変更する

ビルド時に.envファイルにCloud Buildの代入変数を入れるステップを追加します。
この例では_NEXT_PUBLIC_API_URLなどの値をechoで設定していますが、 1で保存した値に読み替えてください。

cloudbuild.yaml
steps:
  # .envにCloud Buildの代入変数を埋め込むステップを追加する
  - name: "gcr.io/cloud-builders/docker"
    entrypoint: "bash"
    args:
      - "-c"
      - |
        echo "NEXT_PUBLIC_API_URL=${_NEXT_PUBLIC_API_URL}" >> .env
        echo "NEXT_PUBLIC_BASE_URL=${_NEXT_PUBLIC_BASE_URL}" >> .env
        echo "NEXTAUTH_SECRET=${_NEXTAUTH_SECRET}" >> .env
        echo "NEXTAUTH_URL=${_NEXTAUTH_URL}" >> .env
        echo "JWT_SECRET=${_JWT_SECRET}" >> .env
        echo "TWITTER_CLIENT_ID=${_TWITTER_CLIENT_ID}" >> .env
        echo "TWITTER_CLIENT_SECRET=${_TWITTER_CLIENT_SECRET}" >> .env
        echo "GOOGLE_CLIENT_ID=${_GOOGLE_CLIENT_ID}" >> .env
        echo "GOOGLE_CLIENT_SECRET=${_GOOGLE_CLIENT_SECRET}" >> .env
        echo "FIREBASE_PROJECT_ID=${_FIREBASE_PROJECT_ID}" >> .env
        echo "FIREBASE_PRIVATE_KEY_ID=${_FIREBASE_PRIVATE_KEY_ID}" >> .env
        echo "FIREBASE_CLIENT_EMAIL=${_FIREBASE_CLIENT_EMAIL}" >> .env
        echo "FIREBASE_PRIVATE_KEY=${_FIREBASE_PRIVATE_KEY}" >> .env
        echo "STRIPE_SECRET_KEY=${_STRIPE_SECRET_KEY}" >> .env

        docker build -f Dockerfile -t gcr.io/$PROJECT_ID/【あなたのCloud Runサービス名】:$COMMIT_SHA .

  # ビルドしたコンテナイメージをArtifact Registryにプッシュ
  - name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/$PROJECT_ID/【あなたのCloud Runサービス名】:$COMMIT_SHA"]

  # Artifact RegistryのコンテナイメージをCloud Runにデプロイ
  - name: "gcr.io/cloud-builders/gcloud"
    args:
      - "run"
      - "deploy"
      - "【あなたのCloud Runサービス名】"
      - "--image"
      - "gcr.io/$PROJECT_ID/【あなたのCloud Runサービス名】:$COMMIT_SHA"
      - "--region"
      - "${_DEPLOY_REGION}"
images:
  - "gcr.io/$PROJECT_ID/【あなたのCloud Runサービス名】:$COMMIT_SHA"
timeout: 900s

options:
  logging: CLOUD_LOGGING_ONLY

この際の、Dockerfileは以下のようになっています。

Dockerfile
# Use a base image with Node.js LTS
FROM node:lts-alpine

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of your application code to the working directory
COPY . .

# Build the Next.js application
RUN npm run build

# Expose the port your app runs on
EXPOSE 3000

# Define the command to run your app
CMD ["npm", "run", "start"]

(3) リポジトリの変更をプッシュする

リポジトリの変更をプッシュして、Cloud Buildのトリガーを発火させます。
Cloud Buildのビルドログを読むと先ほどと違って「Environments: .env」とあるように.envファイルを読み込んでいることが分かります。
ビルドが成功したら、問題は解決です。お疲れ様でした。

Step #0: ▲ Next.js 14.2.10
Step #0: - Environments: .env
Step #0:
Step #0: Creating an optimized production build ...
Step #0: ✓ Compiled successfully
Step #0: Linting and checking validity of types ...
Step #0: Collecting page data ...
Step #0: Generating static pages (0/12) ...
Step #0: Generating static pages (3/12)
Step #0: Generating static pages (6/12)
Step #0: Generating static pages (9/12)
Step #0: ✓ Generating static pages (12/12)
Step #0: Finalizing page optimization ...
Step #0: Collecting build traces ...

5. 参考資料

Discussion

株式会社ホコサキ株式会社ホコサキ

自分も同じことでハマりました
解決策が全くスマートじゃなくて驚きました

環境変数を追加するたびにcloudbuildを変更するのは手間がかかるので、secret-managerを使うのもおすすめです
secret managerに.envの内容を丸ごと入れて

steps:
  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        gcloud secrets versions access latest --secret={secret-manager-name} > .env.production
    id: 'fetch-secret'
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '--no-cache'
      - '-t'
      - >-
        $_AR_HOSTNAME/$PROJECT_ID/cloud-run-source-deploy/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA
      - '--build-arg'
      - 'ENV_FILE=.env.production'
      - '.'
      - '-f'
      - 'Dockerfile'
    id: 'Build'

でDockerfileは

FROM --platform=linux/amd64 node:19-bullseye-slim

WORKDIR /app

COPY . .

RUN yarn install

# ビルド時の引数を受け取る
ARG ENV_FILE
# 環境変数ファイルをコピー
COPY $ENV_FILE .env.production

RUN yarn build

EXPOSE 3000

CMD ["yarn","start"]

とすると、secret-managerの値を変えるだけで環境変数を更新できるのでおすすめです
cloud buildにsecret managerのアクセス権があるサービスアカウントを割り当てないといけないので、その点だけ注意です