BuildWithMatija
  1. Home
  2. Blog
  3. Docker
  4. Docker Compose env file: .env vs env_file vs environment

Docker Compose env file: .env vs env_file vs environment

Precedence rules, security trade-offs, and when to use each approach

13th May 2025·Updated on:21st October 2026·MŽMatija Žiberna·
Docker
Docker Compose env file: .env vs env_file vs environment

🐳 Docker & DevOps Implementation Guides

Complete Docker guides with optimization techniques, deployment strategies, and automation prompts to streamline your containerization workflow.

No spam. Unsubscribe anytime.

Related Posts:

  • •Fix Docker Permission Denied: Volumes, Bind Mounts & CI/CD
  • •How to Configure Development Containers with Docker
  • •Update Docker to Latest Version on Ubuntu
  • •Self-Host Infisical: Ultimate VPS Guide for Secrets
  • •GitHub Actions SSH Deploy to VPS: Run Dev & Prod Safely
📄View markdown version
0

Comments

About the author

Matija Žiberna

Matija Žiberna

Full-stack developer, co-founder

AboutResume

Self-taught full-stack developer sharing lessons from building software and startups.

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in

Fix Docker Permission Denied: Volumes, Bind Mounts & CI/CD
Fix Docker Permission Denied: Volumes, Bind Mounts & CI/CD

14th May 2025

How to Configure Development Containers with Docker
How to Configure Development Containers with Docker

18th August 2025

Update Docker to Latest Version on Ubuntu
Update Docker to Latest Version on Ubuntu

16th August 2025

Self-Host Infisical: Ultimate VPS Guide for Secrets
Self-Host Infisical: Ultimate VPS Guide for Secrets

15th July 2026

GitHub Actions SSH Deploy to VPS: Run Dev & Prod Safely
GitHub Actions SSH Deploy to VPS: Run Dev & Prod Safely

25th May 2026

Contents

  • Quick answer: Docker Compose env file vs `.env` vs `environment:`
  • The Three Mechanisms
  • 1. `environment:` — inline in compose.yml
  • 2. `env_file:` — load from external files
  • 3. The automatic `.env` pickup
  • The Full Precedence Order
  • The `--env-file` CLI Flag
  • When to Use Docker Secrets Instead
  • Troubleshooting: env_file Not Working
  • The Power Combo
  • FAQ
  • What is the difference between `.env` and `env_file` in Docker Compose?
  • Does `docker compose .env` pass variables into the container?
  • Which wins, `environment` or `env_file`?
  • Can I use a different file than `.env`?
  • Should I put secrets in an env file?
  • Where to go next
On this page:
  • Quick answer: Docker Compose env file vs `.env` vs `environment:`
  • The Three Mechanisms
  • The Full Precedence Order
  • The `--env-file` CLI Flag
  • When to Use Docker Secrets Instead
Build with Matija logo

Build with Matija

Senior-led B2B websites, applications, content systems, and digital infrastructure. Business-first, full-stack, AI-assisted, no handoffs.

Services

  • B2B Website Development
  • CMS Architecture Review & Platform Blueprint
  • Next.js + Payload Advisory
  • AI Integration & Implementation

Resources

  • CMS Hub
  • B2B Website Strategy
  • E-commerce Hub
  • Blog
  • Case Studies
  • About Matija

Payload CMS

  • Payload CMS Developer
  • Payload CMS Migration
  • Payload CMS Demos
  • All Payload CMS Resources

Discuss your project

Planning a rebuild, migration, application, workflow change, or platform decision? Start with the business problem and the system behind it.

Book a discovery callContact me →
© 2026Build with Matija•All rights reserved•Privacy Policy•Terms of Service
BuildWithMatija
Get In Touch

Early on, I kept bumping into the same frustrating problem: I'd set a variable in my .env file, Docker Compose would seem to ignore it, then I'd add it under environment: and it'd suddenly work — but then something else would break. I didn't understand the system.

Turns out Docker Compose doesn't have one mechanism for environment variables. It has three, and they interact in a specific precedence order that's non-obvious until you've been burned by it.

This guide covers all three mechanisms, how they rank against each other, the --env-file CLI flag most developers don't know about, when to switch to Docker Secrets instead, and the fixes for the most common failure modes.

Quick answer: Docker Compose env file vs .env vs environment:

  • .env (project root): loaded automatically. Its values feed ${VAR} interpolation inside compose.yaml. It does not, by itself, put variables into your containers.
  • env_file: (per service): loads the variables from the listed file(s) straight into that service's container environment.
  • environment: (per service): sets variables inline in the compose file, and wins over env_file: when both define the same key.

So the short version: .env fills in the blanks in your compose file, env_file: bulk-loads container variables, and environment: is for explicit per-service values and overrides.

Tip

TL;DR — Quick reference

MechanismWhere definedInterpolation supportPriority
docker compose run -eCLI at runtimen/aHighest
environment:compose.ymlYes — ${VAR} resolves from shell / .env2nd
env_file: directivecompose.yml → external fileKeep it out of the file; do it in environment:3rd
Image ENV directiveDockerfileNoLowest
The project-level .env is not in this table because it does not set container variables directly. It supplies values for ${VAR} interpolation in the compose file.

The Three Mechanisms

1. environment: — inline in compose.yml

Define variables directly in your compose file:

yaml
services:
  app:
    environment:
      - DB_HOST=postgres
      - DB_PORT=5432
      # Or mapping syntax:
      API_URL: http://localhost:${HOST_PORT}

The key feature here is interpolation: values can reference shell variables or variables from your .env file using ${VAR} syntax. If HOST_PORT=3000 is set in your shell or .env, the above resolves to http://localhost:3000 at runtime.

Use environment: for:

  • Service-specific config that doesn't need to be shared across services
  • Values that depend on shell interpolation
  • Overrides on top of a shared env_file:

The downside: literal secrets in your compose file end up in version control. Never put passwords or tokens here as hardcoded values — use interpolation from a git-ignored .env file instead.


2. env_file: — load from external files

Tell Compose to load variables from one or more external files:

yaml
services:
  app:
    env_file:
      - ./common.env
      - ./app.env

The file format is simple — one KEY=value per line, # for comments:

env
# common.env
DB_HOST=postgres
DB_PORT=5432
REDIS_URL=redis://cache:6379

Multiple files and load order: files listed later override those listed earlier. So if both common.env and app.env define DB_HOST, the value from app.env wins.

The required field (Compose v2.24.0+): by default, Compose throws an error if a listed env file doesn't exist. You can make files optional:

yaml
env_file:
  - path: ./default.env
    required: true    # default — error if missing
  - path: ./override.env
    required: false   # silently skip if missing

Useful for environment-specific overrides that may not exist in all deployment contexts.

Interpolation: the reliable place for ${VAR} substitution is the compose file itself, so I keep env_file: contents as plain KEY=value lines and put anything that needs interpolation under environment:.


3. The automatic .env pickup

This is the one that catches most people. Docker Compose automatically loads a .env file from your project root (next to your compose.yaml) without any configuration. You don't need to declare it anywhere.

This file serves a different purpose from env_file: — it's primarily a source for variable interpolation inside compose.yaml itself, not a direct injection into the container environment.

env
# .env — auto-loaded from project root
HOST_PORT=3000
DB_PASSWORD=secret
yaml
# compose.yaml — uses values from .env via interpolation
services:
  app:
    ports:
      - "${HOST_PORT}:3000"
    environment:
      - DB_PASSWORD=${DB_PASSWORD}

If you don't specify --env-file and haven't set COMPOSE_DISABLE_ENV_FILE=true, this automatic loading always happens. It's documented behavior, not a side effect.

Note

The automatic .env file is not the same as the env_file: directive. The .env auto-load provides variables for interpolation in compose.yaml. The env_file: directive injects variables directly into the container's environment. Both work simultaneously — they serve different purposes.


The Full Precedence Order

When the same variable is defined in multiple places, Docker Compose resolves it in this order (highest wins):

  1. docker compose run -e VAR=value — CLI flag at runtime
  2. environment: in the compose file (including values interpolated from your shell or .env)
  3. env_file: directive
  4. ENV in the Dockerfile

Separately, for ${VAR} interpolation inside the compose file, a variable set in your shell beats the same variable in .env.

A concrete example — DB_HOST defined in three places:

env
# .env (project root, auto-loaded)
DB_HOST=from-dotenv
yaml
# compose.yaml
services:
  app:
    env_file:
      - ./app.env     # contains: DB_HOST=from-env-file
    environment:
      - DB_HOST=from-environment

Result: the container sees DB_HOST=from-environment because environment: (level 2) beats env_file: (level 3).

Run docker compose run -e DB_HOST=from-cli app bash and the container sees from-cli — level 1 beats everything.

Note that DB_HOST=from-dotenv in .env never reaches the container here. .env only matters if the compose file references ${DB_HOST}.


The --env-file CLI Flag

Most developers know about the env_file: directive in compose.yaml. Fewer know about the --env-file flag on the CLI itself — and they're different things.

bash
# Override the default .env with a specific file at runtime
docker compose --env-file ./config/.env.staging up

This flag replaces the automatic .env pickup for that command — it doesn't merge with it. It changes which file Compose uses as the interpolation source, not which variables get injected into containers.

You can pass multiple --env-file flags in one command. Later files override earlier ones:

bash
docker compose --env-file .env --env-file .env.override up

The practical use case: switching between environments without editing files.

bash
# Development
docker compose --env-file envs/dev.env up

# Staging
docker compose --env-file envs/staging.env up

Each file sets different values for DB_HOST, API_URL, etc. — same compose file, different environments, no file editing required.

Note

--env-file (CLI flag) changes the interpolation source for compose.yaml. env_file: (compose.yaml directive) injects variables into the container. They look similar but operate at different layers.


When to Use Docker Secrets Instead

Env files work fine for most config. But there's a security boundary worth knowing: environment variables are visible in docker inspect.

Run docker inspect <container> on any running container and you'll see the full Env array in the output — including anything you passed via environment: or env_file:. For non-sensitive config this is fine. For database passwords, API keys, and tokens in production, it's a problem.

Docker Compose supports secrets, which are mounted as files under /run/secrets/<n> inside the container rather than exposed as environment variables:

yaml
services:
  app:
    image: myapp:latest
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Your app reads the secret from /run/secrets/db_password at runtime.

env_file vs secrets — when to use which:

env_file:Docker Secrets
Visible in docker inspectYes (Env array)No (mount only)
File formatKEY=valueRaw value (entire file)
Local dev complexityLowLow (Compose v2)
Production (Swarm/K8s)⚠️ Avoid for secrets✅ Native support
Best forNon-sensitive configPasswords, tokens, keys

For local development, the overhead difference is small. For production, if you're using Docker Swarm or Kubernetes, secrets are the right tool for anything sensitive.


Troubleshooting: env_file Not Working

Three failure modes come up constantly.

1. Quotes show up in the value

If your app receives "secret" instead of secret, the quotes were passed through as part of the value. Current Compose v2 parses quoted values in env files, but other tools reading the same file (plain docker run --env-file, shell scripts, older docker-compose v1) may keep the quotes literally. Since unquoted values work everywhere, the safest fix is to strip them:

env
DB_PASSWORD=secret

2. Path is relative to the compose file, not your terminal

The path in env_file: resolves relative to the compose.yaml location, not your current working directory. If you run docker compose from a parent directory, paths that look correct may not resolve.

yaml
# This resolves relative to where compose.yaml lives
env_file:
  - ./config/app.env

If in doubt, use an absolute path or always run docker compose from the same directory as your compose.yaml.

3. UTF-8 BOM on Windows-created files

If you created the .env file on Windows (Notepad, certain editors), it may have a UTF-8 BOM (byte order mark) at the start of the file. Docker Compose can't parse this — the first variable name gets garbled or the file fails silently.

Fix: open the file in VS Code, check the encoding indicator in the bottom-right corner, and re-save as UTF-8 (not UTF-8 with BOM).


The Power Combo

The most maintainable pattern combines all three mechanisms in the right roles:

yaml
services:
  app:
    env_file:
      - ./common.env          # shared config across services
      - path: ./local.env
        required: false       # optional local overrides
    environment:
      - NODE_ENV=${NODE_ENV}  # interpolated from .env or shell
      - DEBUG=false           # service-specific, non-sensitive
env
# .env (git-ignored, project root)
NODE_ENV=development
HOST_PORT=3000
env
# common.env (can be committed if non-sensitive)
DB_HOST=postgres
DB_PORT=5432
REDIS_URL=redis://cache:6379

The .env provides interpolation values for the compose file. common.env holds shared non-sensitive config. environment: handles service-specific values and anything needing shell interpolation. For actual secrets in production, graduate to Docker Secrets.

And for switching between dev/staging/prod: docker compose --env-file envs/staging.env up — no file editing, no mistakes.

FAQ

What is the difference between .env and env_file in Docker Compose?

.env is read automatically from the project directory and supplies values for ${VAR} interpolation in your compose file. env_file: is declared on a service and loads variables directly into that container's environment. Different layers, different jobs.

Does docker compose .env pass variables into the container?

Not automatically. A variable in .env only reaches a container if you reference it as ${VAR} under environment: (or elsewhere in the compose file) or also list the file under env_file:.

Which wins, environment or env_file?

environment: wins. If the same key appears in both, the container gets the environment: value.

Can I use a different file than .env?

Yes, with docker compose --env-file <path> up. That replaces the default .env as the interpolation source for that command. For container variables, list files under env_file: instead.

Should I put secrets in an env file?

For local development it is common, as long as the file is git-ignored. In production, secrets in environment: or env_file: are visible via docker inspect, so prefer Docker secrets or a secrets manager. I cover the second option in self-hosting Infisical for secrets management.

Where to go next

Once your variables are sorted, these are the natural follow-ups:

  • Deploying a Compose stack from CI with per-environment config: GitHub Actions SSH deploy to a VPS.
  • Shipping a frontend with Nginx and Compose: Vite Docker production deployment.
  • Containerising a Next.js app: Next.js standalone Dockerfile guide.
  • A dev setup that uses Compose end to end: configuring development containers with Docker.
  • Running Payload in containers on your own server: see my Payload CMS hosting guide for deployment options.

Thanks, Matija.

Comments