Skip to Content
DocsDockerData & NetworkingEnvironment Variables

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:

Terminal
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-fileCompose env_file:
Where you use itA single docker run commandA service block inside compose.yaml
ScopeOne container, for that one runRe-applied every time that service starts
Typical useTesting one container by handMulti-container apps — covered in the next section

docker run --env-file

.env.production
API_URL=https://api.example.com FEATURE_NEW_CHECKOUT=true

This is a normal .env file, same format as a project without Docker.

Terminal
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:

compose.yaml
services: web: build: . env_file: - .env.production

Every 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=VALUE pair per line.
  • Blank lines and lines starting with # are ignored.
  • No export keyword — that is bash syntax, not .env syntax.
  • 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

Terminal
docker exec next-dev env

Lists 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 byWhen the value is setExamples
NEXT_PUBLIC_SOMETHINGBrowser code (client-side)Build time — baked into the JavaScript bundleNEXT_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 timeDATABASE_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:

Dockerfile
# 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 build
Terminal
docker 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

  1. Run my-next-app with a server-only environment variable using -e, then use docker exec my-next-app env to confirm it is there.
  2. Create a .env.production file and run the same container with --env-file instead of -e.
  3. Add a NEXT_PUBLIC_ variable to your app, rebuild with --build-arg, and confirm the browser can see it.
  4. Try passing that same NEXT_PUBLIC_ variable with -e at run time instead — confirm it has no effect.

Quick Check

  • Can you explain the difference between docker run --env-file and Compose’s env_file:?
  • Do you know the formatting rules a .env file follows?
  • Do you know which kind of Next.js variable needs --build-arg instead of -e?
  • Can you explain why secrets should never be set with a Dockerfile ENV instruction?

Next → Multi-container

docker environment variables, nextjs public env docker, docker build-arg, docker secrets env file, nextjs docker env vars

Last updated on