Download the file for your release from https://airflow.apache.org/docs/apache-airflow/3.3.2/docker-compose.yaml 129 . A YAML anchor, x-airflow-common, holds what all Airflow 129 containers share: the image, AIRFLOW__SECTION__KEY variables that override airflow.cfg, mounts for dags, logs, config and plugins, and your user ID. A one-shot airflow-init migrates the database and creates the airflow/airflow admin first.
BookNest's copy (demos/ch07/airflow/docker-compose.yaml) switches to the LocalExecutor, turns examples off, publishes port 32880, joins a network shared with l2-pg, and adds a connection and mounts:
image: ${AIRFLOW_IMAGE_NAME:-l2/airflow-booknest:3.3.2} # official image + dbt (Dockerfile)
environment:
AIRFLOW__CORE__EXECUTOR: LocalExecutor
AIRFLOW_CONN_BOOKNEST_PG: 'postgresql://postgres:booknest@l2-pg:5432/booknest'
PYTHONPATH: /opt/airflow/include # booknest_pipeline.py
volumes:
- ../dbt:/opt/airflow/dbt # Chapter 4's dbt project
- ../source:/opt/airflow/data/source:ro # the order service's exportThe pipeline needs dbt 37,942 in the image. Rather than _PIP_ADDITIONAL_REQUIREMENTS, which reinstalls at every start, extend the image, with dbt in its own virtual environment so its pins never fight Airflow's (Orchestrating dbt with Cosmos):
FROM apache/airflow:3.3.2
RUN python -m venv /opt/airflow/dbt_venv \
&& /opt/airflow/dbt_venv/bin/pip install --no-cache-dir dbt-core==1.12.5 dbt-postgres==1.11.0The image runs as user airflow, so a first try at /opt/dbt-venv failed with Permission denied. The result, l2/airflow-booknest:3.3.2, reports dbt Core 1.12.5 with the postgres 1.11.0 plugin on Python 3.13.