Environment Variables in Docker: A Complete Guide
Docker gives you five different ways to get a variable into a container, and picking the wrong one is how secrets end up baked into an image layer that anyone with registry access can read.
Methods for Setting Environment Variables
Which one to reach for depends on whether the value is a secret, whether it needs to differ per environment, and whether it needs to survive a container restart.
The -e Flag on the Command Line
The simplest method is passing variables directly when running a container:
Good for: a quick one-off variable, a debug session, a CI step that already has the value in scope
Bad for:anything you don't want showing up in a process list or your shell history, and anything past a handful of variables
Environment Files with --env-file
Point Docker at a file instead of typing every flag out:
Good for: keeping one .env per environment and swapping which one you point at
Bad for: the file still sits on disk in plaintext, so it needs the same care as any other .env file
Dockerfile ENV Instructions
Bake a default value into the image so containers built from it start with something reasonable:
Don't put secrets in ENV instructions. They get baked into a layer, and a plain docker history or docker save on the image exposes them to anyone who can pull it, nothing more than registry access required.
Docker Compose
For anything beyond a single container, Compose handles both inline values and file-based ones in the same service definition:
Variable Interpolation
Compose automatically loads a .env file in the same directory and allows variable substitution:
Docker Secrets for Swarm and Kubernetes
Secrets take this further than any of the methods above: encrypted at rest, mounted only into the containers that need them, never sitting in an environment variable an inspect command can dump.
Your application reads these as files at /run/secrets/secret_name, not as environment variables, which means a little extra code on the read side:
Multi-Stage Builds and Secrets
A build stage that needs a private npm token or an SSH key is a common way secrets leak, even when the final image never sees them, because the intermediate build layer still does. BuildKit's secret mounts fix that by making the secret available only for the duration of one RUN command:
Production Best Practices
Keep Secrets Out of the Image
ENV and ARG values in a Dockerfile end up visible in the image layers regardless of what happens at runtime. Inject secrets when the container starts, not when the image is built:
Mount Secret Files Read-Only
A secrets volume with write access is a bug waiting for a bad script. Mount it read-only:
Split Config by Environment with Compose Overrides
Keep one base compose file and layer environment-specific overrides on top instead of maintaining separate full configs:
Fail Fast on Missing Variables
A container that starts with half its config missing and fails three requests later is harder to debug than one that refuses to start at all. Check for required variables before the app does anything else:
Add .env to .dockerignore
A COPY . . in your Dockerfile grabs .env along with everything else unless you tell it not to:
None of these five methods is universally right. A side project running on a single host is fine with --env-file. A team running Swarm or Kubernetes in front of paying customers should be on Docker Secrets, full stop. What matters more than which one you pick is that you pick deliberately, instead of defaulting to ENV in the Dockerfile because it was the first thing that worked in development.