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