Docker compose environment variables come from three sources per the Docker Compose docs: inline environment: in compose.yaml, env_file: directive (loads .env-style file into container), and .env file in the project directory (for variable substitution in compose.yaml itself). The team that uses env_file for app config and .env for compose-time substitution has the right separation.
Table of contents
- The environment key
- The env_file directive
- The .env file for substitution
- Variable substitution order
- CLI override
- Reading compose.yaml env from container
- Secrets management
- FAQ
The environment key
Inline in compose.yaml:
services:
app:
environment:
DATABASE_URL: postgres://db/app
LOG_LEVEL: info
Or list syntax:
services:
app:
environment:
- DATABASE_URL=postgres://db/app
- LOG_LEVEL=info
The team that uses inline env vars for dev has quick visibility. The team that uses env_file for prod has separation.
The env_file directive
services:
app:
env_file:
- .env.app
- .env.secrets
Each file is loaded into the container’s environment. .env.app for config, .env.secrets for sensitive values. The team that uses env_file has secrets separate from compose.yaml (which can be in git).
The .env file for substitution
Compose reads .env from the project directory for variable substitution in compose.yaml:
# .env
POSTGRES_VERSION=16
APP_PORT=8080
# compose.yaml
services:
db:
image: postgres:${POSTGRES_VERSION}
app:
ports:
- "${APP_PORT}:8080"
The team that uses .env for substitution has one place to configure compose-time values.
Variable substitution order
Compose substitutes variables from (in order):
- OS environment.
.envfile in project directory..envfile passed via--env-file.
OS env wins over .env. The team that uses OS env for overrides has dev/prod differentiation without changing files.
CLI override
docker compose --env-file .env.production up
docker compose run -e DEBUG=true app
The team that uses CLI overrides has runtime config without changing compose files.
Reading compose.yaml env from container
After container starts, env vars are visible via:
docker compose exec app env
docker compose exec app printenv DATABASE_URL
The team that uses this debugs config issues.
Secrets management
For real secrets, don’t use .env (committed by mistake):
- Docker secrets (Swarm mode).
- External secret managers (Vault, AWS Secrets Manager).
docker compose --env-filefrom a secrets manager.
The team that uses Vault Agent Injector pulls secrets at runtime, never in compose files.
FAQ
What’s the difference between .env and env_file?
.env is for compose.yaml variable substitution (read by docker compose CLI). env_file is loaded into the container’s runtime environment.
Can I use ${VAR:-default} syntax?
Yes - docker compose supports shell-style default values. The team that uses ${VAR:-latest} has fallback values for missing env.
Do env vars override Dockerfile ENV?
Yes - compose env vars override Dockerfile ENV directives. The team that uses compose overrides has flexible config.
Can I encrypt env_file contents?
Not directly with compose. Use Docker secrets (Swarm) or external secret managers. The team that has sensitive env_file uses proper secret management.
How do I see what env vars are set in a container?
docker compose exec <service> env lists all env vars. The team that uses this for debugging has visibility.
If you are sizing the infrastructure for the kind of project this post covers, the RunxBuild hosting calculator is the right place to model the line items. The compute, the memory, the storage, the bandwidth, the database - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers. The RunxBuild dashboard is where the team sees the actual usage in one place.
Useful related references: