Nuxt3 Docker 生产环境 runtimeConfig 变量获取失败?解决方案
2025-05-01 05:43:30
修复 Nuxt3 在 Docker 生产环境 env 变量为空的问题
问题来了
兄弟们,咱用 Nuxt3 写了个网站,本地开发跑得好好的,用 PaaS 平台(比如 render.com)部署也没毛病,环境变量都能正常读到。可寻思着自己搞个服务器,用 Docker 部署吧,欸?奇怪了,环境变量咋就变成空的了呢?
具体是这样的:
我在 nuxt.config.ts
里配置了 runtimeConfig
来读取环境变量:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// 这些变量只在服务端可用
// someKey: 'someValue',
// public 部分的变量在客户端和服务端都能用
public: {
uris: {
backHost: process.env.NUXT_BACK_HOST_URL // 期望从环境变量读取
}
}
}
})
然后,准备了一个 .env
文件(或者说,在 docker-compose.yml
里用的 env_file
指向的文件)包含这个变量:
# .env or equivalent file used by Docker
NUXT_BACK_HOST_URL=https://example.com
在 docker-compose.yml
文件里,给 Nuxt 服务指定了 env_file
:
# docker-compose.yml (snippet)
services:
nuxt_app:
build: .
ports:
- "3000:3000"
env_file:
- ./.env # 假设你的 env 文件就在当前目录
# ... 其他配置
为了确认变量确实进了容器,我跑了 docker exec
:
$ docker exec -it <你的容器名或ID> printenv
# 输出包含:
# NUXT_BACK_HOST_URL=https://example.com
# NODE_ENV=production
# ... 其他变量
这证明了 NUXT_BACK_HOST_URL
这个环境变量,的的确确存在于运行的容器环境里。
但是在 Nuxt 代码里获取它的时候:
// 某个 Vue 组件或页面
const { uris } = useRuntimeConfig().public;
const backUri = uris.backHost // 在 Docker 生产环境里拿到的是 undefined 或空!坑爹啊!
明明容器里有这个变量,为啥 useRuntimeConfig
拿不到呢?
补充几点:
- 本地开发(用
npm run dev
)是正常的,Vite 会处理.env
。 - 之前用 PaaS 服务也没这问题,同样的代码和环境变量名。
- 网站是 SSR 模式,不是纯 SPA。
- 项目是在 Dockerfile 里用
npm run build
打包的。但按理说runtimeConfig
是运行时的,跟构建关系不大吧?
刨根问底:为啥变量丢了?
这个问题吧,核心在于 Nuxt3 处理 runtimeConfig
和 Docker 环境变量注入的时机和方式。
-
runtimeConfig
的本质 :runtimeConfig
设计出来,就是为了处理那些在 运行时 才确定或者需要动态配置的变量。比如数据库连接字符串、API 地址等。它主要是在 Nuxt 服务 启动时 读取process.env
来填充配置。 -
构建时 vs 运行时 :
npm run build
这个过程,主要是把你的代码(Vue、TS、JS)打包、编译、优化成最终能在 Node.js 环境跑的服务端代码和给浏览器的静态资源。这时候,它 可能 会处理一些环境变量(比如vite.define
或 build-time 渲染的),但runtimeConfig
设计上更多是关注 启动之后 的环境。 -
Docker 的
env_file
或environment
:你在docker-compose.yml
里用env_file
或者environment
字段,确实能把变量注入到容器的运行环境里。docker exec ... printenv
看到的就是这个结果。 -
关键点:Nuxt 服务进程启动 :问题很可能出在 Docker 容器启动时,运行
node .output/server/index.mjs
(Nuxt build 后的入口) 这个命令的 那个瞬间。虽然printenv
显示变量存在于容器的 shell 环境,但 Nuxt 的 Node.js 进程启动时,它读取process.env
是否真的获取到了那个值?通常情况下是应该获取到的,但有些细微的环节可能导致不一致。 -
Nuxt 读取
process.env
的约定 :虽然你在nuxt.config.ts
里写了process.env.NUXT_BACK_HOST_URL
,Nuxt 在读取 运行时 环境变量来覆盖runtimeConfig
时,有一套自己的规则,特别是针对public
部分。它会查找特定格式的环境变量名来覆盖nuxt.config.ts
中定义的值。
结合这几点,最可能的原因是:虽然 Docker 容器环境里有 NUXT_BACK_HOST_URL
变量,但 Nuxt 服务进程在启动读取 process.env
并填充 runtimeConfig.public
时,要么是没找到它期望的变量名,要么就是某个环节导致进程没能正确继承这个环境变量。
解决办法
别慌,这问题有几种搞法,咱们一个个试。
方案一:确保运行时环境变量直达 Nuxt 进程 (检查 Dockerfile 和启动命令)
这是最基础但也最关键的一步。确保你的 Dockerfile 最终启动 Nuxt 应用的命令能正确继承 docker-compose
注入的环境变量。
-
原理: 标准的 Docker 容器启动方式下,通过
env_file
或environment
设置的环境变量,应该能被CMD
或ENTRYPOINT
指定的主进程(也就是我们的 Nuxt 服务)访问到,即 Node.js 进程能通过process.env
拿到。我们要做的就是确认这个流程没被打断。 -
检查
Dockerfile
:
看看你的Dockerfile
最后是怎么启动应用的。通常是类似这样:# Dockerfile (末尾部分) FROM node:18-alpine AS runtime WORKDIR /app COPY --from=builder /app/.output .output # 从构建阶段拷贝产物 ENV NODE_ENV=production # 设置 Node 环境变量 # 确保端口暴露正确 EXPOSE 3000 # 关键是这一步!这是容器默认执行的命令 CMD ["node", ".output/server/index.mjs"]
这个
CMD
命令运行的 Node.js 进程,理论上可以直接访问到你在docker-compose.yml
里通过env_file
设置的NUXT_BACK_HOST_URL
。 -
操作步骤与确认:
- 确认你的
docker-compose.yml
的env_file
路径是正确的,相对于docker-compose.yml
文件本身。 - 确认你的
.env
文件内容格式没问题(KEY=VALUE
,没有多余空格)。 - 你可以临时修改
Dockerfile
的CMD
来调试,看看启动 Node 前变量是否真的就绪:
然后重新构建镜像 (# 临时调试用 CMD CMD ["sh", "-c", "echo '--- Environment variables before Node start ---' && printenv && echo '--- Starting Nuxt server ---' && node .output/server/index.mjs"]
docker-compose build
) 并启动 (docker-compose up
),查看日志。如果printenv
的输出里包含了NUXT_BACK_HOST_URL=https://example.com
,那就说明环境变量确实传到了启动脚本这一层。如果 Nuxt 还是读不到,那问题可能就在 Nuxt 如何处理process.env
上了(看方案二)。
- 确认你的
-
进阶技巧: 如果你的
CMD
或ENTRYPOINT
不是直接运行node
,而是通过一个 shell 脚本(比如entrypoint.sh
),确保那个脚本里用exec "$@"
来执行传递给它的命令(通常是node .output/server/index.mjs
),这样 Node.js 进程会替换掉 shell 进程,直接继承环境变量,避免潜在的 shell 处理问题。
方案二:使用 Nuxt 专用的运行时环境变量命名约定
Nuxt 为了方便在运行时覆盖 runtimeConfig
,提供了一套基于环境变量的覆盖机制。这通常是解决此类问题的“正道”。
-
原理: Nuxt 在服务启动时,除了读取
nuxt.config.ts
里的默认配置,还会检查特定格式的环境变量。对于runtimeConfig.public
下的配置,它会查找NUXT_PUBLIC_
前缀的环境变量。变量名的构造规则是:NUXT_PUBLIC_
+配置路径全大写
+用下划线替换点
.- 例如,对于
runtimeConfig.public.uris.backHost
,Nuxt 会查找环境变量NUXT_PUBLIC_URIS_BACK_HOST
。
- 例如,对于
-
操作步骤:
-
修改你的
.env
文件(或者docker-compose.yml
的environment
部分),使用 Nuxt 推荐的变量名:# .env 文件修改后 # NUXT_BACK_HOST_URL=https://example.com # 可以保留用于其他目的,或删除 NUXT_PUBLIC_URIS_BACK_HOST=https://your-actual-backend.com # 使用新的变量名和正确的值
或者在
docker-compose.yml
中直接设置:# docker-compose.yml (snippet) services: nuxt_app: # ... build, ports ... environment: # 注意这里不是用 env_file 了,是直接写变量 NUXT_PUBLIC_URIS_BACK_HOST: "https://your-actual-backend.com" NODE_ENV: "production" # 如果你还依赖 .env 文件里的其他变量,可以用 env_file 和 environment 结合 # env_file: # - ./.env
-
nuxt.config.ts
不需要改动! 它里面的process.env.NUXT_BACK_HOST_URL
可以看作是一个“默认值”或者说“构建时的潜在来源”。运行时的覆盖逻辑是由 Nuxt 框架处理的。// nuxt.config.ts 保持原样或简化 export default defineNuxtConfig({ runtimeConfig: { public: { uris: { // 这里可以留空、设置默认值,或者继续用 process.env.NUXT_BACK_HOST_URL 作为开发时的便利 // 运行时 Nuxt 会优先使用 NUXT_PUBLIC_URIS_BACK_HOST 环境变量来覆盖它 backHost: process.env.NUXT_BACK_HOST_URL || 'http://localhost:1337' // 加个本地开发默认值也行 } } } })
-
重新构建镜像并启动容器:
docker-compose build && docker-compose up -d
。 -
访问你的应用,检查
useRuntimeConfig().public.uris.backHost
是否得到了https://your-actual-backend.com
这个值。
-
-
安全建议: 记住,
runtimeConfig.public
里的所有变量最终都会暴露到前端代码里,任何人都能在浏览器开发者工具里看到。千万不要把敏感信息(如 API 密钥、私有地址等)放在public
部分。如果需要服务端私有的环境变量,应该定义在runtimeConfig
的根级别,Nuxt 会查找对应的NUXT_
前缀环境变量(没有PUBLIC_
)。例如,runtimeConfig.apiKey
会被NUXT_API_KEY
环境变量覆盖。
方案三:构建时嵌入(如果变量在构建后无需改变,但不推荐此场景)
这个方案跟你遇到的问题(运行时变量为空)关系不大,但提一下,以防万一是对变量类型理解有偏差。
-
原理: 如果某个值在
npm run build
之后就不会再变了,理论上可以在构建阶段就把它“写死”到代码里。Vite(Nuxt 底层使用)提供了机制,但这不是runtimeConfig
的主要用途,且失去了运行时的灵活性。 -
操作步骤(示意,不推荐用于运行时变量):
- Dockerfile 中使用
ARG
和ENV
在构建阶段传递变量:# Dockerfile (构建阶段) FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ # 定义一个构建参数 ARG NUXT_BACK_HOST_URL_ARG # 将构建参数设为环境变量,让 build 命令能访问 ENV NUXT_BACK_HOST_URL=$NUXT_BACK_HOST_URL_ARG RUN npm install COPY . . # 构建时,理论上 Node.js 进程(npm run build) 能访问到 NUXT_BACK_HOST_URL RUN npm run build # ... 后续运行时阶段 ...
docker-compose.yml
中传递构建参数:# docker-compose.yml services: nuxt_app: build: context: . args: # 把你的变量作为 build arg 传进去 NUXT_BACK_HOST_URL_ARG: "https://example.com" # ... ports, etc ... # 注意:此时 env_file 或 environment 对 NUXT_BACK_HOST_URL 可能就不再重要, # 因为它在构建时可能已被 nuxt.config.ts 读取并“固化”
nuxt.config.ts
(维持原样):
在这种情况下,// nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { public: { uris: { // 在 build 时,process.env.NUXT_BACK_HOST_URL 会被读取 backHost: process.env.NUXT_BACK_HOST_URL } } } })
backHost
的值在npm run build
时就被确定下来,写进了.output
目录里的某个文件。之后即使你在运行时用env_file
设置了不同的NUXT_BACK_HOST_URL
,它也不会改变runtimeConfig.public.uris.backHost
的值了,因为它不再是动态读取process.env
。
- Dockerfile 中使用
-
警告: 这种方法完全违背了使用
runtimeConfig
的初衷(处理运行时配置)。对于像 API 地址这样可能需要在不同环境(开发、测试、生产)或部署后变更的值,绝对不要用构建时嵌入。只有那些真正固定不变、且需要在编译时就确定的值才考虑。对于你的问题,这显然不是正确路线。
检查清单与调试技巧
如果试了方案一和方案二还不行,可以再过一遍这些:
- 路径确认:
docker-compose.yml
里的env_file
路径真的对吗?相对于docker-compose.yml
文件所在位置。 - .env 文件权限: 虽然少见,但确保 Docker 守护进程有读取
.env
文件的权限。 - 变量名拼写:
NUXT_PUBLIC_URIS_BACK_HOST
这样的名字,大小写、下划线都不能错。 - 再次确认
printenv
: 在容器里docker exec <container_id> printenv | grep NUXT
再次确认,特别是用了方案二之后,要看到NUXT_PUBLIC_URIS_BACK_HOST
变量。 - 直接在 Nuxt 代码里打日志: 在 Nuxt 服务启动的早期阶段(比如创建一个简单的 server middleware)打印
process.env
,看看 Node.js 进程视角的环境变量到底有哪些。
把这个文件放到// server/middleware/log-env.ts export default defineEventHandler((event) => { console.log('--- Env Vars Available to Nuxt Process ---'); // 重点看这里! console.log('NUXT_BACK_HOST_URL:', process.env.NUXT_BACK_HOST_URL); console.log('NUXT_PUBLIC_URIS_BACK_HOST:', process.env.NUXT_PUBLIC_URIS_BACK_HOST); // 可以把所有 env 都打出来看看 // console.log(process.env); console.log('------------------------------------------'); // 也可以直接在这里尝试访问 runtimeConfig,看它此时的值 // const config = useRuntimeConfig(); // console.log('Runtime config at middleware:', config.public.uris.backHost); })
server/middleware/
目录下,Nuxt 会自动加载。然后重启容器看日志。
一般来说,遵循方案二,使用 Nuxt 推荐的 NUXT_PUBLIC_
前缀环境变量命名,是最稳妥、最符合 Nuxt 设计思路的解决方案。
相关资源
- Nuxt 3 Runtime Config 文档: https://nuxt.com/docs/guide/going-further/runtime-config
- Docker Compose env_file 文档: https://docs.docker.com/compose/environment-variables/set-environment-variables/#use-the-env_file-attribute