Docker Compose
Docker Compose simplifies managing multi-container applications by allowing you to define and run them together using a single YAML configuration file.
Below is an example of a docker compose file to run a flask APP, it is used in the project Rest Flask Template.
networks:
local_flask_network:
driver: bridge
x-app-service: &app-service
image: flask-app:latest
environment:
FLASK_ENV: ${FLASK_ENV}
EMAIL_SECRET_KEY: ${EMAIL_SECRET_KEY}
TWO_FACTOR_AUTH_SECRET_KEY: ${TWO_FACTOR_AUTH_SECRET_KEY}
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
MAIL_SERVER: ${MAIL_SERVER}
MAIL_PORT: ${MAIL_PORT}
MAIL_USERNAME: ${MAIL_USERNAME}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_DEFAULT_SENDER: ${MAIL_DEFAULT_SENDER}
DATABASE_URI: "postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@database:5432/${POSTGRES_DB}"
CELERY_BROKER_URL: "amqp://${RABBITMQ_USER}:${RABBITMQ_PASS}@rabbit:5672//"
services:
flask-api:
<<: *app-service
ports:
- "5000:5000"
networks:
- local_flask_network
depends_on:
- database
- database-migration
- celery
celery:
<<: *app-service
command: >
sh -c "
mkdir -p /home/appuser/celery_data &
celery -A rest_flask_template.run_celery.celery beat -s /home/appuser/celery_data/celerybeat-schedule --loglevel=info &
celery -A rest_flask_template.run_celery.celery worker --loglevel=info &
tail -f /dev/null;
"
networks:
- local_flask_network
depends_on:
- database
- rabbit
volumes:
- celery_data:/home/appuser/celery_data
database-migration:
<<: *app-service
command: >
sh -c "
python -m rest_flask_template.manage db_upgrade &
python -m rest_flask_template.manage db_check;
"
networks:
- local_flask_network
depends_on:
- database
# Default port number used by Postgres: 5432
database:
image: "postgres:17-bookworm"
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_DB: ${POSTGRES_DB}
networks:
- local_flask_network
volumes:
- database_data:/var/lib/postgresql/data
# Default port number used by RabbitMQ: 5672
rabbit:
image: "rabbitmq:4.0"
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS}
networks:
- local_flask_network
# Docker manage these volume data
volumes:
database_data:
celery_data:
There are multiple interesting features used:
The services are all part of a same
bridge
networklocal_flask_network
This allows them to easily communicate between each other (the name of the services can be directly used in the URLs and there is no need to map ports).
This also prevent port conflicts with ports already used by the HOST machine.
networks
can be configured externally by adding the option external: true, it means you’ll have to create them manually before running thedocker compose up
command.A commonly used network mode used is the
network_mode: "host"
(in this case, ports don’t need to be mapped with HOST because the services shared the same network as the HOST machine)In our case, because we use the
bridge
network, we map the port5000
of serviceflask-api
with the HOST port5000
(- "<host_port_number>:<container_port_number>"
)
This docker compose uses environment variables to avoid setting values directly in the docker compose file. Careful as if you want to use the automatically loaded
.env
next to the docker compose file (or using the command argument--env-file env_vars.env
), the environment variables set in the HOST machine take precedence over the variables set in the env file!! docker compose will take the values in the .env file only if the variable is unset in the HOST.We use the Anchors and Aliases feature of yaml (
&
,*
:code:` <<:) combined with the `extension (x-
) feature of docker compose to avoid duplicating code.expose
is only informative, so we are not using it, the services don’t need it to be able to communicate between each other.In this example, we let docker create the volumes for us, but we can also add external: true in the docker compose and create them manually before running the
docker compose up
command
Using Multiple docker compose files
Docker compose provides multiple option to customize a Compose application for different environments or workflows: https://docs.docker.com/compose/how-tos/multiple-compose-files/
We can extend or include a docker file directly in the docker compose file or we can also merge multiple docker compose file with the -f
argument.
Below is an example showing the merge method to add a service only when necessary (only in dev
environement in this case).
Considering the previous docker-compose.yml
, we can create another docker compose file docker-compose-dev.yml
as below:
services:
smtp-mail-server:
image: python:3.12-slim-bookworm
environment:
PYTHONUNBUFFERED: 1
command: >
sh -c "
pip install aiosmtpd;
python -m aiosmtpd -n --listen 0.0.0.0:${MAIL_PORT} --debug;
"
networks:
- local_flask_network
We can now run our app with the command:
docker compose -f docker-compose.yml -f docker-compose-dev.yml up
Note
The rules used to merge the files are described here: https://docs.docker.com/reference/compose-file/merge/
Sources: