The two values tell the story
Use --env-file for values that must substitute into compose.yaml. Use service-level env_file for variables that belong in the service environment. A service file does not automatically supply every ${…} expression in the Compose model.
A file that is loaded, but not where you expect
# app.env
CC_IMAGE_TAG=9.9
CC_APP_MODE=debugservices:
probe:
image: busybox:${CC_IMAGE_TAG:-1.37}
env_file: app.env
environment:
CC_PASSED: ${CC_APP_MODE:-fallback}Run this in a new directory with no .env and no shell variables named CC_IMAGE_TAG or CC_APP_MODE:
docker compose config --format jsonThese are the fields our real model check returned:
image: busybox:1.37
environment:
CC_IMAGE_TAG: "9.9"
CC_APP_MODE: debug
CC_PASSED: fallbackThe evidence is in the combination: the app variables are present, so app.env was read. Yet the image uses 1.37 and CC_PASSED uses fallback. Neither expression took its input from the service’s environment.
Give interpolation a deliberate source
Put model inputs in a separate file, then select it explicitly. In this example the image tag is an illustration, not a claim that every chosen tag exists.
# model.env
CC_IMAGE_TAG=1.37
CC_APP_MODE=debugdocker compose --env-file ./model.env configThe --env-file flag belongs before config. It changes the interpolation input for this command. Keep env_file: app.env only if those values also belong in the service.
A shell export can still override an interpolated value. If the output differs from your selected file, inspect the specific variable in your current shell or use the isolated precedence experiment linked below. Do not print your whole environment into a public issue.
Do not fix the wrong file
- Changing
app.envcan alter a service variable while leaving an image expression unchanged. - Changing the interpolation input can change the image while leaving a literal service variable unchanged.
- A correct resolved model is a prerequisite, not proof that an existing container was recreated with it.
Restore the original files to undo this scratch-directory experiment. No container was started in our test. Our proof covers Compose 5.3.0 and these explicit assignments; it does not test Swarm deployment or an application’s own configuration precedence.
Sources & evidence
Documentation checked 12 Sep 2026. Our fixture evidence: