
Docker Compose environment variables can supply values while Compose reads a configuration, or become settings inside a container. A value in a .env file does not automatically reach the application. You must connect those two stages through the service configuration.
This guide separates .env, --env-file, env_file, and environment, then uses a small example to make the final values inspectable. It covers precedence, missing variables, dollar signs, and changes that require recreating a container. All example values are disposable configuration, not credentials.
Docker's environment variable overview distinguishes interpolation from container configuration. Interpolation replaces expressions such as ${ENV_DEMO_TAG} while Compose builds its application model. The resulting image name, port, or environment value is then part of that model.
A file can be used for both purposes if you explicitly configure that, but the roles remain different. Docker's container environment guide shows the service attributes. Keep the intended inputs with your project instructions; our development environment guide covers that wider setup.
Create a new test directory and save these three files there. Use the Docker Compose CLI, invoked as docker compose. The example describes a Linux container with no ports, mounted host files, or network access. Compose can validate it without starting the Docker engine.
First, save the interpolation inputs as .env. Make sure your editor does not silently add a .txt extension:
ENV_DEMO_TAG=3.22
ENV_DEMO_MODE=development
ENV_DEMO_LABEL=compose-lab
ENV_DEMO_UNUSED=not-passed
Next, save container.env. Its APP_MODE value deliberately conflicts with the service configuration so you can see which setting wins:
APP_MODE=from-service-file
LOG_LEVEL=info
Finally, save compose.yaml:
services:
inspect:
image: "alpine:${ENV_DEMO_TAG:-3.22}"
network_mode: none
env_file:
- ./container.env
environment:
APP_MODE: "${ENV_DEMO_MODE:-development}"
APP_LABEL: "${ENV_DEMO_LABEL:?Set ENV_DEMO_LABEL in the input file}"
command:
- sh
- -c
- 'printf "mode=%s label=%s log=%s\n" "$$APP_MODE" "$$APP_LABEL" "$$LOG_LEVEL"'
The image tag comes from ENV_DEMO_TAG. APP_MODE and APP_LABEL are explicitly passed to the container; LOG_LEVEL comes from container.env. ENV_DEMO_UNUSED is an interpolation input with no consumer, so it is absent from the service environment.
The command attribute invokes sh explicitly. Compose does not automatically give every command a shell. The doubled dollar signs preserve the variable references for expansion by that shell inside the container.
Run these commands from the test directory. Execute the second only if the first succeeds:
docker compose -p env-lab-20261030 --env-file .env -f compose.yaml config --quiet
docker compose -p env-lab-20261030 --env-file .env -f compose.yaml config --format json
The config command validates and resolves the model. Look for image alpine:3.22 and the service environment APP_MODE=development, APP_LABEL=compose-lab, and LOG_LEVEL=info. If your shell already defines any ENV_DEMO_ variables, the result can differ. Use an isolated test shell or intentionally remove those test overrides before comparing.
We checked this model and its precedence cases with Compose v5.1.4. This verifies configuration resolution, not container execution. Do not paste production output from config or config --environment into a ticket: it can reveal resolved credentials. For real deployments, inspect only the necessary values and redact sensitive output.
To check the expected behavior yourself, confirm that the selected Docker context is a nonproduction host with a reachable Linux-container engine. Stop if either check fails; otherwise run the disposable service:
docker context show
docker version
docker compose -p env-lab-20261030 --env-file .env -f compose.yaml run --rm --no-deps inspect
The image may be pulled if it is missing. The one-off run removes its container afterward. With the base files and no shell overrides, the expected output is:
mode=development label=compose-lab log=info
Alpine 3.22 is an example branch, not a claim about the latest release. Check its support schedule and use a maintained, approved image for your own application.
For interpolation, your shell can override file inputs. An explicit --env-file makes the selected input file clear; when you provide several, later files can replace earlier values. Docker documents the default .env lookup and project-directory rules in its interpolation guide. Use explicit paths in automation instead of depending on the caller's working directory.
For the container, a one-off run -e override takes priority. Service environment entries override env_file entries, including empty or unresolved entries; image ENV values provide defaults when no higher-priority setting replaces them. Values copied from your shell or interpolation file must still be wired into the service. See Docker's precedence reference for the combinations.
In this example, environment sets APP_MODE from ENV_DEMO_MODE and overrides the conflicting value in container.env. To change it for one new test container only:
docker compose -p env-lab-20261030 --env-file .env -f compose.yaml run --rm --no-deps -e APP_MODE=one-off inspect
The expected mode is one-off. That command does not rewrite the files or change an existing container. Avoid treating a shell variable, an input file, and a service environment entry as interchangeable places to set configuration.
The interpolation syntax reference distinguishes unset variables from empty strings:
The required APP_LABEL expression makes a missing label fail early. Without a default or required-value expression, an unresolved substitution can become an empty string after a warning. That can produce a valid-looking configuration with the wrong behavior.
Use $$ when a dollar sign must survive Compose interpolation. It does not by itself cause a container to expand the variable; a shell or application must do that. Quote boolean-looking environment values such as "false" in YAML so they are treated as strings. For values containing literal dollar signs or quotes in a service env_file, Compose 2.30.0 and later also supports format: raw; review the env_file reference before changing parsing rules.
Save this separate file as staging.env:
ENV_DEMO_MODE=staging
ENV_DEMO_LABEL=staging-lab
Resolve the model with the base input file followed by the staging overrides:
docker compose -p env-lab-20261030 --env-file .env --env-file staging.env -f compose.yaml config --format json
With no shell overrides, the resulting service uses APP_MODE=staging and APP_LABEL=staging-lab. The image tag still comes from .env. This selects configuration; it does not create an isolated staging account, network, database, or billing boundary. Docker's configuration recommendations support separate inputs, but you must define what each environment can access.
A .env filename does not encrypt its contents. Keep real credentials out of source control and use dummy values in shared examples. For supported Linux-container workflows, Compose secrets can grant a service access to a mounted secret file. The application must support reading it, and the host source still needs protection. This is not automatic rotation or protection from a host administrator.
Build-time ARG and runtime environment values also have different roles. Docker's build variable documentation explains why an ARG does not automatically become a container variable and why neither ARG nor ENV is an appropriate build-secret channel. Our cloud security guide puts credential handling in the wider access-control process.
Record the Compose version, the selected filenames, and the expected nonsecret settings alongside the application. A teammate should be able to resolve the same model without relying on undocumented shell state.
Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.