Environment variables in Docker Compose are external values that parameterize the YAML file without having to edit it every time.
In the previous article, we wrote a docker-compose.yml that worked, but it had a security and flexibility problem.
We had things like:
environment:
MYSQL_ROOT_PASSWORD: password123 # ❌ Visible password!
ports:
- "8080:80" # ❌ Fixed port
If you share this project:
- Security: Everyone knows your admin password.
- Rigidity: If your colleague is already using port 8080 for something else, they have to edit your
docker-compose.ymlfile to be able to work.
To solve this, Docker Compose integrates natively with .env files.
The code (YAML) defines the structure, but the configuration (Variables) defines the environment.
What is an environment variable?
It’s a dynamic value that can affect the behavior of processes on a computer. In Docker Compose, we use these variables to replace values within the YAML file before executing it.
The syntax to use a variable in Compose is the “Shell” style: ${VARIABLE_NAME}.
This article covers variables in Docker Compose. If you want to define variables inside the Dockerfile, those belong to ARG and ENV, which we covered in the Dockerfile section.
Transforming our YAML
Let’s fix our security disaster. Instead of hardcoding the values, we put “placeholders”:
services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} # ✅ Variable
MYSQL_DATABASE: ${DB_NAME} # ✅ Variable
admin:
image: phpmyadmin/phpmyadmin
ports:
- "${HOST_PORT}:80" # ✅ Variable
depends_on:
- db
If you try to run docker compose up now, Docker will warn you (or use empty strings) because those variables are not defined. We need to give them values.
In older examples, you’ll often see version: "3.8" at the beginning of the file. In current Docker Compose, this field is kept for compatibility, but it’s no longer necessary to include it.
The .env File
Docker Compose automatically looks in the same folder for a file named .env (hidden, starts with a dot).
It’s a simple text file with a KEY=VALUE format. Create an .env file next to your docker-compose.yml:
# Database Configuration
DB_PASSWORD=SuperSecret123!
DB_NAME=my_application
# Port Configuration
HOST_PORT=8080
That’s it! You don’t need to configure anything else. When you run docker compose up, Docker reads the .env, replaces ${DB_PASSWORD} with SuperSecret123!, and starts the containers.
Security and Git
The .env file should NEVER be uploaded to the code repository (Git). It contains your actual secrets. Your production password, your API keys, etc.
So, how does my team know which variables they need to create? The standard practice is to create a template file called .env.example (or .env.template).
Steps to do it right:
Create your .env with your real passwords.
Add .env to your .gitignore file. (This prevents you from accidentally uploading it).
Create an .env.example with fake or empty values:
# .env.example
DB_PASSWORD=change_this
DB_NAME=app_db
HOST_PORT=8080
Upload the .env.example to GitHub.
When a new developer downloads your project, they will see the .example, make a copy, rename it to .env, and put their own values.
The .env file prevents you from writing secrets inside the YAML, but it’s not a safe. If someone has access to the server or the local project, they can read it. For sensitive secrets, use a secret manager or Docker Secrets when appropriate.
Secrets in Docker Compose
If you have a password, token, or private key, it’s best not to treat it as a normal variable.
For these cases, Docker Compose allows declaring secrets. The idea is simple: instead of passing the value as an environment variable, Docker mounts a file inside the container, and the application reads it from there.
For example, for a PostgreSQL database:
services:
db:
image: postgres:16
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
In this case, the actual password is in a local file:
./secrets/db_password.txt
And inside the container, it appears mounted as:
/run/secrets/db_password
The official PostgreSQL image understands the POSTGRES_PASSWORD_FILE variable, reads the password from that file, and avoids having to write it directly in the YAML.
This doesn’t automatically turn your laptop into Fort Knox. In local Docker Compose, the ./secrets/db_password.txt file still exists on your disk, and you must protect it with appropriate permissions and exclude it from Git.
But it’s much better than leaving the password written in docker-compose.yml or embedding it inside the image.
The difference between .env and env_file
There is a common confusion in Docker Compose.
- Substitution in YAML (what we just saw): Docker reads the
.envand replaces${VAR}inside thedocker-compose.ymlfile itself. It’s used to configure ports, replicas, image versions, etc. - Passing variables to the container (
env_file): Sometimes you have an application (e.g., Django or Laravel) that needs 50 environment variables to function internally. Writing them all in theenvironment:section of the YAML is very messy. You can tell Docker: “Take this entire file and inject all its variables INSIDE the container”.
services:
my-app:
image: my-image
env_file:
- ./config/app.env # Load everything from here inside the container
