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 `_. .. code-block:: yaml 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 :code:`bridge` network :code:`local_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. - :code:`networks` can be configured externally by adding the option `external: true`, it means you'll have to create them manually before running the :code:`docker compose up` command. - A commonly used network mode used is the :code:`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 :code:`bridge` network, we map the port :code:`5000` of service :code:`flask-api` with the HOST port :code:`5000` (:code:`- ":"`) - 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 :code:`.env` next to the docker compose file (or using the command argument :code:`--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:`&`, :code:`*` :code:` <<:`) combined with the `extension `_ (:code:`x-`) feature of docker compose to avoid duplicating code. - :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 :code:`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 :code:`-f` argument. Below is an example showing the **merge** method to add a service only when necessary (only in :code:`dev` environement in this case). Considering the previous :code:`docker-compose.yml`, we can create another docker compose file :code:`docker-compose-dev.yml` as below: .. code-block:: yaml 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: .. code-block:: bash 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**: - https://docs.docker.com/compose/how-tos/multiple-compose-files/ - https://docs.docker.com/compose/how-tos/environment-variables/ - https://docs.docker.com/reference/compose-file/extension/