When Docker Compose environment variables do not take effect, separate two operations: .env and --env-file supply variables for Compose to resolve configuration, while the service’s environment and env_file pass variables into containers. After changing configuration, restart does not update an existing container’s environment. Check the resolved configuration, then apply changes with up -d.
When following a deployment tutorial, you may find that a value is clearly present in .env but missing inside the container, or that the application still uses an old address after you edit the value and restart.
Trace the problem through “input files → Compose configuration → container environment → application reads.”
This article covers Compose V2 and Linux containers, including Docker Desktop in Linux container mode. Documentation was checked on November 4, 2026. The commands are based on official documentation; Docker containers were not run in the environment used to prepare this article.
If you maintain self-hosted services such as Joplin Server or Syncthing, use the isolated example below to understand overrides before inspecting your application configuration.
Where the four configuration methods send variables
| Method | Where it is used | Main purpose |
|---|---|---|
.env |
Conventional input file for a Compose project | Supplies values for interpolation such as ${VAR} |
--env-file ./deploy.env |
Compose command-line option | Explicitly selects an interpolation input file |
env_file: ./app.env |
Configuration of an individual service | Injects variables from the file into that service’s container |
environment: |
Configuration of an individual service | Sets, references, or overrides container variables directly |
One file can serve both purposes, but you must declare its use explicitly.
Simply placing .env in the directory does not automatically pass every variable in it to every container.
For example, if .env contains APP_MODE=production but the service declares no environment variables, the container will not normally receive APP_MODE as a result.
Adding environment: APP_MODE: "${APP_MODE}" to the service connects that chain.
See the official guide to setting container environment variables.
Observe overrides in a project without application data
First check the CLI and the Docker host it actually connects to.
|
|
Create a separate directory named compose-env-lab and add the following three files.
Do not append the experimental service directly to a production project.
|
|
compose.yaml:
|
|
.env:
|
|
app.env:
|
|
This example has no published ports, volumes, or privileged permissions. The image must be pulled from a registry your Docker host can reach. Keep the files unchanged until you finish the initial experiment so you can compare results.
Run these commands in that directory first:
|
|
A successful config -q means only that configuration validation passed.
config --environment displays the variables used for interpolation; plain config shows the merged and resolved service model.
Neither proves that a container has been created, let alone that the application has read the values.
These outputs may contain passwords. View them only in a trusted terminal and redact sensitive values before sharing. See docker compose config for the options.
Read only the variables you need after startup
|
|
If the terminal that starts Compose has no variables with matching names overriding these values, the expected output is:
|
|
APP_MODE comes from interpolation using .env, overriding the matching entry in app.env.
APP_COLOR is injected through the service’s env_file.
APP_LABEL is required: a missing or empty value causes an error during configuration resolution.
Now query the variable that was not declared for injection into the container:
|
|
Expect no output and a nonzero exit status.
This is the intended result: .env is not a file that automatically injects all its variables into the container.
Why your terminal overrides values in .env
When Compose resolves ${APP_MODE}, a variable with the same name in the Shell that launches it takes precedence.
If the file says production but the resolved value is staging, check the terminal environment first.
In Bash, inspect and clear the experimental variable like this:
|
|
The PowerShell equivalent is:
|
|
Before removing a variable from your current terminal, confirm that another task does not need it. IDE tasks, CI jobs, and system services may also inherit environments different from a terminal opened manually.
To reduce ambiguity caused by the working directory, explicitly select the configuration and input files in deployment commands:
|
|
Replace myapp with the existing project name; do not casually rename a production deployment.
The project name contributes to container, network, and default volume names. Changing it may create a different set of resources.
A relative path passed to --env-file is based on the working directory where you run the command; ordinary relative paths in a service’s env_file are based on the Compose file’s location.
Multiple Compose files, --project-directory, and COMPOSE_FILE can complicate these relationships. Start troubleshooting with explicit paths to narrow the scope.
When you specify multiple interpolation input files, later files can override matching entries in earlier files:
|
|
Variables already present in the Shell may still take precedence.
Check default .env discovery behavior against your CLI version; see Compose interpolation and file-loading rules.
Check container precedence separately
After identifying the interpolation inputs, inspect how the service ultimately sets container variables. For common cases with explicit assignments, use these override relationships:
| Scenario | Effect on the result |
|---|---|
docker compose run -e APP_MODE=debug |
Overrides the variable in this temporary container |
Setting APP_MODE in a service’s environment |
Overrides the matching value in the service’s env_file |
Listing multiple env_file entries for a service |
Later files override matching values from earlier files |
ENV in the image’s Dockerfile |
Remains only if no matching runtime setting overrides it |
An environment value may itself come from Shell or .env interpolation.
Keep “where the value was resolved from” separate from “which service field passes it into the container.”
Pass-through declarations containing only a variable name also have removal semantics when unresolved. Prefer explicit assignments while troubleshooting.
Test a temporary override in the experiment:
|
|
Expect debug.
This creates a one-off container and does not modify the probe container previously started with up -d.
Do not treat run -e as a general option for up.
See the official environment variable precedence guide for the full boundaries and pass-through examples.
Why restart keeps the old value after a file change
A container’s environment variables are determined when the container is created.
docker compose restart restarts existing containers without recreating their environments from modified configuration.
Change APP_MODE in the experimental .env to staging, then compare:
|
|
If the container originally used production, it should still have that value after the restart.
The configuration files and the running container are now in different states.
After confirming that the resolved configuration is correct, apply the change:
|
|
Compose normally detects configuration changes and recreates affected services. If you actually need to force recreation, use:
|
|
Recreation interrupts the service, so schedule it within the appropriate maintenance window. Whether dependencies such as databases also need action depends on the architecture. Do not routinely recreate the whole project.
Mounted volumes continue to be used according to the configuration, but temporary changes in a container’s writable layer may be lost. Confirm that application data is actually stored in persistent locations before proceeding. See the official descriptions of restart and up.
Common traps with dollar signs, empty values, and booleans
Expand a variable inside the container
Compose normally processes $VAR in a Compose file first.
To let the container’s Shell read the variable instead, use $$ and explicitly invoke a Shell:
|
|
This is a separate, short-lived service example that prints the value and exits.
$$ prevents expansion by Compose on the host; sh -c performs expansion inside the container.
Putting a variable in an argument array that does not invoke a Shell does not automatically enable Shell expansion.
See Compose Interpolation for the syntax.
An empty string is different from an unset variable
${VAR:-default} uses the default when the variable is missing or empty.
${VAR-default} uses the default only when the variable is missing.
${VAR:?message} suits inputs that must be nonempty, preventing empty addresses or paths from reaching runtime.
Do not temporarily mask production settings with empty strings without documenting why.
An explicit empty value in a service can override a valid env_file setting.
Quote boolean-like values as strings in YAML:
|
|
This only ensures that a string is passed.
Whether the application interprets "false" as disabled or treats any nonempty string as true depends on its own configuration parser.
Why the application stays unchanged even when configuration and container values are correct
First check that the application supports the variable name and whether higher-priority sources, such as configuration files or command-line arguments, override it.
APP_URL, BASE_URL, and PUBLIC_URL are not a universal protocol and cannot be freely substituted across projects.
Then check when the value is read: some frontend settings are embedded in static files during the image build. Setting runtime environment variables does not rewrite existing frontend artifacts.
For projects requiring build arguments, configure build.args or the dedicated startup script according to the project’s documentation.
Finally, verify that you are inspecting the correct service and instance. A project’s worker, web, and scheduler may be three separate containers. Updating one does not change the other two.
| Observation | Next step |
|---|---|
config still shows the old value |
Check input file paths, Shell overrides, and multi-file merging |
config is correct but exec printenv shows the old value |
Check the project name, Docker context, and container recreation |
| The container value is correct but application behavior is unchanged | Check variable names, application precedence, read timing, and the instance being accessed |
exec fails because the container is not running |
Start with ps -a and the service logs |
In the experiment, run:
|
|
Application images may lack printenv or a Shell.
Use the image’s diagnostic commands, or have an administrator inspect and redact container configuration. Avoid installing extra tools in a production image just for troubleshooting.
Passwords, rollback, and experiment cleanup
Do not put real passwords in an example repository or paste complete config, inspect, or full env output.
.env is a plain text file and provides no encryption.
You can commit a credential-free .env.example; restrict access to real deployment files as required by the project and exclude them from version control.
If the application can read credentials from files, consider Compose secrets.
They expose an authorized secret as a file inside the container, commonly at /run/secrets/<name>.
_FILE variables are a convention adopted by some images, not an automatically supported feature in every application.
Local source files still need protection; see the official Compose secrets guide.
Before changing production configuration, save the previous configuration and environment inputs, and record the project name, image version, and command arguments.
To roll back, restore those files, check config -q with the same arguments, run up -d, and verify application behavior.
Configuration rollback does not replace a database backup or guarantee that completed application data migrations can be reversed.
When finished, run this in the compose-env-lab directory:
|
|
This example declares no volumes, so you do not need -v.
Do not turn the experimental command into a global cleanup operation. For storage maintenance, see the Docker disk space cleanup guide.
At the end of troubleshooting, confirm separately that the resolved configuration matches expectations, the running container has the new values, and application behavior has changed.
These are three distinct layers. Checking each one prevents repeated .env edits that address the wrong cause.