Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

Docker Compose env File: .env, env_file, and the Precedence That Trips Everyone

Sean

Platform Writer

Jul 21, 2026
8 min read

Docker Compose has two different things both called ‘env file’, and conflating them is the source of nearly every environment-variable headache. There is the .env file, which Compose reads automatically to substitute ${VARIABLES} inside your compose.yaml. And there is the env_file attribute, which sets environment variables inside a container. One configures the Compose file itself; the other configures what the running process sees. They look alike, they overlap, and knowing which is which is most of the battle.

Docker Compose env File: .env, env_file, and the Precedence That Trips Everyone

The two are easy to mix up because both hold KEY=value lines. The distinction - and the precedence order when they conflict - is what this comes down to.

Table of contents

The .env file: interpolation into compose.yaml

A file literally named .env next to your compose.yaml is read automatically. Its values fill in ${...} placeholders in the Compose file before anything runs:

# .env
POSTGRES_VERSION=16
APP_PORT=8080
# compose.yaml
services:
  db:
    image: postgres:${POSTGRES_VERSION}
  web:
    ports:
      - "${APP_PORT}:8080"

This is variable substitution for the config file itself - image tags, port numbers, volume paths. The values do not automatically appear inside the containers; they are used to build the Compose configuration. Run docker compose config to see the file with everything substituted, which is the fastest way to check what Compose actually resolved.

The env_file attribute: variables inside the container

The env_file attribute names one or more files whose contents become environment variables in the container’s process:

# compose.yaml
services:
  web:
    image: myapp
    env_file:
      - ./web.env       # these become env vars INSIDE the container

This is how your application gets DATABASE_URL, API_KEY, and the rest of its runtime configuration. It is separate from the top-level .env file, uses a path you specify (not automatic), and you can list several files, evaluated in order. This is the one you want for application secrets and settings.

environment vs env_file, and who wins

services:
  web:
    env_file:
      - ./web.env             # DEBUG=0 here
    environment:
      - DEBUG=1               # this wins

When the same variable is set in both, the inline environment attribute beats the env_file. The rough precedence, highest to lowest: docker compose run -e on the command line, then the environment attribute, then env_file, then any ENV baked into the image. A value set closer to the run wins over one set further away. Knowing this order is what turns ‘why is DEBUG still 1’ from a mystery into a two-second answer.

Practical rules that avoid the traps

  • Use .env for compose-file values - image versions, ports, paths - that you want to parameterize.
  • Use env_file for container runtime config - the variables your app reads at startup.
  • Never commit real secrets in either file. Add .env and *.env to .gitignore and inject production secrets through your platform’s secret store, not a checked-in file.
  • Run docker compose config whenever a variable is not what you expect - it shows the fully-resolved configuration and ends the guessing.

The single most common bug is expecting .env values to appear inside the container. They do not, unless you also pass them through environment or env_file. Keep the two roles separate in your head and the whole system becomes predictable.

How this fits the rest of the stack

Environment configuration is deceptively simple until precedence and file roles blur together, and then a wrong value costs an hour. A platform that surfaces environment variables and secrets explicitly removes most of that ambiguity, because you can see what the running service will read. The RunxBuild hosting calculator shows a service, its database, and bandwidth as line items, and the RunxBuild dashboard is where you set environment variables and secrets and confirm what the container actually gets.

Useful related references:

FAQ

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

The .env file is read automatically and substitutes ${VARIABLE} placeholders inside your compose.yaml itself - image tags, ports, paths. The env_file attribute names files whose contents become environment variables inside the container’s process. One configures the Compose file; the other configures the running application. Values in .env do not appear inside containers unless you also pass them via environment or env_file.

Does the .env file set environment variables inside my container?

No, not by itself. The top-level .env file only fills in ${…} placeholders in the compose.yaml during configuration. To get a variable into the container, set it under the environment attribute or list a file under env_file. Expecting .env values to appear inside the container is the single most common Docker Compose environment bug.

What wins when a variable is set in both environment and env_file?

The inline environment attribute takes precedence over env_file. The broader order from highest to lowest is: docker compose run -e on the command line, then the environment attribute, then env_file, then any ENV baked into the image. A value set closer to the actual run overrides one set further away, so command-line and inline values win.

How do I check what variables Docker Compose actually resolved?

Run docker compose config. It prints the fully-resolved compose configuration with all ${…} substitutions applied, so you can see exactly which image tags, ports, and values Compose computed from your .env file and defaults. It is the fastest way to diagnose a variable that is not what you expected without starting the containers.

Should I commit my Docker Compose env files to git?

Not ones containing real secrets. Add .env and *.env patterns to .gitignore and inject production secrets through your platform’s secret store rather than a checked-in file. It is fine to commit an example file like .env.example with placeholder values so teammates know which variables are required, as long as the real values stay out of version control.

#docker compose env file#docker#compose#config#dev-infra