Next.js building in container, all .env variables which not starting with NEXT_PUBLIC_ are undefined (on the server side)

huangapple go评论69阅读模式
英文:

Next.js building in container, all .env variables which not starting with NEXT_PUBLIC_ are undefined (on the server side)

问题

这是您的翻译好的部分:

我有一个 Next.js 应用,本地运行正常(yarn dev),使用 .env.local 存储秘密变量,一切正常。

但是,当我将我的应用部署到一个在 Docker 容器中的服务器上,使用 GitHub Actions 动态创建 .env 文件时,似乎只有以 NEXT_PUBLIC_ 开头的变量才会被正常定义,其他变量在服务器(容器)日志中未定义,我使用 console.log 进行了检查。

所以客户端环境变量没问题,服务器端的环境变量未定义。

以下是创建 .env 文件的 GitHub Actions yml:

jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the repo
        uses: actions/checkout@v2

      - name: Make envfile
        uses: SpicyPizza/create-envfile@v1.3
        with:
          envkey_NEXT_PUBLIC_JWT_EXPIRATION: ${{ vars.NEXT_PUBLIC_JWT_EXPIRATION }}
          envkey_NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET: ${{ vars.NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET }}
          envkey_NEXT_PUBLIC_JWT_SECRET: ${{ vars.NEXT_PUBLIC_JWT_SECRET }}
          envkey_NEXT_PUBLIC_API_BASE_URL: ${{ vars.DEV_API_BASE_URL }}
          envkey_MONGO_DB_URI: ${{ secrets.DEV_MONGO_DB_URI }}
          envkey_WEATHER_DB: ${{ vars.DEV_WEATHER_DB }}
          envkey_WILLI_WEATHER_API_KEY: ${{ secrets.WILLI_WEATHER_API_KEY }}
          file_name: .env
          fail_on_empty: false

      - name: Build container image
        run: docker build -t $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7) .

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

      - name: Log in to DigitalOcean Container Registry with short-lived credentials
        run: doctl registry login --expiry-seconds 600

      - name: Remove all old images
        run: if [ ! -z "$(doctl registry repository list | grep "$(echo $IMAGE_NAME)")" ]; then doctl registry repository delete-manifest $(echo $IMAGE_NAME) $(doctl registry repository list-tags $(echo $IMAGE_NAME) | grep -o "sha.*") --force; else echo "No repository"; fi

      - name: Push image to DigitalOcean Container Registry
        run: docker push --max-concurrent-uploads 1 $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7)

      - name: Run registry garbage collection
        run: doctl registry garbage-collection start --include-untagged-manifests --force

这是我的 Dockerfile:

FROM node:16-alpine as dependencies
WORKDIR /my-project
COPY package.json yarn.lock ./
    
RUN yarn install --frozen-lockfile

FROM node:16-alpine as builder
WORKDIR /my-project
COPY . .
COPY --from=dependencies /my-project/node_modules ./node_modules
COPY ./.env ./node_modules/.env
RUN yarn build

FROM node:16-alpine as runner
WORKDIR /my-project
ENV NODE_ENV production

# 如果您使用自定义的 next.config.js 文件,请取消注释以下一行。
# COPY --from=builder /my-project/next.config.js ./
COPY --from=builder /my-project/public ./public
COPY --from=builder /my-project/.next ./.next
COPY --from=builder /my-project/node_modules ./node_modules
COPY --from=builder /my-project/package.json ./package.json
COPY --from=builder /my-project/_fonts /usr/local/share/fonts
COPY --from=builder /my-project/_assets ./_assets

# 安装 fontconfig 并清除缓存
RUN apk add --update fontconfig 
RUN fc-cache -f -v

EXPOSE 3000
CMD ["yarn", "start"]

在 Docker 文件中,我将 .env 文件复制到 node_modules 文件夹中,以确保该文件正确创建。我可以验证,所有具有和不具有 NEXT_PUBLIC_ 前缀的变量都与我的原始 .env.local 文件中的值相同。

以下是我在容器中触发特定端点请求时的日志输出:

### 响应从这里开始:
时间戳:5/21/23,上午9:39
NEXT_PUBLIC_API_BASE_URL http://10.0.4.6:3000/
MONGO_DB_URI 未定义
WEATHER_DB 未定义
WILLI_WEATHER_API_KEY 未定义
NEXT_PUBLIC_JWT_EXPIRATION 5m
NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET 7c4c1c50-3230-45bf-9eae-c9b2e401c767
NEXT_PUBLIC_JWT_SECRET dd5f3089-40c3-403d-af14-d0c228b05cb4
Delta 时间 = 142392

这是在构建 Docker 镜像时复制的 .env 文件的输出(我修改了敏感数据):

/my-project/node_modules # cat .env
MONGO_DB_URI=mongodb+srv://mame:reJHeDc@maev.v8ilhrq.mongodb.net/?retryWrites=true&w=majority
NEXT_PUBLIC_API_BASE_URL=http://10.0.4.6:3000/
NEXT_PUBLIC_JWT_EXPIRATION=5m
NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET=7c4c1c50-3230-45bf-9eae-c9b2e401c767
NEXT_PUBLIC_JWT_SECRET=dd5f3089-40c3-403d-af14-d0c228b05cb4
WEATHER_DB=Weather
WILLI_WEATHER_API_KEY=YjQ3MDkwMGDkN2VjQ3MDkwMGZjjOD

(我修改了敏感数据)

英文:

I have a Next.js app that works locally (yarn dev) using .env.local for secret vars and all working fine.

When I deploy my app to a server in a Docker container, with GitHub Actions, while creating the .env file on the fly, it appears that only variables that start with NEXT_PUBLIC_ are actually defined as normal, other variables are undefined when I console.log and check in the **server **(container) logs.

So the client side env vars are fine, server side are undefined.

Here is my GitHub Actions yml that creates the .env file:

jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the repo
        uses: actions/checkout@v2

      - name: Make envfile
        uses: SpicyPizza/create-envfile@v1.3
        with:
          envkey_NEXT_PUBLIC_JWT_EXPIRATION: ${{ vars.NEXT_PUBLIC_JWT_EXPIRATION }}
          envkey_NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET: ${{ vars.NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET }}
          envkey_NEXT_PUBLIC_JWT_SECRET: ${{ vars.NEXT_PUBLIC_JWT_SECRET }}
          envkey_NEXT_PUBLIC_API_BASE_URL: ${{ vars.DEV_API_BASE_URL }}
          envkey_MONGO_DB_URI: ${{ secrets.DEV_MONGO_DB_URI }}
          envkey_WEATHER_DB: ${{ vars.DEV_WEATHER_DB }}
          envkey_WILLI_WEATHER_API_KEY: ${{ secrets.WILLI_WEATHER_API_KEY }}
          file_name: .env
          fail_on_empty: false

      - name: Build container image
        run: docker build -t $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7) .

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

      - name: Log in to DigitalOcean Container Registry with short-lived credentials
        run: doctl registry login --expiry-seconds 600

      - name: Remove all old images
        run: if [ ! -z "$(doctl registry repository list | grep "$(echo $IMAGE_NAME)")" ]; then doctl registry repository delete-manifest $(echo $IMAGE_NAME) $(doctl registry repository list-tags $(echo $IMAGE_NAME) | grep -o "sha.*") --force; else echo "No repository"; fi

      - name: Push image to DigitalOcean Container Registry
        run: docker push --max-concurrent-uploads 1 $(echo $REGISTRY)/$(echo $IMAGE_NAME):$(echo $GITHUB_SHA | head -c7)

      - name: Run registry garbage collection
        run: doctl registry garbage-collection start --include-untagged-manifests --force

Here is my Dockerfile:

FROM node:16-alpine as dependencies
WORKDIR /my-project
COPY package.json yarn.lock ./
    
RUN yarn install --frozen-lockfile

FROM node:16-alpine as builder
WORKDIR /my-project
COPY . .
COPY --from=dependencies /my-project/node_modules ./node_modules
COPY ./.env ./node_modules/.env
RUN yarn build

FROM node:16-alpine as runner
WORKDIR /my-project
ENV NODE_ENV production

# If you are using a custom next.config.js file, uncomment this line.
# COPY --from=builder /my-project/next.config.js ./
COPY --from=builder /my-project/public ./public
COPY --from=builder /my-project/.next ./.next
COPY --from=builder /my-project/node_modules ./node_modules
COPY --from=builder /my-project/package.json ./package.json
COPY --from=builder /my-project/_fonts /usr/local/share/fonts
COPY --from=builder /my-project/_assets ./_assets

#install fontconfig and clear cache
RUN apk add --update fontconfig 
RUN fc-cache -f -v

EXPOSE 3000
CMD ["yarn", "start"]

In the Docker file, I copy the .env file into the node_modules folder to make sure that this file is created correctly. And I can verify that all variables with and without NEXT_PUBLIC_ are having values as in my original .env.local file.

Here is an output of my log from the container which was triggered on a specific endpoint request:

### Response start here:                                                                                                                                                                    
Timestamp:  5/21/23, 9:39 AM                                                                                                                                                                
NEXT_PUBLIC_API_BASE_URL http://10.0.4.6:3000/                                                                                                                                              
MONGO_DB_URI undefined                                                                                                                                                                      
WEATHER_DB undefined                                                                                                                                                                        
WILLI_WEATHER_API_KEY undefined                                                                                                                                                            
NEXT_PUBLIC_JWT_EXPIRATION 5m                                                                                                                                                              
NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET 7c4c1c50-3230-45bf-9eae-c9b2e401c767                                                                                                                  
NEXT_PUBLIC_JWT_SECRET dd5f3089-40c3-403d-af14-d0c228b05cb4                                                                                                                                
Delta time =  142392    

Here is the output of the .env file I copied when the Docker image was built (I modified sensitive data):

/my-project/node_modules # cat .env                                                                                                                                                        
MONGO_DB_URI=mongodb+srv://mame:reJHeDc@maev.v8ilhrq.mongodb.net/?retryWrites=true&w=majority                                                                
NEXT_PUBLIC_API_BASE_URL=http://10.0.4.6:3000/                                                                                                                                              
NEXT_PUBLIC_JWT_EXPIRATION=5m                                                                                                                                                              
NEXT_PUBLIC_JWT_REFRESH_TOKEN_SECRET=7c4c1c50-3230-45bf-9eae-c9b2e401c767                                                                                                                  
NEXT_PUBLIC_JWT_SECRET=dd5f3089-40c3-403d-af14-d0c228b05cb4                                                                                                                                
WEATHER_DB=Weather                                                                                                                                                                          
WILLI_WEATHER_API_KEY=YjQ3MDkwMGDkN2VjQ3MDkwMGZjjOD

(I modified sensitive data)

答案1

得分: 1

I see a variable like WILLI_WEATHER_API_KEY is visible and defined when Docker is built.
But not when the application is run.

意思是:问题应该与Next.js如何加载环境变量有关。
环境变量在构建时加载到Next.js应用程序中,而不是在运行时加载,这意味着构建过程之后对环境变量的任何更改都不会在运行中的应用程序中反映出来。

在你的情况下,你已经正确地创建了一个包含所有环境变量的 .env 文件,但是在这个文件被创建并放置到 node_modules 目录之后,Next.js 应用程序已经构建完成,这时再提供这些变量已经太晚了。

Next.js 有一种特定的处理环境变量的方式:

  • 如果你的变量以 NEXT_PUBLIC_ 为前缀,它将在服务器端和客户端两侧都可用。
  • 如果不以 NEXT_PUBLIC_ 开头,它将只在服务器端可用。

此外,这些变量需要在构建时可用,而不是运行时。这意味着你必须在运行 yarn build 命令之前提供这些变量。
在你的情况下,.env 文件是在Docker构建过程中生成并复制的,这是在Next.js构建过程之后,因此它们无法访问。

为了解决这个问题,你需要确保你的环境变量在运行 yarn build 命令之前可用。

你的Dockerfile应该如下所示:

FROM node:16-alpine as dependencies
WORKDIR /my-project
COPY package.json yarn.lock ./

RUN yarn install --frozen-lockfile

FROM node:16-alpine as builder
WORKDIR /my-project
COPY . .
COPY --from=dependencies /my-project/node_modules ./node_modules
COPY ./.env ./.env  # 复制 .env 文件到项目的根目录
RUN yarn build

FROM node:16-alpine as runner
WORKDIR /my-project
ENV NODE_ENV production

# 如果你使用自定义的 next.config.js 文件,请取消注释这一行。
# COPY --from=builder /my-project/next.config.js ./
COPY --from=builder /my-project/public ./public
COPY --from=builder /my-project/.next ./.next
COPY --from=builder /my-project/node_modules ./node_modules
COPY --from=builder /my-project/package.json ./package.json
COPY --from=builder /my-project/_fonts /usr/local/share/fonts
COPY --from=builder /my-project/_assets ./_assets
COPY --from=builder /my-project/.env ./.env  # 这可能在运行时并不是必需的,但不会有问题

# 安装 fontconfig 并清除缓存
RUN apk add --update fontconfig
RUN fc-cache -f -v

EXPOSE 3000
CMD ["yarn", "start"]

出于安全原因,你应确保不要在日志或Docker镜像中暴露敏感数据。
使用安全管理系统或在运行时注入环境变量(针对非Next.js变量)将是一种更安全的方法。

英文:

I see a variable like WILLI_WEATHER_API_KEY is visible and define when Docker is built.
But not when the application is run.

Meaning: the issue should be linked to how Next.js loads environment variables.
Environment variables are loaded into the Next.js application at build time, not at runtime, which means that any changes to the environment variables after the build process won't be reflected in the running application.

In your case, you are correctly creating a .env file with all your environment variables, but by the time this file is created and placed into the node_modules directory, the Next.js application has already been built, and it's too late for these variables to be incorporated.

Next.js has a specific way of handling environment variables:

  • If you prefix the variable with NEXT_PUBLIC_, it will be available on both the server and client side.
  • If it doesn't start with NEXT_PUBLIC_, it will only be available on the server side.

Additionally, these variables need to be available at build time, not runtime. This means that you must provide these variables before the yarn build command is run.
In your case, the .env file is generated and copied during the Docker build process, which is after the Next.js build process, hence they are not accessible.

To solve this issue, you need to ensure that your environment variables are available before the yarn build command is run.

Your Dockerfile would then be:

FROM node:16-alpine as dependencies
WORKDIR /my-project
COPY package.json yarn.lock ./
    
RUN yarn install --frozen-lockfile

FROM node:16-alpine as builder
WORKDIR /my-project
COPY . .
COPY --from=dependencies /my-project/node_modules ./node_modules
COPY ./.env ./.env  # copy the .env file to the root of the project
RUN yarn build

FROM node:16-alpine as runner
WORKDIR /my-project
ENV NODE_ENV production

# If you are using a custom next.config.js file, uncomment this line.
# COPY --from=builder /my-project/next.config.js ./
COPY --from=builder /my-project/public ./public
COPY --from=builder /my-project/.next ./.next
COPY --from=builder /my-project/node_modules ./node_modules
COPY --from=builder /my-project/package.json ./package.json
COPY --from=builder /my-project/_fonts /usr/local/share/fonts
COPY --from=builder /my-project/_assets ./_assets
COPY --from=builder /my-project/.env ./.env  # this is probably not necessary for runtime, but does not hurt

#install fontconfig and clear cache
RUN apk add --update fontconfig 
RUN fc-cache -f -v

EXPOSE 3000
CMD ["yarn", "start"]

You should make sure not to expose sensitive data in your logs or Docker image for security reasons.
Using secrets management systems or environment variable injection at runtime (for non-Next.js variables) would be a more secure approach.

huangapple
  • 本文由 发表于 2023年5月21日 18:37:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76299447.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定