Shipping Next.js with Docker Without Leaking Secrets
A production-minded Docker setup for Next.js standalone — build args vs runtime env, .dockerignore, and why API keys should never bake into the image.
Docker makes deploys repeatable. It also makes it dangerously easy to bake API keys into an image you later push to a registry.
I’ve tightened this on my own portfolio: Next.js standalone, a multi-stage Dockerfile, Compose for runtime config, and a hard rule — secrets never enter the build stage.
The shape of the image
- Builder — install deps, run
next buildwithoutput: "standalone" - Runner — copy standalone output, static assets, and run as a non-root user
Only non-secret build config belongs in ARG / ENV during build. For me that’s things like APP_URL for absolute links and metadata — not Gmail, Resend, or OpenAI keys.
# Build stage — public config only
ARG APP_URL
ENV APP_URL=${APP_URL}
# Runner — inject secrets at container start, not bake time
# Compose: env_file: .envWhy build-time secrets are a trap
If you pass RESEND_API_KEY as a Docker ARG, it can end up in image history even if you “don’t use it” in the final stage. Anyone who can pull the image can inspect layers.
The fix is boring and correct:
- Put
.envin.dockerignore - Pass secrets only at runtime (
env_file/ orchestrator secrets) - Keep server keys off the
NEXT_PUBLIC_*prefix so they never ship to the browser
Compose responsibilities
services:
next-app:
build:
context: .
args:
APP_URL: ${APP_URL}
env_file:
- .envBuild gets the public URL. The running container gets email and AI keys. The published image stays reusable across environments.
Checklist before docker compose up --build
-
.envis gitignored and dockerignored - No secret
ARG/ENVin the Dockerfile - API routes read
process.envat request time - Old registry tags rebuilt after removing baked secrets
- Keys rotated if an old image was ever public
Final thought
Docker isn’t just packaging — it’s a security boundary. Treat the image as something you might accidentally publish. If a key would embarrass you in a registry dump, it doesn’t belong in the build.