返回

Nuxt3 Docker 生产环境 runtimeConfig 变量获取失败?解决方案

vue.js

修复 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 环境变量注入的时机和方式。

  1. runtimeConfig 的本质runtimeConfig 设计出来,就是为了处理那些在 运行时 才确定或者需要动态配置的变量。比如数据库连接字符串、API 地址等。它主要是在 Nuxt 服务 启动时 读取 process.env 来填充配置。

  2. 构建时 vs 运行时npm run build 这个过程,主要是把你的代码(Vue、TS、JS)打包、编译、优化成最终能在 Node.js 环境跑的服务端代码和给浏览器的静态资源。这时候,它 可能 会处理一些环境变量(比如 vite.define 或 build-time 渲染的),但 runtimeConfig 设计上更多是关注 启动之后 的环境。

  3. Docker 的 env_fileenvironment :你在 docker-compose.yml 里用 env_file 或者 environment 字段,确实能把变量注入到容器的运行环境里。docker exec ... printenv 看到的就是这个结果。

  4. 关键点:Nuxt 服务进程启动 :问题很可能出在 Docker 容器启动时,运行 node .output/server/index.mjs (Nuxt build 后的入口) 这个命令的 那个瞬间。虽然 printenv 显示变量存在于容器的 shell 环境,但 Nuxt 的 Node.js 进程启动时,它读取 process.env 是否真的获取到了那个值?通常情况下是应该获取到的,但有些细微的环节可能导致不一致。

  5. 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_fileenvironment 设置的环境变量,应该能被 CMDENTRYPOINT 指定的主进程(也就是我们的 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

  • 操作步骤与确认:

    1. 确认你的 docker-compose.ymlenv_file 路径是正确的,相对于 docker-compose.yml 文件本身。
    2. 确认你的 .env 文件内容格式没问题(KEY=VALUE,没有多余空格)。
    3. 你可以临时修改 DockerfileCMD 来调试,看看启动 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 上了(看方案二)。
  • 进阶技巧: 如果你的 CMDENTRYPOINT 不是直接运行 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
  • 操作步骤:

    1. 修改你的 .env 文件(或者 docker-compose.ymlenvironment 部分),使用 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 
      
    2. 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' // 加个本地开发默认值也行
              }
            }
          }
      })
      
    3. 重新构建镜像并启动容器:docker-compose build && docker-compose up -d

    4. 访问你的应用,检查 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 的主要用途,且失去了运行时的灵活性。

  • 操作步骤(示意,不推荐用于运行时变量):

    1. Dockerfile 中使用 ARGENV 在构建阶段传递变量:
      # 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 
      
      # ... 后续运行时阶段 ...
      
    2. 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 读取并“固化”
      
    3. 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
  • 警告: 这种方法完全违背了使用 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 设计思路的解决方案。

相关资源