E-NO
Docker Compose Environment Variables commands 10 Min Read

Docker Compose Environment Variables: A Practical Guide with Commands and Examples

calendar_today Published: 2026-09-13
update Last Updated: 2026-09-13
analytics SEO Efficiency: 100%
Technical guide illustration for Docker Compose Environment Variables: A Practical Guide with Commands and Examples.

Intro

Environment variables are the primary way to configure containers in Docker Compose without modifying images. They control database credentials, feature flags, API endpoints, and resource limits. When variables are missing, mistyped, or exposed, services fail at startup or leak secrets into logs. This guide walks through the commands and patterns you need to observe, set, validate, and recover environment variables in Docker Compose projects. It is written for developers, DevOps engineers, and platform teams who run multi-container applications locally and in production.

You will learn how to inspect the current Compose version and project, understand variable precedence and substitution, use env files safely, pass variables at build and runtime, run read-only checks, diagnose common failures, and implement a repeatable operational checklist. Each section includes concrete commands with expected output, failure signals, and recovery steps.

Version and Environment Inventory

Before changing any variable, establish what you are running and where configuration lives. Start with read-only observations. Run docker compose version to confirm the CLI version:

docker compose version

Expected output looks like:

Docker Compose version v2.24.6

Older v1 projects may use docker-compose (with a hyphen). If you see command not found, you are either using an old installation or the plugin is missing. Use docker compose version to confirm v2. For projects that must support v1, the syntax for environment variables is mostly identical, but Compose v2 has better interpolation and named profiles.

Confirm the active project name and configuration files with:

docker compose ls

This lists running Compose projects, their status, and config file paths. If the project name is unexpected, check the COMPOSE_PROJECT_NAME variable or the -p flag. Project name affects network names, volume names, and container names, so it matters for troubleshooting.

Inspect the resolved configuration without applying changes:

docker compose config

This prints the fully merged YAML with all variable substitutions applied. It does not start containers. Compare this against your docker-compose.yml to see exactly which values will be used. For a specific service, run docker compose config <service>. If the command errors out with invalid interpolation or required variable is not set, you have a missing variable that must be addressed before any container starts.

List all environment variables currently visible to a running container:

docker compose exec <service> env

or, without entering the container:

docker inspect <container> --format '{{range .Config.Env}}{{println .}}{{end}}'

This output includes variables from the image, the Compose file, the shell environment, and env files. Use it to audit whether secrets are unintentionally exposed. Never print sensitive values to CI logs; use docker inspect with a filtered template or an env-file diff instead.

Record the Docker and Compose versions, the project name, the config file path, and a timestamp before any change. A simple inventory command sequence might look like:

docker version --format '{{.Server.Version}}'
docker compose version
docker compose ls
docker compose config --quiet
date -u +"%Y-%m-%dT%H:%M:%SZ"

Save this output to a temporary file or shared channel. It is your baseline for rollback.

Quick check 1 of 2

During `docker compose up`, how does Compose pass information about provisioned resources to services that depend on a provider?

The passage states: 'This information is passed to services that declare a dependency on the provider service, typically through environment variables.'

Safe Configuration Path

Docker Compose resolves environment variables from several sources in a strict order. Knowing this order prevents unexpected overrides.

Precedence, from highest to lowest, in Compose v2:

  1. Shell environment variables (those already exported in the current session)
  2. Variables from the .env file in the project directory (for --env-file see below)
  3. Variables set in the environment: section of the service
  4. Variables defined in an env_file: list (later files override earlier, and these are injected into the container but not used for interpolation in the Compose file itself)
  5. Dockerfile ENV instructions in the image

A practical example:

Given a docker-compose.yml:

services:
  web:
    image: nginx:alpine
    environment:
      - API_URL=http://api:8080
      - DEBUG=false
    env_file:
      - ./common.env

And common.env:

API_URL=http://localhost:3000
DEBUG=true

If you run docker compose config, you will see that API_URL resolves to http://api:8080 because environment: overrides env_file:. However, inside the running container, the DEBUG variable will be true from common.env unless it is also in environment:. This duality confuses many operators: environment: is only for interpolation and container runtime, while env_file: only injects values into the container; it does not make them available for ${VAR} substitution in the Compose file.

To make substitution explicit and avoid surprises, use an .env file located next to docker-compose.yml (or referenced via --env-file). The default .env file is used only for interpolation in the Compose file; it is not automatically injected into containers. To have variables available both for interpolation and inside the container, you can either:

  • Set them in the shell before running docker compose up
  • Reference them explicitly in environment: using ${VAR}
  • Use multiple env_file: entries for runtime variables and a separate .env for Compose file interpolation

Example of a .env file for interpolation:

POSTGRES_VERSION=16.2
APP_PORT=8080

And in docker-compose.yml:

services:
  db:
    image: postgres:${POSTGRES_VERSION}
    ports:
      - "${APP_PORT}:5432"
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}

Here DB_PASSWORD must also be in the .env file or the shell, otherwise docker compose config fails. If you need to pass sensitive values, use Docker secrets or external secret stores, not plain env files committed to source control.

A safe workflow when changing an environment variable:

  1. Copy the current docker-compose.yml and .env to a backup location.
  2. Run docker compose config to see the resolved output. Redirect it to a file with > /tmp/compose-resolved.yml and inspect for unintended variable replacements or exposed secrets.
  3. Change one variable at a time. If multiple variables interact, document the dependency. For example, changing DATABASE_URL may require changing REDIS_URL if they share a hostname.
  4. Apply the change with docker compose up -d --force-recreate <service> only after docker compose config succeeds.
  5. Verify with a health check or log inspection (see Verification and Diagnostics).

Never edit the .env file directly on a production server without version control. Use a controlled deploy process that applies the same change to Staging first, then Production, with a rollback plan (e.g., revert the file and run docker compose up -d again).

Verification and Diagnostics

After changing environment variables, verify the service actually picked up the new values. Do not assume success from docker compose up -d producing no errors; a service can start and still be misconfigured.

First, check container status:

docker compose ps

Expected healthy output:

NAME                COMMAND                  SERVICE             STATUS              PORTS
myapp-web-1         "nginx -g 'daemon of…"   web                 running (healthy)   0.0.0.0:8080->80/tcp

If the status is restarting, unhealthy, or exited, inspect logs immediately:

docker compose logs --tail=50 <service>

For a misconfigured environment variable, typical log messages include:

  • panic: runtime error: invalid memory address or nil pointer dereference (common in Go apps when a required env var is empty)
  • Error: connect ECONNREFUSED 127.0.0.1:5432 (database host or port wrong from DATABASE_URL)
  • The server requested authentication method unknown to the client (Postgres auth variable mismatch)
  • Error: Missing required environment variable: API_KEY

Use docker compose exec to inspect the live variable value inside the container:

docker compose exec web printenv DEBUG

If the container is restarting too quickly, use docker inspect on the container ID:

docker inspect <container-id> --format '{{.State.Status}} {{.State.ExitCode}}'

For PostgreSQL containers, you can also verify the configured password by connecting:

docker compose exec db psql -U postgres -c "SELECT current_user, current_database();"

If authentication fails, the POSTGRES_PASSWORD variable likely did not reach the container. Check docker compose config output again.

To diagnose variable precedence issues, compare the resolved config against the container's actual environment:

# Resolved Compose environment for service web:
docker compose config web | grep -A5 "environment"
# Actual container environment:
docker inspect $(docker compose ps -q web) --format '{{range .Config.Env}}{{println .}}{{end}}'

Any mismatch indicates an override from env_file, shell, or image defaults.

Run a restart test to confirm persistence of environment-driven configuration. For a stateful service like a database, stop and recreate the container, then verify data and configuration remain:

docker compose stop db
docker compose rm -f db
docker compose up -d db
# After start, check a known variable:
docker compose exec db printenv POSTGRES_DB

If the variable is empty or the database fails to start, the variable was not set in the Compose file but perhaps in a previous shell session or the image's default. This is a common failure when moving from local to CI.

Failure Modes and Recovery

Several common mistakes cause environment variable problems in Docker Compose. Understanding why they happen and how to recover saves time and prevents production incidents.

1. Variable interpolation fails during docker compose up

Symptom:

ERROR: The interpolation of '${DB_PASSWORD}' in 'services.db.environment.POSTGRES_PASSWORD' is not valid. Required variable DB_PASSWORD is missing.

Why: You referenced ${DB_PASSWORD} in the Compose file, but it is not defined in the shell environment or in the .env file. Compose v2 only reads the .env file in the project directory (or specified by --env-file), not any env_file: entries.

Recovery:

  • Add the variable to .env in the same directory as docker-compose.yml.
  • Or export it in the shell before running Compose: export DB_PASSWORD=yourpassword.
  • Or pass it explicitly: docker compose --env-file ./config/secrets.env up -d.
  • Avoid committing secrets to .env; use a CI secret manager and write the file during deployment.

2. Variables from env_file are not used for substitution

Symptom: You set DATABASE_URL=postgres://user:pass@db:5432/mydb in env_file, but the Compose file still shows ${DATABASE_URL} unresolved in cmd or entrypoint.

Why: env_file injects variables into the container at runtime; it is not available to the Compose file parser. The parser only uses shell env vars and the .env file.

Recovery: Move the variable to the .env file if you need it for interpolation, or reference it directly in the command with a hardcoded value. Remember that .env variables are not automatically injected into containers; you must also list them under environment: or env_file: if the application needs them.

3. Sensitive variables leaked via docker inspect or docker compose config

Symptom: Running docker inspect shows passwords, tokens, or API keys in the environment list. These values may end up in CI logs, support bundles, or monitoring dashboards.

Why: Passing secrets through environment: or env_file: makes them visible to anyone with Docker API access on the host. They are also stored in container metadata.

Recovery:

  • Use Docker secrets if running in Swarm mode, or bind-mounted secret files for Compose projects. For example:
services:
  web:
    secrets:
      - db_password
secrets:
  db_password:
    file: ./secrets/db_password.txt
  • At minimum, use an external secret manager and inject secrets via the container entrypoint from a mounted file.
  • Avoid printing env output in CI. Use docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -v 'PASSWORD\|TOKEN\|KEY' to filter sensitive keys, but never trust filters alone; the value may appear in command lines or health checks.

4. Host environment overrides .env unexpectedly

Symptom: In CI or on a developer machine, the same Compose file resolves a variable differently than on another machine. docker compose config shows a different value than what is in .env.

Why: Shell environment variables have higher precedence than .env file variables. If a developer has DEBUG=true exported in their shell, it overrides DEBUG=false in .env. This leads to inconsistent behavior across environments.

Recovery:

  • Run docker compose config locally and in the environment to compare. If you need a deterministic build, unset conflicting variables or use env -i docker compose config to see the baseline.
  • Document which variables are allowed to be overridden by the shell and which must come from .env or CI secrets.
  • Consider using a dedicated project directory with no inherited shell variables.

5. Changes to .env not picked up after docker compose up

Symptom: You edit .env, run docker compose up -d, but the running containers still use old values.

Why: Environment variables are fixed at container creation. Updating .env alone does not recreate existing containers. docker compose up -d only recreates containers with changed image or service config; it may not detect a change in the resolved environment if the Compose file itself has not changed.

Recovery:

  • Force recreation of the affected service: docker compose up -d --force-recreate <service>.
  • Or stop and remove the containers, then start again: docker compose down && docker compose up -d.
  • Before forcing recreation, run docker compose config to confirm the new resolved values.

6. Using :ro or :rw modifiers in env_file paths incorrectly

Symptom: Compose fails to parse an env_file entry like ./env/common.env:ro.

Why: env_file does not support mount modifiers; that syntax is only for volumes. It is a common copy-paste mistake.

Recovery: Remove the modifier: - ./env/common.env. If you need the file to be read-only inside the container, place it in a bind mount and source it via env_file using the container path.

Quick check 2 of 2

Which command allows you to set an environment variable temporarily when running a one-off command in a service?

The passage gives the example: `docker compose run -e DEBUG=1 web python console.py`.

Operations Checklist

Use this checklist before and after any change to Docker Compose environment variables. It assumes a production-like staging environment first, then production. Each item has an owner and a review frequency.

Before change (owner: DevOps engineer or service owner; review: every deploy)

  • [ ] Record current docker compose version and docker compose config output in a dated log file.
  • [ ] Identify all services affected by the variable change using docker compose config --services.
  • [ ] Confirm the variable is not hardcoded in the image or overwritten by a higher-precedence source.
  • [ ] Check for usage of ${VAR} in any command:, entrypoint:, healthcheck:, or environment: sections.
  • [ ] Ensure secrets are not currently in .env or env_file that is committed to source control.
  • [ ] Define the expected value and a validation command for each variable (e.g., docker compose exec web env | grep API_URL must return http://api:8080).
  • [ ] Back up the current Compose file and .env to a versioned location (e.g., Git tag or artifact store).

After change (owner: same engineer; verify within 30 minutes of deploy)

  • [ ] Run docker compose config and compare with expected output.
  • [ ] Start or recreate the service with docker compose up -d --force-recreate <service>.
  • [ ] Check service health with docker compose ps (look for running (healthy)).
  • [ ] Inspect logs for errors related to missing/invalid variables.
  • [ ] Run the validation command for the changed variable.
  • [ ] Perform a restart test: docker compose stop <service> && docker compose start <service> and revalidate.
  • [ ] Confirm no sensitive values appear in docker inspect output or logs.
  • [ ] Update runbooks or documentation with the new variable and its source of truth.

Rollback plan (owner: on-call engineer; to be executed if post-change checks fail)

  • [ ] Revert the .env and Compose file changes from version control.
  • [ ] Run docker compose up -d --force-recreate with the previous configuration.
  • [ ] Verify the previous known-good variable values are restored.
  • [ ] Notify the team and record the incident timeline.

This checklist is not a substitute for CI tests. Add a pipeline step that runs docker compose config --quiet and a smoke test that asserts a service can fetch a known variable. For example, an Nginx container can expose a header with add_header X-App-Env $APP_ENV;, and the test checks the header equals staging or production.

Common Pitfalls and How to Avoid Them

The following pitfalls appear repeatedly in real-world Compose projects. Each includes a concrete example and a preventive measure.

Pitfall 1: Assuming env_file variables are available for Compose interpolation

As noted, env_file is runtime-only. Developers often write:

services:
  app:
    image: myapp
    env_file:
      - ./app.env
    environment:
      - LOG_LEVEL=${LOG_LEVEL}

They expect ${LOG_LEVEL} to be resolved from app.env. It is not. The correct approach is to put LOG_LEVEL in .env or export it in the shell, and optionally also list it in env_file if the app needs it inside the container. A safer pattern is to avoid env_file for variables used in the Compose file altogether; use .env for interpolation and environment: for explicit runtime values.

Pitfall 2: Mixing development and production .env files

Using a single .env for all environments leads to secrets leaking into development containers or production values bleeding into local tests. Instead, use environment-specific files:

.env.staging
.env.production

Deploy with docker compose --env-file .env.production up -d. Keep these files in a secure store, not the Git repository.

Pitfall 3: Overriding a variable in environment: but forgetting the dependency in another service

Suppose service web and worker both need API_URL. You change API_URL only for web. The worker silently keeps the old URL, causing split-brain behavior. To avoid this, define shared variables in a common .env and reference them in both services, or use YAML anchors and a single source of truth. Run docker compose config and grep for the variable name to see all occurrences:

docker compose config | grep -n "API_URL"

Pitfall 4: Not quoting variable values with special characters

YAML parsing can misinterpret values containing :, #, -, or spaces. For example:

environment:
  - GREETING=Hello, World!   # fails because comma and space are okay? Actually the comma is fine, but the `!` might cause issues in some YAML parsers. Better to quote:
  - GREETING="Hello, World!"
  - DSN=postgres://user:pass@db:5432/mydb?sslmode=disable  # colon may cause issues? Actually colon is okay if no space after it. But `#` could comment out the rest.

Always quote values that contain special characters, including *, ?, [, ], {, }, #, &, !, |, >, %, @, and backticks. Use single quotes for literal interpretation and double quotes when you need to include interpolation. Example:

environment:
  - "PASSWORD=pa$$w0rd"  # double quotes prevent $ from interpolating
  - 'REGEX=[0-9]+'

Pitfall 5: Relying on default values without verifying

Compose allows default values with ${VAR:-default}. This can hide typos. If you expect POSTGRES_USER to be admin but the .env has POSTGRES_USR=admin (typo), the variable resolves to the default postgres, which may still work but with the wrong user. Avoid defaults for critical variables, or add a validation step:

environment:
  - POSTGRES_USER=${POSTGRES_USER:?POSTGRES_USER is required}

The :? syntax forces an error if the variable is empty, making mistakes visible.

Pitfall 6: Storing secrets in docker-compose.override.yml and committing it

The override file is convenient for local overrides but often contains secrets like database passwords. If it is committed to source control, secrets leak. Use a .gitignore for override files that contain secrets, or better, never put secrets in Compose files; use Docker secrets (Swarm) or bind-mounted files from a secure location.

Conclusion

Environment variables are the control plane for containerized applications. In Docker Compose, managing them correctly requires understanding the resolution order, separating build-time from runtime configuration, and verifying changes with observable commands. The commands in this article provide a repeatable workflow: inventory the current state, make one scoped change, validate the resolved configuration, observe the running container, and have a rollback path.

Start with a single low-risk variable change in a non-production environment. Run docker compose config to see the effect, recreate the service, and confirm the container sees the expected value using docker compose exec. As you gain confidence, extend the checklist to all environment-driven configuration.

The core principle is operational safety: never inject secrets from unencrypted files, never change multiple interdependent variables at once, and always verify with a concrete command that the running container matches the declared configuration. With these practices, environment variables become a reliable, auditable, and reversible configuration mechanism.

Related Research

Article Quality Score

Reader usefulness 100%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL