Environment Variables
This page explains how a value like a database address gets into your running app, the two ways Docker can read that value from a .env file, and one Next.js detail that trips up almost everyone the first time.
How a Value Reaches Your Code
Docker takes environment variables at two different times: build time (docker build) or run time (docker run). Most variables use run time — the same image can run on your laptop and on a server, each with its own values, without rebuilding.
Docker never reads a .env file on its own — you always have to tell it to. Next.js reads .env.local automatically, but only outside Docker, when you run npm run dev directly; that automatic behavior does not carry over into a container. Inside a container, you supply each value yourself:
docker run -e DATABASE_URL=postgres://localhost:5432/mydb my-next-app-e NAME=value sets one variable inside the container — the same mechanism as export DATABASE_URL=... in a terminal. process.env.DATABASE_URL in your code then reads that value, never a .env file.
Two Ways Docker Reads a .env File
Typing -e repeatedly for every variable gets unwieldy once you have more than one or two. Put them in a file instead, then hand that file to Docker one of two ways.
docker run --env-file | Compose env_file: | |
|---|---|---|
| Where you use it | A single docker run command | A service block inside compose.yaml |
| Scope | One container, for that one run | Re-applied every time that service starts |
| Typical use | Testing one container by hand | Multi-container apps — covered in the next section |
docker run --env-file
API_URL=https://api.example.com
FEATURE_NEW_CHECKOUT=trueThis is a normal .env file, same format as a project without Docker.
docker run --env-file .env.production my-next-app--env-file reads the file from your computer at run time. The file itself is never copied into the image — only the values inside it are set as variables.
Compose env_file:
The same .env.production file can be handed to a service in compose.yaml instead:
services:
web:
build: .
env_file:
- .env.productionEvery variable inside the file becomes available to that service, the same way --env-file does for docker run. The Multi-container section covers compose.yaml in full — keep env_file: in mind as the second way once you get there.
What a .env File Needs
A .env file is plain text, not a script:
- One
KEY=VALUEpair per line. - Blank lines and lines starting with
#are ignored. - No
exportkeyword — that is bash syntax, not.envsyntax. - Do not wrap values in quotes unless your app expects the quotes literally; Docker passes them through as-is.
Never put real secrets — database passwords, API keys — directly into a Dockerfile with ENV or ARG. Both get baked into the image’s build history, visible to anyone who runs docker history on it. Pass secrets through a .env file at run time instead, and add that file to .gitignore so it never reaches version control.
Checking What Actually Made It Inside
docker exec next-dev envLists every environment variable set inside that container — a quick way to confirm your value actually arrived.
The Next.js Gotcha: NEXT_PUBLIC_
Next.js has two kinds of environment variables, and they do not both follow the run-time rule above:
| Read by | When the value is set | Examples | |
|---|---|---|---|
NEXT_PUBLIC_SOMETHING | Browser code (client-side) | Build time — baked into the JavaScript bundle | NEXT_PUBLIC_MAPS_API_KEY, NEXT_PUBLIC_ANALYTICS_ID |
| Everything else (no prefix) | Server-side code only (API routes, Server Components) | Run time — read fresh each time | DATABASE_URL, JWT_SECRET, STRIPE_SECRET_KEY |
Setting a NEXT_PUBLIC_ variable with -e or --env-file at run time has no effect on the browser code. Next.js already compiled its value into the JavaScript files earlier, during npm run build.
For a value the browser needs, provide it during the build instead, using a Dockerfile ARG:
# In the builder stage, before RUN npm run build
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
RUN npm run builddocker build --build-arg NEXT_PUBLIC_API_URL=https://api.example.com -t my-next-app .Different NEXT_PUBLIC_ values for staging versus production need a separate image build for each — not just a different --env-file at run time. This is a limitation of client-side environment variables, not something specific to Docker.
Hands-on Task
- Run
my-next-appwith a server-only environment variable using-e, then usedocker exec my-next-app envto confirm it is there. - Create a
.env.productionfile and run the same container with--env-fileinstead of-e. - Add a
NEXT_PUBLIC_variable to your app, rebuild with--build-arg, and confirm the browser can see it. - Try passing that same
NEXT_PUBLIC_variable with-eat run time instead — confirm it has no effect.
Quick Check
- Can you explain the difference between
docker run --env-fileand Compose’senv_file:? - Do you know the formatting rules a
.envfile follows? - Do you know which kind of Next.js variable needs
--build-arginstead of-e? - Can you explain why secrets should never be set with a Dockerfile
ENVinstruction?
Next → Multi-container