Next.js Docker 环境变量解决方案

最近boss给了我一个需求, 要把前端项目分离出来, 现在要让整个前端独立运行. 现在发现容器不够灵活, apihost不一样, 要为staigng和 production环境分别打一个包, 避免环境不一致的问题. 经过我的摸索, 终于找到了个可行的解决方案, 能够一次打包, 在多个环境所运行

众所周知, 在构建 Next.js 应用程序时,对于不同的环境来管理环境变量通常很难,尤其是使用 Docker 部署。一般情况下,是通过 process.env.VARIABLE_NAME 访问环境变量,这中模式对服务器端代码很友好。但是,由于浏览器无法访问服务器的运行时变量,我们只能使用在客户端打包构建过程中嵌入的环境变量。仅仅是因为环境不同而要多次打包, 这不禁令人抓狂😫

在客户端代码中访问 process.env 时,Next.js 通过将这些变量的值直接嵌入到生成的 JavaScript 中来编译它们。例如,以下代码:

const apiURL = process.env.NEXT_PUBLIC_API_URL;

它在构建时将被翻译成: const apiURL = "https://api.example.com";

https://api.example.com 是构建时 NEXT_PUBLIC_API_URL 环境变量的值, 这种行为使得在不重新构建应用程序的情况下无法更改环境变量,同一个镜像无法在不同环境中使用, 失去了灵活性.

那么, 有什么简单又高效的办法使得我们仅需构建一次, 从而能够在多个环境中使用呢? 🤔

有的兄弟, 有的, 接下来, 我将详细阐述如何解决这个问题: 使用自定义 Docker 入口点在运行时动态替换环境变量

准备一个next.js 应用

假设有个next 应用, 我们还需要两个环境变量, 环境名称和 API_URL, 这些值我们要在client 端中使用, 并且, 开发环境/预发布环境/生成, 他们的值均有所不同, 样式如下:

在 next.config.ts 中定义这些变量

为了使这些变量可供客户端使用,我们将在next.config.ts中使用publicRuntimeConfig定义。这使我们能够在运行时动态访问特定于环境的值(理论上是这样的):

js 复制代码
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  publicRuntimeConfig: {
    env: process.env.NEXT_PUBLIC_ENV,
    apiURL: process.env.NEXT_PUBLIC_API_URL,
  },
};

export default nextConfig;

在客户端使用环境变量

一但我们在 next.config.ts 中完成环境变量定义后, 我们可以在客户端代码的任何地方中来访问它们. 在这个实例中, 我将创建一个简单的page来验证该功能, 该page 使用 publicRuntimeConfig 来显示当前的环境以及apiUrl

js 复制代码
import getConfig from "next/config";

export default function Home() {
  const { publicRuntimeConfig } = getConfig();

  return (
    <div>
      <p>你好, 我当前运行在<b>{publicRuntimeConfig.env}</b> 环境</p>
      <p>当前apiUrl是<b>{publicRuntimeConfig.apiURL}</b></p>
    </div>
  );
}

现在我们可以在启动时传入这些参数, 并运行这个项目:

sh 复制代码
NEXT_PUBLIC_ENV=dev NEXT_PUBLIC_API_URL=https://dev-api.example.com yarn dev

在服务启动后, 你将在页面看到以下内容:

这时, 环境变量确实准确的反应在了本地的开发环境中😃, 但是, 使用这个方式之所以有效, 是因为环境变量是在build之后, 在run之前完成注入的. 如果我们把这个应用打包成docker, 也会存在相关的问题😔, 必须为每个环境单独打包一个镜像

build之后, 环境变量确实会被替换, 这是next本身所决定的, 那么如果我们想通过在运行时来指定环境变量, 那么我们该怎么做呢🤔?

答案就是: build完成替换后, 我们再替换回来

秘籍------使用 Docker Entrypoint 在运行时完成变量替换

假设你已经有一个Dockerfile(虽然你可能没有...), 但是关于创建Dockerfile的内容不在本次博客的范围中, 我们跳过这一部分, 我们的重点放在如何使用 自定义的 Entrypoint 在运行时完成环境变量替换

想法非常简单, 在构建时, 我们不会传入实际的变量, 而是使用占位符. 然后, 当容器启动后, 再通过脚本把这些占位符替换为你所需要的环境变量

准备环境

如上所述, 我们要使用占位符(环境变量中间值)来简化替换过程, 这些占位符将在运行时被替换为实际的环境变量. next.js 使用 .env* 文件来获取这些环境变量. 你可以使用.env.docker, .env.production., 以及其他env文件. 在本次教程中, 我将使用.env.production 来演示

.env.production 复制代码
NEXT_PUBLIC_ENV=_NEXT_PUBLIC_ENV_
NEXT_PUBLIC_API_URL=_NEXT_PUBLIC_API_URL_

遵循中间值的简单模式, _{env_var_name}_ ,因为这种方法可以更容易地在客户端包中搜索和替换占位符

在运行Dockerfile之前, 你可以复制这个文件到docker中, 从而能够让next.js 在构建后使用这些环境变量, 可以将以下代码复制到你的dockerfile中

Dockerfile 复制代码
...
COPY .env.production .env.production
RUN yarn build

...

创建Docker 入口点

现在到了整个文章最核心的部分------创建一个Dokcer 入口点脚本, 脚本将在容器运行前, 搜索客户端中的占位符, 并替换为实际的环境变量, 这是相关脚本:

sh 复制代码
#!/usr/bin/env sh

set -e

# 获取环境变量并筛选以NEXT_PUBLIC_开头的环境变量
printenv | grep NEXT_PUBLIC_ | while read -r ENV_LINE ; do
  # 通过找到的行, 将 key 和 value 分隔开
  ENV_KEY=$(echo $ENV_LINE | cut -d "=" -f1)
  ENV_VALUE=$(echo $ENV_LINE | cut -d "=" -f2)

  # 找到所有的占位符, 并用当前的环境变量值替换它
  find .next -type f -exec sed -i "s|_${ENV_KEY}_|${ENV_VALUE}|g" {} \;
done

# 执行应用程序
exec "$@"

这个脚本确保了.next.production文件(构建的客户端包)中的占位符(如_NEXT_PUBLIC_ENV__NEXT_PUBLIC_API_URL_ )被传递给容器的实际环境变量替换. 最后,它执行传递给容器的主命令( exec "$@" ).

现在只需要把这个脚本复制到容器中, 以下是更新后的Dockerfile

Dockerfile 复制代码
COPY ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

# 把入口点设置到新加的脚本上
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

CMD ["node", "server.js"]

测试Docker容器

现在我们尝试在运行容器时, 传入不同的环境变量值来校验它是否生效

先从staing 开始

bash 复制代码
docker run --rm -e NEXT_PUBLIC_ENV=staging -e NEXT_PUBLIC_API_URL=https://staging-api.example.com -p 3000:3000 next-app

替换成 production

bash 复制代码
docker run --rm -e NEXT_PUBLIC_ENV=prod -e NEXT_PUBLIC_API_URL=https://api.example.com -p 3000:3000 next-app

总结

在本篇文章中,我们探讨了在 Dockerized Next.js 应用程序中动态管理环境变量的实用方法. 通过使用中间占位符并在运行时用自定义 Docker 入口点替换它们,克服了环境变量在构建时固定的限制. 此方法可确保单个 Docker 映像可以部署到多个环境中,而无需为每个环境重建镜像

虽然这个解决方案是使用 Next.js 应用进行演示的,但它并不仅限于 Next.js. 同样的方法可以应用于其他前端框架和库,例如 React、NuxtJS 甚至普通的 JavaScript 应用. 如果您的应用程序在容器化设置中需要动态环境特定值,此策略可提供灵活、高效且可重复使用的解决方案

最后, 希望能够帮助到你🙇‍

相关推荐
敲代码的玉米C5 小时前
Next.js服务端渲染应用部署详解:与Vue.js静态部署的区别
next.js
Bigger2 天前
基于 GitHub Actions 的 Next.js 全自动部署指南
前端·github·next.js
HyaCinth5 天前
Next.js 服务端渲染超时导致生产事故
前端·react.js·next.js
a_little161256 天前
NEXT.js 中文文档
前端·next.js
web_Leon8 天前
Next.js 13+ App Router 国际化方案实践
react.js·next.js
前端双越老师13 天前
React19 和 Nextjs15 可否用于生产环境?
react.js·全栈·next.js
朝阳3923 天前
Next.js【详解】获取数据(访问接口)
next.js
朝阳3924 天前
Next.js 15【实用教程】2025最新版
next.js
前端破坏球1 个月前
开源一款丝滑纯粹的简历编辑器,小小集成AI-DeepseekV3
前端·next.js