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:
npm run build inside a Docker multi-stage build. The final stage runs CMD ["node", ".output/server/index.mjs"].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:
Error: secretOrPrivateKey must have a value because config.portalJwtSecret is an empty string (""), although its type is correctly inferred as string.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.runtimeConfig directly as a string literal (e.g., someConstant: 'ThisValueWorks'), it is correctly accessible via useRuntimeConfig().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.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:
.env file..env, docker-compose.yml, and nuxt.config.ts.docker-compose.override.yml (none found).docker compose down), removing .nuxt, .output, node_modules, running npm install, and rebuilding (docker compose up --build -d --force-recreate).JWT_SECRET, PORTAL_JWT_SECRET, NUXT_PORTAL_JWT_SECRET).process.env.VAR_NAME is accessible via middleware logs.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?
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