Docker

Docker Compose .env File Priority Ignoring System Environment Variables

· by DebuggedIt

Quick answer

When Docker Compose seems to ignore a system environment variable you've explicitly set, in favor of a value from a .env file, the behavior is usually correct...

When Docker Compose seems to ignore a system environment variable you've explicitly set, in favor of a value from a .env file, the behavior is usually correct according to Compose's documented precedence rules — the fix is understanding and working with that precedence order, rather than assuming Compose has a bug.

The Problem

You explicitly export an environment variable in your shell, expecting it to override whatever's in the project's .env file, but Compose seems to use the .env file's value instead:

export API_URL=http://override-value.com
docker compose up
# container still shows the .env file's original value, not your exported override

Why It Happens

Docker Compose has a specific, documented precedence order for resolving variable values, and the source of confusion is usually not understanding which mechanism applies to which specific use case:

  • Variables used for interpolation directly within the docker-compose.yml file itself (like ${API_URL} appearing in the YAML) follow one precedence order: shell environment variables actually do take priority over the .env file for this specific purpose — a correctly exported shell variable should override the .env file's value when used this way
  • Variables passed into the container's actual runtime environment via the environment: key in the compose file are a separate matter — if the compose file explicitly writes environment: - API_URL=${API_URL}, and this interpolation resolves incorrectly, that's usually because of the first bullet point's precedence issue, not a separate rule
  • Variables referenced via env_file: (pointing to a file to load directly into the container's environment) are handled completely differently from .env-based interpolation in the compose file — these are two genuinely distinct mechanisms that happen to have similarly-named files, which is a common source of confusion
  • A shell variable that was exported in a different terminal session, or before a shell profile reload, might simply not actually be present in the specific shell session running docker compose up, despite appearing "set" based on memory of having exported it at some earlier point

The Fix

First, confirm the variable is genuinely exported and visible in the exact shell session running the Compose command:

echo $API_URL
env | grep API_URL

If this doesn't show your expected value, the variable isn't actually present in this specific shell session — re-export it, or check whether you're running Compose from a different terminal/session than the one where you originally set it.

For variable interpolation directly within docker-compose.yml (using ${VARIABLE} syntax in the file itself), confirm the actual precedence Compose is using by checking the fully resolved configuration:

docker compose config

This shows exactly what value Compose resolved for every interpolated variable, which immediately clarifies whether your shell export is actually taking effect as expected, or whether something else is overriding it.

If you specifically want the container's runtime environment to reflect a value regardless of shell export state, set it explicitly and directly in the compose file's environment: section instead of relying on interpolation from an external variable at all, which removes the ambiguity entirely:

services:
  app:
    environment:
      API_URL: http://explicit-value.com  # hardcoded, no interpolation ambiguity

For explicitly overriding values per-environment without relying on shell export precedence at all, use Compose's override file mechanism (a separate docker-compose.override.yml, or an explicitly specified additional compose file), which provides clearer, more predictable, version-controllable precedence than depending on ambient shell state:

docker compose -f docker-compose.yml -f docker-compose.override.yml up

If you need different values across different environments (development, staging, production) reliably, consider whether relying on shell-exported variables at all is the right approach, versus maintaining explicit, separate .env files per environment and specifying which one to use explicitly:

docker compose --env-file .env.production up

This explicit approach avoids ambient shell state entirely, making the actual source of every variable value clear and reproducible regardless of what might or might not currently be exported in whatever terminal session happens to run the command.

Still Not Working?

If you've confirmed the shell variable is genuinely exported and visible, and you're still seeing unexpected precedence behavior, check your specific Docker Compose version, since precedence rules and behavior around .env file handling have had some changes and clarifications across different Compose versions (particularly between the older Python-based docker-compose and the newer Go-based docker compose plugin) — confirming which specific version and implementation you're running, and checking that version's specific documented precedence behavior, resolves ambiguity if the general precedence rules described above don't seem to match what you're actually observing.