Environment Variables in Docker
A practical guide to environment variables in Docker, including ENV, ARG, .env files, Docker Compose, runtime configuration and secret handling.
Environment variables are one of the standard ways to configure applications running inside Docker containers. Instead of hardcoding configuration values into application code or creating a different image for every environment, you can provide values when a container starts.
Docker supports several mechanisms related to environment variables, including ENV in a Dockerfile, --env and --env-file when starting containers, and environment configuration in Docker Compose. Docker also supports build arguments with ARG, but ARG and environment variables serve different purposes.
Understanding these differences is important for building predictable containers and avoiding one of the most common Docker mistakes: treating environment variables as a secure storage mechanism for secrets.
What Are Environment Variables in Docker?
An environment variable is a named value made available to a process. Applications can read these values at runtime and use them for configuration such as ports, database connection settings, feature flags and external service URLs.
NODE_ENV=production
PORT=3000
API_URL=https://api.example.comWhen an application runs inside a Docker container, these variables exist in the container's process environment. The application can read them in the same general way it would read environment variables outside Docker.
Why Use Environment Variables with Docker?
Containers are commonly designed to be reusable across environments. The same image can be deployed to development, staging and production while receiving different configuration values at runtime.
- Keep environment-specific configuration outside application code.
- Reuse the same Docker image across environments.
- Change runtime configuration without rebuilding the image.
- Configure database and API endpoints.
- Control application modes such as development or production.
- Configure ports and feature flags.
- Integrate containers with deployment platforms.
ENV in a Dockerfile
The ENV instruction defines environment variables in the image. These values become available to subsequent Dockerfile instructions and to processes started from containers created from that image.
FROM node:22
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY . .
CMD ["node", "server.js"]In this example, NODE_ENV and PORT are part of the image configuration. Any container created from the image receives these default values unless they are overridden when the container starts.
Setting Multiple ENV Values
Docker supports both separate ENV instructions and multiple key-value pairs in a single instruction.
ENV NODE_ENV=production
ENV PORT=3000
# Also valid
ENV NODE_ENV=production PORT=3000Using one variable per line can make a Dockerfile easier to read when an application has several configuration values.
Overriding ENV at Container Startup
An environment variable defined in a Dockerfile can be overridden when the container is started.
docker run --env PORT=8080 my-appThe container will receive PORT=8080 instead of the Dockerfile's default value. This allows one image to be configured differently for different environments.
Using docker run --env
The --env or -e option allows you to provide environment variables directly when creating a container.
docker run --env NODE_ENV=production --env PORT=3000 my-appYou can provide multiple --env options when several variables are required.
docker run \
--env NODE_ENV=production \
--env PORT=3000 \
--env API_URL=https://api.example.com \
my-appUsing --env-file
When an application has many environment variables, placing them directly in the docker run command quickly becomes inconvenient. Docker can load variables from a file using --env-file.
NODE_ENV=production
PORT=3000
API_URL=https://api.example.comdocker run --env-file .env my-appThe variables from the file are made available to the container. This approach is convenient for local development and controlled deployment environments.
What Is a .env File?
A .env file is commonly used to store environment configuration in a simple KEY=VALUE format. Docker Compose supports .env files for variable substitution, and Docker can also use environment files as input when starting containers.
APP_NAME=my-app
PORT=3000
NODE_ENV=development
API_URL=http://localhost:4000The exact behavior of a .env file depends on how it is being used. A .env file used by Docker Compose for interpolation is not exactly the same concept as passing an environment file directly into a container with docker run --env-file.
.env Files and Git
Local .env files often contain credentials or environment-specific configuration and should generally not be committed to a public repository when they contain sensitive values.
.env
.env.local
.env.production
.env.*.localA safer pattern is to commit a template containing variable names but not real credentials.
# .env.example
NODE_ENV=
PORT=
API_URL=
DATABASE_URL=Docker Compose Environment Variables
Docker Compose provides several ways to define environment variables. A common approach is the environment section in compose.yaml.
services:
app:
image: my-app
environment:
NODE_ENV: production
PORT: 3000Compose passes these values into the container when the service starts.
Using List Syntax in Docker Compose
The environment section can also use list syntax.
services:
app:
image: my-app
environment:
- NODE_ENV=production
- PORT=3000Both mapping and list forms are supported. Mapping syntax is often easier to read when there are many variables.
Using env_file in Docker Compose
Compose can load container environment variables from an external environment file using env_file.
services:
app:
image: my-app
env_file:
- .envThis keeps a large collection of configuration values outside the Compose file itself and can be convenient for local development.
Environment Variable Substitution in Compose
Compose can substitute environment variables into the Compose configuration. For example, a port mapping can use a variable defined in the environment.
services:
app:
image: my-app
ports:
- "${APP_PORT}:3000"If APP_PORT is set to 8080, Compose can resolve the expression to a mapping from host port 8080 to container port 3000.
Default Values in Compose
Compose variable substitution supports default-value syntax. This can be useful when a value has a reasonable local default.
services:
app:
ports:
- "${APP_PORT:-3000}:3000"If APP_PORT is not set, the expression uses 3000 as the default host port.
ARG vs ENV in Docker
ARG and ENV are frequently confused because both can contain key-value configuration. Their purposes are different.
| Feature | ARG | ENV |
|---|---|---|
| Primary purpose | Build-time configuration | Runtime environment configuration |
| Available during image build | Yes | Yes |
| Available to container process by default | No | Yes |
| Can be overridden at container startup | No | Yes |
| Suitable for secrets | No | No |
For example, ARG can be used to select a build-time version.
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}ENV, on the other hand, defines configuration that is intended to exist in the resulting image or container environment.
ENV NODE_ENV=productionARG Is Not a Secret Mechanism
Passing a password, API key or other sensitive value through ARG does not make it secure. Build arguments can become visible through build metadata or image history depending on how they are used.
# Do not use ARG for credentials
ARG API_KEY=secret-valueEnvironment Variables and Docker Image Builds
It is important to distinguish values required to build an application from values required to run it. A frontend application may need certain variables during its build process, while a backend application may primarily read configuration when it starts.
Build-time values can become embedded into generated application files. Once a value has been compiled into static JavaScript or another artifact, changing the container's runtime environment may not change that value.
Environment Variables in Node.js Containers
Node.js applications can access environment variables through process.env.
const port = process.env.PORT || 3000;
const nodeEnv = process.env.NODE_ENV || "development";
console.log({
port,
nodeEnv,
});When the container starts, Docker provides the configured environment variables to the Node.js process.
docker run --env PORT=8080 --env NODE_ENV=production my-appValidate Required Environment Variables
Applications should validate important environment variables during startup instead of silently continuing with missing configuration.
const required = ["DATABASE_URL", "API_URL"];
for (const name of required) {
if (!process.env[name]) {
throw new Error(`Missing required environment variable: ${name}`);
}
}Failing early makes configuration problems easier to identify and prevents an application from starting in a partially configured state.
Do Not Hardcode Secrets in Dockerfiles
Credentials should not be placed directly in ENV instructions.
# Bad practice
ENV DATABASE_PASSWORD=my-password
ENV API_SECRET=super-secret-valueEven if a secret is later overridden, putting the original value into the Dockerfile can expose it through source control, build logs, image history or other build metadata.
Environment Variables vs Docker Secrets
Environment variables are convenient for ordinary configuration, but secrets require additional consideration. Docker and container orchestration platforms provide mechanisms specifically intended for sensitive values.
For example, a production deployment may provide a database password through a secret-management system instead of putting it directly into the Dockerfile or Compose configuration.
The exact secret mechanism depends on the deployment environment. The important principle is to keep credentials separate from the image and avoid treating ordinary environment variables as encrypted storage.
Do Environment Variables Become Part of the Image?
Environment variables defined with ENV in a Dockerfile become part of the image configuration. They are therefore not equivalent to values injected only when a container starts.
This distinction is another reason to avoid putting sensitive information into ENV. If a value is environment-specific, it is usually better to provide it at runtime.
Passing Variables Without Rebuilding the Image
One of the main advantages of runtime environment configuration is that you can change configuration without rebuilding the image.
docker run --env API_URL=https://staging.example.com my-app
docker run --env API_URL=https://api.example.com my-appBoth containers can use the same image while communicating with different API endpoints.
Environment Variable Precedence
When multiple configuration sources define the same variable, the effective value depends on the Docker command or Compose configuration being used. Understanding the precedence rules is important when debugging configuration issues.
For Docker Compose in particular, distinguish between variables used to substitute values into the Compose file and variables that are actually passed into the container. A variable can exist in the Compose process environment without automatically becoming a container environment variable in every configuration.
Inspecting Container Environment Variables
Docker provides commands that can help inspect the environment associated with a container.
docker inspect my-containerYou can also inspect the environment from inside a running container.
docker exec my-container envBe careful when sharing command output. Environment variables can contain credentials or other sensitive information.
Common Environment Variable Mistakes
| Mistake | Why It Is a Problem | Better Approach |
|---|---|---|
| Hardcoding secrets in ENV | Secrets can become part of the image | Inject secrets through an appropriate secret mechanism |
| Using ARG for passwords | Build arguments are not a secure secret store | Use build secret mechanisms when a build needs credentials |
| Committing .env with credentials | Secrets can enter version control | Ignore sensitive env files and use templates |
| Copying .env into an image | Configuration and secrets become image contents | Provide runtime configuration externally |
| Assuming frontend variables are private | Browser code can expose them | Keep private values server-side |
| Changing runtime ENV expecting a rebuilt value | Build-time values may already be embedded | Understand whether the variable is build-time or runtime |
| Not validating required variables | Application may start with invalid configuration | Validate configuration during startup |
Recommended Environment Variable Structure
A project benefits from having a consistent naming and configuration strategy. Variable names should clearly communicate their purpose and should be documented for developers and deployment systems.
# Application
NODE_ENV=production
PORT=3000
# External services
API_URL=https://api.example.com
# Database
DATABASE_URL=...Sensitive values can be documented in an example file without including the actual credentials.
# .env.example
NODE_ENV=production
PORT=3000
API_URL=
DATABASE_URL=Environment Variables in Development and Production
Development and production usually require different configuration values. For example, development may connect to a local database while production connects to a managed database service.
# Development
NODE_ENV=development
API_URL=http://localhost:4000
# Production
NODE_ENV=production
API_URL=https://api.example.comThe Docker image does not necessarily need to change just because these values change. Runtime configuration can provide the appropriate settings for each environment.
Environment Variables with Docker Compose for Multiple Services
Compose becomes particularly useful when several services need different configuration values. For example, an application and database can be configured independently while sharing a Compose network.
services:
app:
image: my-app
environment:
NODE_ENV: production
DATABASE_URL: postgresql://db:5432/app
db:
image: postgres:18
environment:
POSTGRES_DB: app
POSTGRES_USER: appIn real production environments, database credentials should be handled using appropriate secret-management mechanisms rather than storing real passwords directly in a Compose file.
Use Descriptive Variable Names
Environment variables should be easy to identify and consistent across the project.
- Use uppercase names for conventional environment configuration.
- Use underscores to separate words.
- Choose names that describe the value clearly.
- Use consistent prefixes for related services when useful.
- Document required variables.
- Avoid ambiguous names such as VALUE or CONFIG.
Environment Variables and .dockerignore
If local environment files are not supposed to be copied into an image, they should usually be excluded from the Docker build context with .dockerignore.
.env
.env.*
!.env.exampleThe exact patterns depend on the project's requirements. The important goal is to prevent local credentials and environment-specific files from accidentally becoming build inputs.
A Practical Dockerfile Pattern
A production-oriented Dockerfile commonly provides only safe defaults through ENV and leaves environment-specific configuration to the runtime.
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]The image contains a reasonable default runtime configuration, while deployment-specific values such as API URLs and database connections can be injected when the container starts.
Environment Variable Workflow
A simple workflow helps keep Docker configuration predictable.
- Identify which configuration values the application requires.
- Separate ordinary configuration from sensitive credentials.
- Document expected variables in an example file.
- Keep sensitive local files out of version control.
- Use ENV only for appropriate image-level defaults.
- Use runtime injection for environment-specific values.
- Use secret-management mechanisms for sensitive values.
- Validate required variables when the application starts.
- Verify the final container environment before deployment.
Frequently Asked Questions
How do I pass an environment variable to a Docker container?
You can use docker run with the --env or -e option, for example docker run --env PORT=3000 my-app. For multiple variables, you can also use an environment file with --env-file.
What is the difference between Docker ENV and ARG?
ARG is primarily intended for build-time configuration, while ENV defines environment variables that are available to processes in containers created from the image. ARG values do not automatically become runtime environment variables.
Should passwords be stored in Docker ENV?
No. ENV is not a secure secret-storage mechanism. Sensitive credentials should be supplied through an appropriate secret-management mechanism or secure runtime configuration.
Can I use a .env file with Docker?
Yes. Docker can use environment files with commands such as docker run --env-file, and Docker Compose can use .env files for variable substitution and container configuration. The exact behavior depends on how the file is referenced.
Do environment variables require rebuilding a Docker image?
Not when they are provided at container startup. Runtime environment variables can change without rebuilding the image. However, variables used during an application's build process may become embedded into generated artifacts and can therefore require a new build when changed.
Are Docker environment variables secure?
Ordinary environment variables should not be considered a secure secret store. Depending on how they are configured, they can be inspected by Docker commands or processes with sufficient access. Use dedicated secret-management mechanisms for sensitive credentials.
How can I see environment variables inside a Docker container?
You can inspect container configuration with docker inspect or execute a command such as env inside a running container with docker exec. Be careful not to expose sensitive values when sharing the output.
Helpful Docker Tools
Working with Docker configuration often involves several related tasks. Environment variable generators can help create configuration templates, dotenv parsers can inspect and analyze .env files, Docker Compose generators can create service configurations, Dockerfile generators can help build container definitions, and secret generators can create random values for appropriate secret-management workflows.
Conclusion
Environment variables provide a flexible way to configure Docker containers without creating a separate image for every environment. Dockerfile ENV can provide safe defaults, docker run can inject values at startup, --env-file can load larger configurations, and Docker Compose provides additional mechanisms for multi-container applications.
The most important distinction is between build-time and runtime configuration. ARG is intended for build-time values, while ENV is used for runtime environment configuration. Neither should be treated as a secure place to store credentials. Keeping secrets outside images, validating required configuration and using runtime injection makes Docker deployments easier to maintain and safer to operate.