Configuration is where many Compose projects become confusing. Variables can come from the shell, from a .env file, from env_file entries or from the environment block itself. Understanding which source wins, and why, prevents a great deal of puzzlement.
Two distinct mechanisms
It helps to separate two ideas that are often conflated:
- Interpolation substitutes values into the Compose file itself before anything runs, using syntax such as
${IMAGE_TAG}. - Container environment sets variables inside the running container, using
environmentorenv_file.
The project-level .env file feeds interpolation. It does not automatically inject every variable into every container — a frequent misunderstanding.
IMAGE_TAG=2.4.1
WEB_PORT=8080services:
web:
image: example/web:${IMAGE_TAG:-latest}
ports:
- "${WEB_PORT}:80"
env_file:
- ./config/web.env
environment:
LOG_LEVEL: infoOrder of precedence
When the same variable is set in several places for a container, Compose applies a well-defined order. From highest to lowest priority:
- Values set with
docker compose run -eon the command line. - The
environmentattribute in the Compose file (including interpolated values). - Files listed under
env_file. ENVinstructions baked into the image.
When in doubt, docker compose config shows the final resolved values, and docker compose exec web env shows what the container actually sees.
Interpolation helpers
${VAR:-default}uses a default when the variable is unset or empty.${VAR:?error message}stops Compose with a clear error when a required value is missing.$$escapes a literal dollar sign.
Handling secrets responsibly
Environment variables are convenient but not confidential: they can appear in docker inspect output, crash reports and child processes. For passwords, tokens and keys, prefer Compose secrets, which mount values as files under /run/secrets/.
Per-environment configuration
Use --env-file to choose between configurations, for example docker compose --env-file .env.staging up. Combined with override files, this keeps a single source of truth for the architecture while allowing values to vary by environment.
A disciplined approach to configuration makes Compose projects safer, easier to audit and far simpler to move between machines.
Distinguish interpolation from application configuration
Compose interpolation resolves values while constructing the application model. Container environment variables are delivered later to the process inside a service. Confusing these stages makes apparently plausible configuration behave unexpectedly, especially when shell variables and env files both define the same name.
A secret file reduces routine disclosure through environment inspection, but its source remains sensitive. Restrict file permissions, exclude credential material from version control and rotate compromised values.
Trace a value from your shell to the application
When a docker compose environment variable appears incorrect, trace its entire path. First decide whether the value is used to interpolate the Compose model or passed into a container for the application to read. These are separate steps. A value can exist in your shell or project environment file and still be absent from the container if the service never declares it. Conversely, an image may define a default that the application receives even when your project file does not mention that variable at all.
Use a small harmless setting to test the path before investigating a real credential. For example, a development feature flag or application label can show whether your service environment is being recreated as expected. Inspect the resolved model locally, then verify what the application actually receives. Remember that changing a file on disk does not rewrite the environment of an existing process. Recreate the affected container through the appropriate Compose startup operation.
Make missing configuration fail clearly
Silent defaults can be helpful for local ports but dangerous for required settings. Use a required interpolation expression where the project cannot work without a value, and provide an error message that identifies the missing setting. For optional values, choose a default that is safe for a new developer. Keep the example environment file consistent with those expressions. If the sample claims a field is optional while the YAML requires it, every new contributor must rediscover the mismatch.
Watch for the difference between an unset variable and an empty one when selecting interpolation syntax. An empty password or hostname can travel surprisingly far before an application reports a useful error. Validate important values in the application as well as in the Compose model. The application can explain format requirements that a simple interpolation check cannot express. Avoid displaying the full value in validation errors when it might be sensitive.
Keep secret delivery compatible with the image
Compose secrets provide files to services that explicitly request them. Your application or image must know how to read those files. Some database images support variables such as a password file setting, while a custom program may need code that reads a path directly. Merely naming an environment variable with a _FILE suffix does not make every application understand it. Check the image documentation and verify the resulting path inside the service.
For local Compose usage, a file-backed secret still exists on the host. Keep it out of source control, restrict access and decide how developers obtain their own copy. Do not describe it as an automatically encrypted vault. If your team needs central rotation, audit trails or more advanced access control, choose a secret management workflow appropriate to that environment. Compose can participate in that workflow by delivering the required value, but it does not remove the need to manage the original credential and its lifetime.
Separate environments without hiding differences
Development, testing and deployment often need different configuration, but the service relationships should remain understandable. Keep shared structure in a base file and use explicit overrides where they improve clarity. Always inspect the merged model with the same file order used for startup. A development bind mount or published debug port should not accidentally become part of a server configuration. Give each environment a short list of required values and explain which are secrets. This is easier to review than a large directory of environment files with unclear ownership.
Be careful when sharing diagnostic material. A resolved Compose model, process environment, shell history or application traceback can expose values that never appeared in the committed YAML. Redact sensitive output before posting it in an issue or build log. If a real credential was exposed, removing the text does not invalidate copies that already exist; rotate the credential through the relevant service. For routine troubleshooting, substitute a harmless test value and reproduce the same delivery path. That usually reveals the precedence problem without bringing real account access into the investigation.
Rotate and verify credentials deliberately
Changing a password file is only one part of credential rotation. The backing service must accept the new credential, and every dependent application must receive and use the corresponding value. Some database image variables apply only during initialisation of an empty data directory. Editing those variables later may therefore leave the actual database password unchanged. Check the image's lifecycle behaviour and perform the documented database operation when required. Then restart or recreate consumers as appropriate and test a real connection before considering the rotation finished.
Keep a clear distinction between disposable test credentials and shared environment credentials. A local tutorial can create a fresh database to verify initialisation, whereas an existing database requires a procedure that preserves records. Document the sequence and test it in a safe environment before applying it to valuable data. Where possible, remove old access only after confirming that every intended consumer uses the replacement. This reduces avoidable downtime and makes failures easier to explain because each step has an observable result.
Frequently asked questions
Does a .env file automatically populate container variables?
Not by itself. A project environment file commonly supplies interpolation values to Compose. To deliver variables to an application, declare the appropriate service environment or env_file settings. Check the resolved model and the application's configuration path separately.
Are Compose secrets encrypted on my laptop?
A local file-backed secret should not be treated as an encrypted vault. The source file still needs protection on the host. Secret mounts limit which services receive the file, while host access and credential distribution remain your responsibility.
Why did changing a database password variable have no effect?
Many database images read initialisation variables only when creating a new data directory. An existing database can retain its original credentials. Use the database's supported password change procedure and update dependent services in a coordinated way.
Should I commit an example environment file?
Yes, if it contains harmless sample values and explains required fields. Keep actual credentials out of that file and out of repository history. The example should help someone configure the project without granting access to any real account.
Conclusion
Reliable docker compose configuration starts by separating interpolation, container environment values and secret files. Trace values through each layer, make required settings explicit and confirm that the chosen image supports your delivery method. Protect the original credentials and test rotation against the actual service lifecycle. With these checks, environment configuration becomes easier to reproduce while sensitive values stay out of everyday source files and diagnostic output.



