最近在做一个 React 项目时,需要在多个环境中部署应用程序,使用 Docker 镜像。其中一个关键问题是如何将环境变量注入到应用程序中,经过一番研究和尝试,我找到了两种主要的方法:可以在构建时注入这些变量,或者在运行时注入。在这篇文章里,我会详细聊聊这两种方式,并聊聊我为什么最终选择了其中一种而不是另一种。
项目设置简介我工作的一个 React 应用程序是:
- 一个
**.env**
文件用于管理环境变量。 - 一个 Dockerfile 用于构建 Docker 镜像。
- 一个自定义的 Nginx 配置文件 (
nginx-custom.conf
) 被复制到容器内 Nginx 的配置目录中,以根据 React 应用程序的需求调整服务器的行为,使其更好地适应应用。
下面,我将在这里谈谈在这个 Docker 化的环境中处理环境变量的两种方式。
在构建时注入环境变量(第一种方式)在这种方法中,环境变量是在Docker构建阶段传递的。如下所示:
理解Dockerfile(Docker文件):
FROM node:22.4.1-slim as build
# 定义构建过程中使用的参数
ARG APP_API_URL
ARG APP_ENV
# 设置环境变量VITE_API_URL和VITE_APP_ENV
ENV VITE_API_URL=$APP_API_URL
ENV VITE_APP_ENV=$APP_ENV
# 设置工作目录为/app
WORKDIR /app
# 将当前目录的所有文件复制到/app目录
COPY . .
# 清除node_modules和package-lock.json文件
RUN rm -rf node_modules package-lock.json
# 安装项目依赖
RUN npm install
# 构建项目
RUN npm run build
# 使用nginx:1.27.0-alpine作为运行时基础镜像
FROM nginx:1.27.0-alpine
# 将构建完成的应用程序文件复制到nginx的web根目录
COPY --from=build /app/dist /usr/share/nginx/html
# 复制自定义nginx配置文件
COPY nginx-custom.conf /etc/nginx/conf.d/default.conf
# 暴露80端口
EXPOSE 80
- 构建参数值:这些是在构建时提供的占位符,用于提供特定环境的值。例如
例如:
ARG APP_API_URL
ARG APP_ENV
- 环境变量:在构建过程中,通过设置构建参数的值,这些变量被设定,并在构建过程中嵌入到 React 应用程序。
# 设置API URL和应用环境变量
ENV VITE_API_URL=$APP_API_URL
ENV VITE_APP_ENV=$APP_ENV
这怎么用:
下面是如何在构建 Docker 镜像时传递参数值的一个示例:
docker build --build-arg APP_API_URL=https://api.example.com --build-arg APP_ENV=正式 - docker-approach:1 .
运行Docker镜像的方法如下:
在终端中输入以下命令来运行容器:
docker run -p 3000:80 docker-approach:1
你可以看到,你注入的这些环境变量值在Web应用中已经正确发挥了作用。
注意:如果你需要在不同的环境里运行React应用,你需要为每个环境分别创建Docker镜像。
方法二:在Docker运行时环境变量注入这种方法是在运行时传递环境变量,而不是在构建时传递环境变量。
面临挑战React 通常在构建过程中将配置变量嵌入到静态文件中。例如,API端点的URL(如https://api.example.com
)、认证密钥(如secret_key_123
)或功能标志(如feature_toggle=true
)会被硬编码进生成的JavaScript文件里。构建完成后,这些文件变得静态且不会改变,除非重新构建应用程序。
env.sh
脚本
为了应对这个挑战,我用了一个env.sh
脚本,来在运行时修改这些静态文件。
#!/bin/sh
for i in $(env | grep APP_)
do
key=$(echo $i | cut -d '=' -f 1)
value=$(echo $i | cut -d '=' -f 2-)
echo $key=$value
# 注释:以下代码将对所有文件进行处理
# find /usr/share/nginx/html -type f -exec sed -i "s|${key}|${value}|g" '{}' +
# 注释:以下代码将仅处理 .js 和 .css 文件
find /usr/share/nginx/html -type f -name '*.js' -exec sed -i "s|${key}|${value}|g" '{}' +
done
echo '完成'
它是这样工作的。
- 遍历环境变量值:脚本遍历环境变量,只筛选出以特定前缀(例如
APP_
)开头的变量。
# 遍历环境变量中以APP_开头的变量
for i in $(env | grep APP_); do
# 获取变量的值
value=$(printenv $i)
...
done
- 替换 JavaScript 文件中的内容 : 脚本会在
/usr/share/nginx/html
目录下找到所有的 JavaScript 文件,并将这些占位符替换为实际值。
# 找到所有.js文件并替换特定键值对
find /usr/share/nginx/html -type f -name '*.js' -exec sed -i "s|${key}|${value}|g" '{}' +
在 Dockerfile 中加入 env.sh
为了确保「env.sh」脚本在 Docker 容器启动时运行,你需要把它加到 Dockerfile 里。
FROM node:22.4.1-slim as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:1.27.0-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY /nginx-custom.conf /etc/nginx/conf.d/default.conf
# 将 env.sh 移动到 docker-entrypoint.d
COPY env.sh /docker-entrypoint.d/env.sh
RUN chmod +x /docker-entrypoint.d/env.sh
将 env.sh
脚本复制到容器的 /docker-entrypoint.d/
目录中。/docker-entrypoint.d/
目录是 Docker 入口脚本用来执行脚本的一个特殊位置。任何放在这里的脚本在容器启动后,Nginx 开始提供静态文件之前自动运行。
这使得 env.sh
脚本在运行 Docker 容器时可以使用在运行时注入的环境变量来更新这些静态文件。
这里是如何使用这种方法构建和运行Docker镜像的:
构建 Docker 镜像的步骤:
运行 docker build -t docker-approach:2 .
命令。这会构建一个名为 docker-approach:2
的Docker镜像。
运行 Docker 容器,并传递必要的环境变量。
使用 Docker 运行容器,并将主机的 3000 端口映射到容器的 80 端口,同时设置环境变量 APP_API_URL 为 https://test-api.example.com 和 APP_ENV 为 staging。命令如下:
docker run -p 3000:80 -e APP_API_URL=https://test-api.example.com -e APP_ENV=staging docker-approach:2
其中 `-e APP_API_URL=https://test-api.example.com` 设置了 API 的 URL,而 `-e APP_ENV=staging` 设置了应用环境为 staging。这里的版本标签 `docker-approach:2` 表示特定的应用版本。
你可以看到,你设置的环境变量在 Web 应用中运行得很好。
好处是:这种方法允许你在不同环境中部署同一个Docker镜像,无需重新构建。你可以在运行时根据需要更改环境变量,从而使它更加灵活高效。
为什么我选择了运行时注入?
我选择了在运行时注入环境变量,因为这样提供了更大的灵活性和更高的效率,使得一个 Docker 镜像可以在不同环境中使用。这种方法遵循了 DRY 原则,通过避免重复,避免了每次环境变化都需要重新构建镜像的麻烦。
它也符合CI/CD最佳实践,降低了复杂性,并确保了开发、测试和生产环境之间的一致性,从而简化了部署流程并简化了流程。
参考文献为进一步了解运行时注入环境变量,请可以看看这篇:为 Docker 化的 React 应用设置动态环境变量。
请访问我的 GitHub 存储库,查看与此文章相关的代码示例:GitHub Repository Link。
祝你编程愉快,谢谢!