How to Use Environment Variables in Docker

Diagram showing Docker container using environment variables: .env file, docker run -e flags, Dockerfile ENV, and secure secrets management for configurable runtime settings. best.

How to Use Environment Variables in Docker
SPONSORED

Sponsor message — This article is made possible by Dargslan.com, a publisher of practical, no-fluff IT & developer workbooks.

Why Dargslan.com?

If you prefer doing over endless theory, Dargslan’s titles are built for you. Every workbook focuses on skills you can apply the same day—server hardening, Linux one-liners, PowerShell for admins, Python automation, cloud basics, and more.


Why Environment Variables Matter in Your Docker Journey

Working with Docker means managing configurations that change between development, testing, and production environments. Hard-coding values like database passwords, API keys, or service endpoints directly into your application or Dockerfile creates security risks and maintenance nightmares. Environment variables solve this problem by allowing you to inject configuration at runtime, keeping sensitive data separate from your code and making your containers truly portable across different environments.

Environment variables in Docker are key-value pairs that provide runtime configuration to your containers without modifying the application code or rebuilding images. They act as a bridge between your infrastructure and applications, allowing the same container image to behave differently based on external settings. This separation of configuration from code follows the twelve-factor app methodology and represents a fundamental best practice in modern containerized applications.

Throughout this guide, you'll discover multiple methods for setting environment variables in Docker, understand when to use each approach, learn security best practices for handling sensitive data, and explore real-world patterns that professional teams use daily. Whether you're running a single container or orchestrating complex multi-service applications, mastering environment variables will make your Docker workflow more secure, flexible, and maintainable.

Setting Environment Variables with the Docker Run Command

The most straightforward way to pass environment variables to a Docker container is using the -e or --env flag with the docker run command. This method provides immediate control over individual variables and works perfectly for quick tests or simple deployments where you need to override specific values.

When you execute a docker run command with environment variables, Docker injects these values into the container's environment before starting the main process. The application running inside can then access these variables through standard environment variable mechanisms provided by the programming language or framework.

docker run -e DATABASE_HOST=postgres.example.com -e DATABASE_PORT=5432 myapp:latest

This approach shines when you need to quickly test different configurations or override default values for specific runs. You can set multiple variables by repeating the -e flag, and the values become immediately available to any process running inside the container.

Passing Multiple Variables Efficiently

For scenarios requiring numerous environment variables, repeatedly typing -e flags becomes cumbersome and error-prone. Docker allows you to reference variables from your host environment without explicitly specifying their values, which proves especially useful in CI/CD pipelines where environment variables are already set by the automation system.

docker run -e DATABASE_HOST -e DATABASE_PORT -e API_KEY myapp:latest

When you omit the value after the variable name, Docker automatically looks for a variable with the same name in your host environment and passes its value to the container. If the host variable doesn't exist, Docker passes an empty string, so this technique works best when you're certain the host environment has these variables defined.

"The separation between configuration and code isn't just a best practice—it's the difference between a system that scales securely and one that becomes a maintenance burden as complexity grows."

Understanding Variable Precedence and Override Behavior

When the same environment variable is defined in multiple places—such as in the Dockerfile and the docker run command—Docker follows a specific precedence order. Variables set with the -e flag during docker run always take priority over variables defined in the Dockerfile using the ENV instruction. This override mechanism gives you flexibility to maintain sensible defaults while allowing runtime customization.

Method Precedence Level Best Use Case Security Level
docker run -e flag Highest Runtime overrides, testing Low (visible in process list)
--env-file option High Environment-specific configs Medium (file-based)
docker-compose environment High Multi-container applications Medium (file-based)
Dockerfile ENV Low Default values, build-time settings Low (baked into image)
Docker secrets Highest (for secrets) Sensitive production data High (encrypted at rest)

Using Environment Files for Organized Configuration

As your applications grow in complexity, managing dozens of environment variables through command-line flags becomes impractical. Environment files provide a cleaner, more maintainable approach by consolidating all variables into dedicated configuration files that can be version-controlled, reviewed, and shared across team members.

An environment file is simply a text file containing key-value pairs, one per line, following the standard KEY=value format. Docker reads this file and sets all the variables it contains when starting the container, giving you the same result as multiple -e flags but with significantly better organization.

# production.env
DATABASE_HOST=postgres.production.example.com
DATABASE_PORT=5432
DATABASE_NAME=myapp_production
REDIS_URL=redis://cache.production.example.com:6379
API_RATE_LIMIT=1000
LOG_LEVEL=info
FEATURE_FLAG_NEW_UI=true

To use an environment file, pass the --env-file flag to docker run followed by the path to your file. Docker processes the file and injects all variables into the container environment before starting your application.

docker run --env-file production.env myapp:latest

Structuring Environment Files for Different Environments

Professional teams typically maintain separate environment files for different deployment contexts—development, staging, and production. This pattern allows you to keep environment-specific configurations organized while using the same container image across all environments, which is essential for ensuring that what you test is exactly what you deploy.

🔹 Create separate files like development.env, staging.env, and production.env

🔹 Use descriptive naming that clearly indicates the target environment

🔹 Include comments in your environment files to document the purpose of each variable

🔹 Never commit files containing production secrets to version control

🔹 Consider using template files with placeholder values for documentation purposes

Handling Comments and Special Characters

Environment files support comments using the hash symbol (#) at the beginning of a line, which helps document your configuration choices. However, values containing spaces, special characters, or quotes require careful handling to ensure Docker interprets them correctly.

# Database configuration
DATABASE_URL="postgresql://user:pass@host:5432/db"

# API configuration with spaces
API_WELCOME_MESSAGE="Welcome to our service"

# Values with equals signs need quotes
ENCODED_KEY="base64==encoded==string"

When a value contains spaces or special characters, wrapping it in double quotes ensures Docker treats the entire string as a single value. Without quotes, Docker might interpret spaces as separators or special characters as syntax elements, leading to unexpected behavior.

"Environment files transform configuration management from a series of command-line acrobatics into a documented, reviewable process that the entire team can understand and maintain."

Defining Environment Variables in Dockerfiles

The Dockerfile ENV instruction allows you to set environment variables that become part of the image itself. These variables are available during the build process and persist into containers created from the image, making them ideal for default values, application constants, and build-time configuration that rarely changes between deployments.

FROM node:18-alpine

# Set default environment variables
ENV NODE_ENV=production \
    PORT=3000 \
    LOG_LEVEL=info \
    APP_NAME="MyApplication"

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Variables defined with ENV are baked into the image layers, which means they're visible in the image history and can be inspected by anyone with access to the image. This characteristic makes ENV suitable for non-sensitive configuration but inappropriate for secrets, passwords, or API keys.

Build-Time Variables with ARG

While ENV creates runtime environment variables, the ARG instruction defines build-time variables that exist only during the image build process. Build arguments provide a way to parameterize your Dockerfile, allowing you to create flexible build processes that can produce different image variants from the same Dockerfile.

FROM node:18-alpine

# Build-time argument with default value
ARG BUILD_VERSION=latest
ARG NODE_ENV=production

# Convert ARG to ENV if needed at runtime
ENV APP_VERSION=${BUILD_VERSION}
ENV NODE_ENV=${NODE_ENV}

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=${NODE_ENV}

COPY . .
CMD ["node", "server.js"]

To pass values to build arguments, use the --build-arg flag with docker build. This mechanism allows CI/CD systems to inject version numbers, build identifiers, or environment-specific settings during the image creation process.

docker build --build-arg BUILD_VERSION=1.2.3 --build-arg NODE_ENV=production -t myapp:1.2.3 .

Combining ARG and ENV for Maximum Flexibility

A powerful pattern involves using ARG to accept build-time parameters and then converting them to ENV variables when you need those values available at runtime. This approach gives you the flexibility of build-time parameterization while ensuring the values persist into the running container.

Instruction Scope Visibility Primary Use
ENV Build and runtime Image layers and running containers Default runtime configuration
ARG Build-time only Build process and image history Parameterizing the build process
ARG → ENV Build and runtime Image layers and running containers Build-time parameterization with runtime persistence
"Understanding the distinction between build-time and runtime configuration is fundamental to creating Docker images that are both flexible and secure."

Managing Environment Variables with Docker Compose

Docker Compose elevates environment variable management to the orchestration level, providing sophisticated mechanisms for configuring multi-container applications. The docker-compose.yml file supports several methods for setting environment variables, each suited to different scenarios and complexity levels.

The environment key in a service definition allows you to specify variables directly in the compose file using either the key-value mapping format or the array format. This inline approach works well for non-sensitive configuration that you want visible in your infrastructure-as-code.

version: '3.8'

services:
  web:
    image: myapp:latest
    environment:
      - DATABASE_HOST=postgres
      - DATABASE_PORT=5432
      - REDIS_URL=redis://cache:6379
      - LOG_LEVEL=debug
    ports:
      - "3000:3000"
    depends_on:
      - postgres
      - cache

  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}

  cache:
    image: redis:7-alpine

Using env_file in Docker Compose

For better organization and security, Docker Compose supports the env_file directive, which loads environment variables from external files just like the --env-file flag in docker run. This approach keeps sensitive configuration out of your compose file while maintaining the convenience of declarative infrastructure.

version: '3.8'

services:
  web:
    image: myapp:latest
    env_file:
      - common.env
      - production.env
    ports:
      - "3000:3000"

  worker:
    image: myapp:latest
    env_file:
      - common.env
      - production.env
    command: ["node", "worker.js"]

When multiple environment files are specified, Docker Compose reads them in order, with later files overriding values from earlier ones. This layering mechanism enables sophisticated configuration strategies where you maintain common settings in one file and environment-specific overrides in others.

Variable Substitution in Compose Files

Docker Compose performs variable substitution using values from the host environment or a special .env file in the same directory as docker-compose.yml. This feature allows you to parameterize your compose files, making them more flexible and reusable across different deployment contexts.

# .env file in the same directory as docker-compose.yml
APP_VERSION=1.2.3
DB_PASSWORD=secure_password_here
EXTERNAL_PORT=8080
version: '3.8'

services:
  web:
    image: myapp:${APP_VERSION}
    environment:
      - DATABASE_PASSWORD=${DB_PASSWORD}
    ports:
      - "${EXTERNAL_PORT}:3000"

The .env file in Docker Compose serves a specific purpose: it provides values for variable substitution in the compose file itself, not for passing variables directly to containers. To pass variables to containers, use the environment or env_file keys in your service definitions.

"Docker Compose transforms environment variable management from a container-level concern into an orchestration-level capability, enabling consistent configuration across complex multi-service applications."

Security Best Practices for Sensitive Environment Variables

Environment variables often contain sensitive information like database passwords, API keys, and authentication tokens. Handling these values securely requires understanding the limitations of environment variables and implementing appropriate protective measures for different deployment scenarios.

The fundamental security challenge with environment variables is their visibility. Any process running in the container can read them, they appear in container inspection output, and they're visible in process listings on the host system. For truly sensitive data in production environments, Docker secrets or external secret management systems provide stronger security guarantees.

Avoiding Common Security Pitfalls

⚠️ Never commit environment files containing real secrets to version control systems

⚠️ Avoid logging environment variables in application output or error messages

⚠️ Don't expose sensitive variables in Dockerfile ENV instructions

⚠️ Restrict access to environment files using file system permissions (chmod 600)

⚠️ Rotate secrets regularly and update environment configurations accordingly

Using Docker Secrets for Production Workloads

Docker Swarm mode provides a secrets management system designed specifically for sensitive data. Secrets are encrypted during transit and at rest, and they're mounted into containers as files rather than environment variables, reducing their visibility to processes and logging systems.

# Create a secret from a file
echo "my_secure_password" | docker secret create db_password -

# Create a secret from stdin
docker secret create api_key api_key.txt
version: '3.8'

services:
  web:
    image: myapp:latest
    secrets:
      - db_password
      - api_key
    environment:
      - DATABASE_HOST=postgres
      - DATABASE_USER=appuser

secrets:
  db_password:
    external: true
  api_key:
    external: true

Inside the container, secrets appear as files in the /run/secrets/ directory. Your application reads these files to access sensitive values, which provides better security than environment variables because the values don't appear in process listings or container inspection output.

Integrating External Secret Management Systems

For enterprise environments, dedicated secret management systems like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault provide comprehensive solutions for storing, accessing, and rotating secrets. These systems integrate with Docker through various mechanisms, including init containers that fetch secrets before the main application starts or sidecar containers that provide secrets through a local API.

"Security in containerized environments isn't about finding one perfect solution—it's about layering appropriate controls based on your specific threat model and operational requirements."

Accessing Environment Variables in Your Application Code

Once environment variables are set in your Docker container, your application needs to read and use them. Every programming language provides standard mechanisms for accessing environment variables, and understanding these patterns helps you write portable, configurable applications.

In Node.js, environment variables are accessible through the process.env object, which provides a simple key-value interface to all environment variables available to the process.

// Node.js example
const dbHost = process.env.DATABASE_HOST || 'localhost';
const dbPort = parseInt(process.env.DATABASE_PORT || '5432', 10);
const logLevel = process.env.LOG_LEVEL || 'info';

console.log(`Connecting to database at ${dbHost}:${dbPort}`);
console.log(`Log level set to: ${logLevel}`);

For Python applications, the os.environ dictionary provides access to environment variables with similar simplicity and flexibility.

# Python example
import os

db_host = os.environ.get('DATABASE_HOST', 'localhost')
db_port = int(os.environ.get('DATABASE_PORT', '5432'))
log_level = os.environ.get('LOG_LEVEL', 'info')

print(f"Connecting to database at {db_host}:{db_port}")
print(f"Log level set to: {log_level}")

Implementing Configuration Validation

Production applications should validate environment variables at startup rather than discovering missing or invalid configuration during runtime. Failing fast with clear error messages helps operators identify and fix configuration problems quickly.

// Node.js configuration validation
function validateConfig() {
  const required = ['DATABASE_HOST', 'DATABASE_PORT', 'API_KEY'];
  const missing = required.filter(key => !process.env[key]);
  
  if (missing.length > 0) {
    throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
  }
  
  const port = parseInt(process.env.DATABASE_PORT, 10);
  if (isNaN(port) || port < 1 || port > 65535) {
    throw new Error('DATABASE_PORT must be a valid port number');
  }
}

validateConfig();
console.log('Configuration validated successfully');

Using Configuration Libraries for Complex Applications

As applications grow, managing configuration through raw environment variable access becomes cumbersome. Configuration libraries provide structured approaches to configuration management, including validation, type conversion, and hierarchical configuration merging.

Popular libraries include dotenv for Node.js, python-decouple for Python, and Viper for Go. These tools abstract away the complexity of environment variable handling while providing features like default values, type conversion, and configuration file support.

"Configuration validation isn't just about preventing errors—it's about creating clear contracts between your infrastructure and application that make troubleshooting faster and deployments more reliable."

Debugging Environment Variable Issues

Environment variable problems manifest in subtle ways—applications fail to connect to services, features behave unexpectedly, or containers exit immediately after starting. Developing systematic debugging approaches helps you quickly identify and resolve configuration issues.

The docker inspect command reveals all environment variables set in a container, providing a comprehensive view of the configuration that's actually being applied. This inspection capability proves invaluable when troubleshooting discrepancies between expected and actual configuration.

# Inspect environment variables in a running container
docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' container_name

# Inspect environment variables in an image
docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' myapp:latest

Interactive Debugging with Docker Exec

When you need to verify environment variables from inside a running container, docker exec provides interactive access. This approach lets you see exactly what the application sees, including any variable substitution or modification that might occur during container initialization.

# Print all environment variables in a running container
docker exec container_name env

# Print a specific environment variable
docker exec container_name printenv DATABASE_HOST

# Start an interactive shell to explore the environment
docker exec -it container_name /bin/sh

Common Environment Variable Problems and Solutions

Problem: Variables set in docker-compose.yml aren't appearing in the container

Solution: Check that you're using the environment key under the service definition, not at the top level. Verify that docker-compose is actually reading your compose file by running docker-compose config to see the resolved configuration.

Problem: Variable substitution in docker-compose.yml returns empty values

Solution: Ensure your .env file is in the same directory as docker-compose.yml and uses the correct KEY=value format without spaces around the equals sign. Check that variable names match exactly, including case sensitivity.

Problem: Secrets in environment variables are visible in logs or container inspection

Solution: Migrate sensitive data to Docker secrets or an external secret management system. As a temporary measure, ensure your application doesn't log environment variables and restrict access to docker inspect commands.

Advanced Patterns and Real-World Examples

Professional Docker deployments often require sophisticated environment variable strategies that go beyond basic key-value pairs. Understanding these advanced patterns helps you build more maintainable and scalable containerized applications.

Multi-Stage Builds with Environment-Specific Configuration

Multi-stage Dockerfiles can use different environment variables for build and runtime stages, allowing you to optimize images for specific environments while maintaining a single Dockerfile.

# Build stage with development tools
FROM node:18 AS builder
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage with minimal footprint
FROM node:18-alpine
ENV NODE_ENV=production \
    PORT=3000

WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

EXPOSE 3000
CMD ["node", "dist/server.js"]

Dynamic Configuration with Template Files

Some applications require configuration files that can't be directly populated with environment variables. A common pattern involves using template files and an entrypoint script that performs variable substitution before starting the main application.

#!/bin/sh
# entrypoint.sh

# Replace environment variables in configuration template
envsubst < /app/config.template.json > /app/config.json

# Start the main application
exec "$@"
# Dockerfile
FROM myapp:base

COPY config.template.json /app/
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT ["entrypoint.sh"]
CMD ["node", "server.js"]

Health Checks Using Environment Variables

Docker health checks can reference environment variables, allowing you to create flexible health check configurations that adapt to different deployment environments.

FROM node:18-alpine

ENV PORT=3000 \
    HEALTH_CHECK_PATH=/health

WORKDIR /app
COPY . .

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js || exit 1

EXPOSE ${PORT}
CMD ["node", "server.js"]
"Advanced environment variable patterns aren't about complexity for its own sake—they're about creating flexible, maintainable systems that can adapt to changing requirements without requiring image rebuilds."

Performance Considerations and Optimization

While environment variables are lightweight, certain patterns and practices can impact container startup time, image size, and runtime performance. Understanding these considerations helps you make informed decisions about configuration management strategies.

Large numbers of environment variables can slightly increase container startup time as Docker processes and injects them into the container environment. For applications with hundreds of configuration values, consider alternative approaches like configuration files mounted as volumes or configuration services that applications query at startup.

Environment variables defined with ENV in Dockerfiles create additional image layers, which marginally increases image size. Combining multiple ENV instructions into a single multi-line instruction reduces layer count and keeps images leaner.

# Less efficient - creates three layers
ENV DATABASE_HOST=postgres
ENV DATABASE_PORT=5432
ENV DATABASE_NAME=myapp

# More efficient - creates one layer
ENV DATABASE_HOST=postgres \
    DATABASE_PORT=5432 \
    DATABASE_NAME=myapp

Caching Considerations for Build Arguments

Build arguments affect Docker's layer caching behavior. When a build argument value changes, Docker invalidates the cache for that layer and all subsequent layers. Structure your Dockerfile to place instructions that use frequently-changing build arguments later in the file to maximize cache utilization.

FROM node:18-alpine

# Stable base configuration - good cache utilization
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Variable configuration - place later
ARG BUILD_VERSION=latest
ENV APP_VERSION=${BUILD_VERSION}

COPY . .
CMD ["node", "server.js"]

Frequently Asked Questions

How do I pass environment variables from my host machine to a Docker container?

Use the -e flag with docker run to pass individual variables, or use --env-file to load variables from a file. For variables already set in your host environment, you can use -e VARIABLE_NAME without a value, and Docker will automatically use the host's value. In Docker Compose, use the environment key or env_file directive in your service definition.

What's the difference between ENV and ARG in a Dockerfile?

ENV creates environment variables that persist into the running container and are available at both build time and runtime. ARG defines build-time variables that only exist during the image build process and don't persist into containers. Use ARG for parameterizing builds and ENV for runtime configuration. You can convert ARG to ENV by setting ENV to the value of an ARG.

Are environment variables secure for storing passwords and API keys?

Environment variables provide basic security by keeping secrets out of your code, but they're visible in container inspection output and process listings. For production workloads with sensitive data, use Docker secrets in Swarm mode or integrate with dedicated secret management systems like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault for stronger security guarantees.

How can I override environment variables defined in a Dockerfile?

Variables set with the -e flag during docker run or in the environment section of docker-compose.yml always override variables defined with ENV in the Dockerfile. This precedence allows you to set sensible defaults in the image while maintaining flexibility to override them at runtime for different deployment environments.

Why aren't my environment variables appearing in the container?

Common causes include typos in variable names, incorrect syntax in environment files (missing equals signs or extra spaces), using the wrong Docker Compose key (environment vs env_file), or variable substitution failures in docker-compose.yml. Use docker inspect or docker exec to verify what variables are actually set in the container, and check that your .env file is in the correct location for Docker Compose.

Can I use environment variables in Docker Compose file paths and port mappings?

Yes, Docker Compose supports variable substitution using ${VARIABLE_NAME} syntax throughout the compose file, including in port mappings, volume paths, and image tags. Define these variables in a .env file in the same directory as docker-compose.yml or set them in your host environment before running docker-compose commands.

How do I handle environment variables that contain special characters or spaces?

Wrap values containing spaces or special characters in double quotes in environment files. For command-line usage with docker run, use shell quoting appropriate for your operating system. In docker-compose.yml, YAML string quoting rules apply—use quotes for values containing special characters and escape quotes within strings with backslashes.

What's the best way to manage environment variables across multiple environments?

Create separate environment files for each environment (development.env, staging.env, production.env) and load the appropriate file based on your deployment context. Use a common.env file for shared configuration and environment-specific files for overrides. Never commit files containing real secrets to version control—use template files with placeholder values for documentation instead.