MeshWorld MeshWorld.
Docker DevOps Security Backend How-To 7 min read

How to Use Environment Variables in Docker

Cobie
By Cobie

Docker containers have no access to your host’s environment variables by default. That means your database URL, API keys, and feature flags don’t just appear inside a container — you have to pass them in explicitly. There are several ways to do this, each with different tradeoffs between convenience and security.

:::note[TL;DR]

  • Inline in docker run: -e KEY=value (fine for development, avoid in production)
  • From host shell: -e KEY with no value (container inherits from host environment)
  • From a file: --env-file .env (good for local dev)
  • In Compose: environment: block or env_file: key
  • For production secrets: Docker Secrets or external secret managers (AWS SSM, Vault) — never bake secrets into images :::

Prerequisites

  • Docker installed and running
  • Basic familiarity with docker run and docker-compose.yml

How do you pass environment variables to a single container?

Inline on the command line:

docker run -e DATABASE_URL="postgres://user:pass@localhost:5432/mydb" myimage

Multiple variables:

docker run \
  -e DATABASE_URL="postgres://user:pass@localhost:5432/mydb" \
  -e NODE_ENV=production \
  -e PORT=3000 \
  myimage

Inherit from the host shell (useful for secrets already in your shell environment):

# Export in your shell first
export DATABASE_PASSWORD="supersecret"

# Pass it by name — Docker reads the current value
docker run -e DATABASE_PASSWORD myimage

From a file:

docker run --env-file .env myimage

The .env file format is one KEY=VALUE per line. Lines starting with # are comments. No quotes needed around values:

# .env
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
NODE_ENV=development
API_KEY=abc123

How do you use environment variables in Docker Compose?

There are three approaches and they can be combined.

Inline in docker-compose.yml:

services:
  api:
    image: myimage
    environment:
      NODE_ENV: production
      PORT: 3000

Reference host environment variables:

services:
  api:
    environment:
      DATABASE_URL: ${DATABASE_URL}   # Read from host shell or .env file
      API_KEY: ${API_KEY}

Compose automatically loads a .env file from the project directory and makes its contents available for ${VAR} substitution in the compose file itself.

Use a separate env file for the container:

services:
  api:
    env_file:
      - .env
      - .env.local    # Optional override file

The difference: environment: ${VAR} pulls variables into the compose file for substitution. env_file: injects the file’s contents directly into the container’s environment at runtime.


What is the variable precedence order in Docker Compose?

Highest to lowest priority:

  1. Variables set with --env-file on the docker compose CLI command
  2. Shell environment variables in the terminal running docker compose
  3. .env file in the project directory
  4. environment: block values in docker-compose.yml

In practice: if NODE_ENV=production is set in your CI/CD environment’s shell, it overrides whatever’s in your .env file. This is usually what you want — CI environments often inject production secrets through environment variables, and those should take precedence.


How do you see what variables are set inside a running container?

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

# Or filter for a specific one
docker exec mycontainer env | grep DATABASE

# Using docker inspect
docker inspect mycontainer | grep -A 20 '"Env"'

:::warning docker inspect shows all environment variables including secrets in plaintext. Anyone with Docker socket access can read them. This is one of the reasons you should not pass long-lived production secrets as plain environment variables — use Docker Secrets or a secret manager instead. :::


How do you handle secrets in production?

Plain environment variables are convenient but not secure for sensitive values:

  • They appear in docker inspect output
  • They’re visible to any process in the container
  • They can leak into logs if your app crashes or logs its config

Option 1: Docker Secrets (Swarm mode only)

Docker Secrets store encrypted values that are mounted as files inside the container at /run/secrets/:

# Create a secret
echo "supersecret" | docker secret create db_password -

# Use in compose (Swarm mode)
services:
  api:
    image: myimage
    secrets:
      - db_password

secrets:
  db_password:
    external: true

Inside the container, your app reads the secret from a file:

const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

Option 2: Mount secrets as files at runtime

For non-Swarm deployments, the same pattern works with bind mounts:

services:
  api:
    volumes:
      - /run/secrets/db_password:/run/secrets/db_password:ro

Option 3: External secret managers

This is the production standard at most organizations. The container fetches secrets at startup from AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager:

# Example: fetch from AWS Secrets Manager in your entrypoint
export DB_PASSWORD=$(aws secretsmanager get-secret-value \
  --secret-id myapp/db_password \
  --query SecretString --output text)
exec "$@"

The container needs IAM permissions (instance profile or Kubernetes service account) to access the secret store — no credentials need to be baked into the image or passed as environment variables.


What should you never do?

Don’t bake secrets into your image:

# Bad — this is in the image layer forever, even if you delete it later
ENV API_KEY=supersecret123

Anyone with access to your image can read this with docker inspect. It also gets committed to your image history. Remove sensitive values from ENV instructions entirely.

Don’t commit .env files with real secrets:

# .gitignore
.env
.env.local
.env.*.local

Commit a .env.example with placeholder values instead. Real values go in .env locally and are injected by your CI/CD platform in production.

Don’t log environment variables on startup:

// Very common, very bad
console.log('Starting with config:', process.env);

This dumps all secrets to stdout, which often ends up in a log aggregator where it’s searchable and persistent.


How do you validate required variables at startup?

If your app requires certain environment variables, check for them at startup and fail fast with a clear error:

// Node.js example
const required = ['DATABASE_URL', 'JWT_SECRET', 'PORT'];
const missing = required.filter(key => !process.env[key]);

if (missing.length > 0) {
  console.error('Missing required environment variables:', missing.join(', '));
  process.exit(1);
}
# Python example
import os
import sys

required = ['DATABASE_URL', 'SECRET_KEY', 'PORT']
missing = [k for k in required if not os.environ.get(k)]

if missing:
    print(f"Missing required environment variables: {', '.join(missing)}", file=sys.stderr)
    sys.exit(1)

Failing fast at startup is much better than a cryptic error halfway through a request when the app tries to use an undefined variable.


Summary

  • -e KEY=value for quick one-offs; --env-file .env for local dev
  • In Compose, use env_file: to inject a file’s contents, or environment: ${VAR} to pull from the host environment
  • Shell variables > .env file > environment: block — later entries override earlier ones
  • Never bake secrets into images or commit .env with real values
  • For production: Docker Secrets (Swarm), file mounts, or external secret managers

FAQ

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

ARG is build-time only — it’s available during docker build but not in the running container. ENV sets a variable that persists in the image and is available at runtime. Use ARG for build configuration (platform, version numbers). Use ENV for runtime defaults. Never use either for secrets — ARG values appear in build history, and ENV values appear in docker inspect.

My container can’t see a variable I set in .env. What’s wrong?

Check a few things. First, Compose’s .env substitution applies to the compose file itself (for ${VAR} references), not automatically to containers unless you have an env_file: key or explicit environment: entries. Second, confirm the .env file is in the same directory as your docker-compose.yml. Third, check for typos — variable names are case-sensitive.

Can I change environment variables without rebuilding the container?

Yes — docker run and docker compose up inject environment at container start, not at build time. Stop and restart the container with new values. No rebuild needed. The image itself doesn’t contain the values (unless you used ENV in the Dockerfile).