You can pass environment variables to Docker Compose services, but the way it works is a bit more subtle than just dumping them into a .env file and expecting magic.
Let’s see it in action. Imagine you have a simple docker-compose.yml file:
version: '3.8'
services:
app:
image: alpine:latest
command: sh -c "echo 'My variable is: $MY_VAR'; echo 'Another variable is: $ANOTHER_VAR'"
environment:
- MY_VAR=default_value
And a .env file in the same directory:
MY_VAR=from_env_file
ANOTHER_VAR=env_file_only
Now, if you run docker compose up, you’ll see:
[+] Running 1/1
⠿ Container myproject-app-1 Created 0.1s
⠿ Container myproject-app-1 Started 0.2s
My variable is: from_env_file
Another variable is: env_file_only
Notice how MY_VAR from the .env file overrode the value defined in the environment section of the docker-compose.yml. And ANOTHER_VAR was picked up from the .env file because it wasn’t defined in the compose file itself. This is the core behavior: .env file variables are injected before the compose file is parsed, and they can override values defined directly in the compose file.
This system is designed to separate configuration from your service definitions. You define your services with their dependencies and basic settings in docker-compose.yml, and then you manage environment-specific settings (like database passwords, API keys, or feature flags) in .env files. This makes it easy to deploy the same docker-compose.yml to development, staging, and production environments by simply swapping out the .env file.
The environment section in docker-compose.yml can take variables in a few ways:
-
Key-value pairs:
environment: MY_VAR: "some_value" ANOTHER_VAR: "another_value" -
List of strings:
environment: - MY_VAR=some_value - ANOTHER_VAR=another_value -
References to host environment variables:
environment: - MY_VAR - ANOTHER_VARIf you list a variable name without a value, Docker Compose will look for it in the host’s environment. If it’s not found on the host, it won’t be set in the container.
-
References to
.envfile variables (implicitly): As shown in the example, variables in the.envfile are automatically available to be used within thedocker-compose.ymlitself. You can reference them like this:environment: MY_VAR: ${MY_VAR_FROM_ENV}This would look for
MY_VAR_FROM_ENVin your.envfile.
The .env file is a special file named .env in the same directory as your docker-compose.yml file (or in a parent directory, up to the project root). Docker Compose automatically loads variables from this file. If you have multiple .env files (e.g., .env.development, .env.production), you can specify which one to use with the -f flag: docker compose -f docker-compose.yml -f .env.production up. The order matters: later files override earlier ones.
Crucially, the .env file is parsed before the docker-compose.yml file. This means that values defined in the .env file can be used to override values defined directly in the environment section of your docker-compose.yml. This is a common point of confusion. If you define MY_VAR=default_value in docker-compose.yml and MY_VAR=override_value in .env, the .env value wins.
What most people don’t realize is that you can also define environment variables that are only used by Docker Compose itself for interpolation, not necessarily passed into the container. For instance, you could have a variable in your .env file that’s only used to construct a service name or a volume path within the docker-compose.yml, and never intended to be seen by the application running inside the container. The interpolation happens before the container is even started.
The next thing you’ll likely run into is managing secrets, which are a more secure way to handle sensitive environment variables.