🔖

DockerコンテナでNEXT_PUBLIC_を使用する方法

2023/11/13に公開

状況

Azure App ServiceでDockerを用いてNext.jsのアプリケーションを実行しようとしたところ、reCAPTCHAが recaptcha key not provided というエラーを吐いていた。またその他複数の不具合が確認された。

結論

  • Node.jsのDockerイメージが、Next.jsのNEXT_PUBLIC_接頭辞に対応していなかった。
  • この問題はAzureだろうがAWSだろうがGCPだろうが、DockerコンテナでNext.jsを動かす場合に発生し得る。

NEXT_PUBLIC_ の役割

通常環境変数というものにはAPIキーのような秘匿情報が含まれることが多く、Next.jsのようにClientとServerが一体となったフレームワークでは、秘匿すべき情報を誤ってバンドルに含めてしまい、ブラウザから確認できてしまうようになることが考えられます。
一方でreCAPTCHAやSentry, Google AnalyticsなどのClientサイドで動作するサービスでは、むしろ必ずClientに送信される必要があります。

つまり安全に環境変数を運用するためには、露出すべき環境変数と秘匿すべき環境変数の境界を明確に区別する必要があり、その機能としてNEXT_PUBLIC_接頭辞が提供されています。

Vercelや一部のクラウドサービスでは、NEXT_PUBLIC_という接頭辞の付いた環境変数のみClientに送信し、それが付いていない変数は全て秘匿する機能に対応しています。
https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser

一方でNext.jsに対応していないNode.jsのサーバーは、NEXT_PUBLIC_という接頭辞を付けてもビルドする際にバンドリングされません。これはNode.jsのDockerイメージでも同様です。

対策

ビルドする際に環境変数を宣言してやる必要があります。

Dockerfile
FROM node:18.16.1-alpine AS deps
why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

FROM node:18.16.1-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npx prisma generate

# 🌝 NOTE: buildコマンドの前に付ける
RUN NEXT_PUBLIC_EXAMPLE_KEY=hogehogehoge yarn build

FROM node:18.16.1-alpine AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

# Copy the entrypoint script
COPY --chown=nextjs:nodejs entrypoint.sh ./
RUN chmod +x ./entrypoint.sh

COPY --chown=nextjs:nodejs ./prisma ./prisma

ENTRYPOINT ["./entrypoint.sh"]

上記でも問題ありませんが、entrypointを使って次のように実装することも可能です。

Dockerfile
...
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# 🌝 NOTE: ここでentrypointをコピー
COPY --from=builder /app/entrypoint.sh ./entrypoint.sh

USER nextjs

EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

# Copy the entrypoint script
COPY --chown=nextjs:nodejs entrypoint.sh ./
RUN chmod +x ./entrypoint.sh

# Copy prisma file for migration
COPY --chown=nextjs:nodejs ./prisma ./prisma

ENTRYPOINT ["./entrypoint.sh"]
entrypoint.sh
#!/bin/bash

# .env ファイルが存在するか確認
if [ -f .env ]; then
    # .env ファイルから NEXT_PUBLIC_EXAMPLE_KEY を読み込む
    export NEXT_PUBLIC_EXAMPLE_KEY=$(grep NEXT_PUBLIC_EXAMPLE_KEY .env | cut -d '=' -f2)
fi

exec "$@"
Dynagon Tech Blog

Discussion