nuxt.jsnuxt3.js

Nuxt 3 runtimeConfig not populated from process.env in production Docker build, but process.env is available later


I'm encountering an issue where my server-side runtimeConfig in Nuxt 3 is not being populated correctly from environment variables when running the production build inside a Docker container managed by Docker Compose. However, I can see the correct value when accessing process.env directly within server middleware during a request.

Setup:

Problem:

In my nuxt.config.ts, I define a server-side runtime configuration variable intended to be loaded from an environment variable:

// nuxt.config.ts
export default defineNuxtConfig({
  // ...
  runtimeConfig: {
    // This should be populated from the environment variable
    jwtSecret: process.env.JWT_SECRET,
    // ... 
  },
  // ...
})

My .env file (used by Docker Compose) defines the variable:

# .env file
JWT_SECRET=a9d3fd7cee941559f18f43dd6c87fc39cf611a540ffbc6b4b1f5675b569af176
# ... other vars

My docker-compose.yml passes this variable to the portal service:

# docker-compose.yml
services:
  portal:
    # ... image, build context, etc.
    environment:
      JWT_SECRET: ${JWT_SECRET}
      # ... other env vars
    # ... ports, depends_on, etc.

When I run an API route (e.g., /api/auth/login.post.ts), the code attempts to access the secret:

// server/api/auth/login.post.ts (simplified)
import { defineEventHandler, useRuntimeConfig } from 'h3';
import jwt from 'jsonwebtoken';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event);
  const secret = config.jwtSecret;

  console.log('JWT Secret value INSIDE login handler:', secret);
  console.log('Type of portalJwtSecret:', typeof secret);

  try {
    // ... authentication logic ...
    // This line throws the error because 'secret' is an empty string
    const token = jwt.sign({ userId: user.id }, secret, { expiresIn: '7d' });
    // ... return token ...
  } catch (error) {
    console.error('Login error:', error);
    // ... handle error ...
  }
});

Symptoms:

  1. Login Fails: The login handler throws Error: secretOrPrivateKey must have a value because config.portalJwtSecret is an empty string (""), although its type is correctly inferred as string.
  2. Middleware Sees process.env: I added a server middleware (server/middleware/log-config.ts) to check the values during a request:
    // server/middleware/log-config.ts
    import { defineEventHandler, useRuntimeConfig } from 'h3';
    export default defineEventHandler((event) => {
      const config = useRuntimeConfig(event);
      console.log('[Middleware log-config] JWT Secret:', config.jwtSecret);
      console.log('[Middleware log-config] Type of portalJwtSecret:', typeof config.jwtSecret);
      console.log('[Middleware log-config] process.env.JWT_SECRET:', process.env.JWT_SECRET);
    });
    
    The logs from this middleware show:
    [Middleware log-config] JWT Secret: 
    [Middleware log-config] Type of jwtSecret: string
    [Middleware log-config] process.env.JWT_SECRET: a9d3fd7cee941559f18f43dd6c87fc39cf611a540ffbc6b4b1f5675b569af176
    
    This clearly shows that process.env.JWT_SECRET is correctly set in the running Node.js environment when the request is handled, but useRuntimeConfig(event).jwtSecret is returning an empty string.
  3. Constants Work: If I define a variable in runtimeConfig directly as a string literal (e.g., someConstant: 'ThisValueWorks'), it is correctly accessible via useRuntimeConfig().
  4. nuxt dev Works: This issue does not occur when running locally with nuxt dev; runtimeConfig is populated correctly from the .env file in development mode.
  5. NUXT_ Prefix Fails: I also tried prefixing the environment variable with NUXT_ (e.g., NUXT_JWT_SECRET) hoping for automatic population, but useRuntimeConfig().jwtSecret was undefined in that case.

What I've Tried:

Question:

Why is useRuntimeConfig() returning an empty string for server-side variables defined via process.env in nuxt.config.ts when running the production build (.output/server/index.mjs)? How can I ensure runtimeConfig is correctly initialized with these environment variables at server startup within the Docker container?


Solution

  • TLDR;

    Just add NUXT_ prefix to your docker-compose.yml's environment field.

    Explanation

    Some of the answers can be found in this nuxt documentation. I just don't know why process.env works in the same level with runtimeConfig.

    Since you are using docker compose with environment variable, you can adjust the following to make the config.jwtSecret accessible:

    add the NUXT_ prefix.

    # docker-compose.yml
    services:
      portal:
        # ... image, build context, etc.
        environment:
          NUXT_JWT_SECRET: ${NUXT_JWT_SECRET}
          # ... other env vars
        # ... ports, depends_on, etc.
    

    Docker compose will read the .env file and set NUXT_JWT_SECRET as the container's environmental variable and will work with production setup of Nuxt.

    You can try it yourself here in my github repo that I just created.

    https://github.com/YutaInouePH/env_check

    Here are the following outputs.

    Access http://localhost:3000

    ↓

    $ docker compose up -d
    $ docker compose logs -f
    app-1  | Listening on http://0.0.0.0:3000
    app-1  | Frontned:  mysecret 2025-04-11T10:41:53.944Z
    app-1  | JWT Secret value INSIDE server side: mysecret
    app-1  | Type of portalJwtSecret: string
    

    browser output