Appendix A: Environment Setup
Everything in this book runs on a laptop. No cloud account, no credit card, and no managed service is required to complete any chapter including the capstone.
The sandbox is four containers and about 2 GB of disk. It stands in for the production stack the book teaches, and the substitutions are deliberate: the code you write against them is unmodified when pointed at the real thing.
| Sandbox | Stands in for | What differs |
|---|---|---|
| PostgreSQL 16 | the operational source database | nothing; it is Postgres |
| MinIO | S3 | the endpoint URL, and nothing else |
| DuckDB | Snowflake / BigQuery | SQL dialect at the margins (§B.9) |
| Redpanda | Kafka | the Kafka protocol, one binary, no ZooKeeper |
Plus Python 3.11+ and the pinned packages in §A.4.
A.1 Prerequisites
# verify what you have
docker --version # 24.0 or newer
docker compose version # v2; note the space, not docker-compose
python --version # 3.11 or newer
git --version
Disk: about 6 GB total — 2 GB of images, 1 GB of Python packages, and room for the generated data.
Memory: 8 GB works. 16 GB is comfortable, and Chapter 22's benchmark is more interesting with it because you can see where pandas stops fitting.
Windows: everything here works under WSL2 and under Git Bash. PowerShell users: the paths in this book use forward slashes, which Python accepts on Windows; the shell commands assume a POSIX shell.
A.2 The Compose File
# docker-compose.yml
services:
postgres:
image: postgres:16.4
environment:
POSTGRES_USER: kestrel
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: kestrel
command:
- "postgres"
- "-c"
- "wal_level=logical" # required for Chapter 14's CDC
- "-c"
- "max_replication_slots=4"
- "-c"
- "shared_preload_libraries=pg_stat_statements"
ports: ["5432:5432"]
volumes:
- pgdata:/var/lib/postgresql/data
- ./sandbox/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kestrel -d kestrel"]
interval: 5s
retries: 10
minio:
image: minio/minio:RELEASE.2024-09-13T20-26-02Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER in .env}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD in .env}
ports: ["9000:9000", "9001:9001"]
volumes:
- miniodata:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
retries: 10
redpanda:
image: redpandadata/redpanda:v24.2.4
command:
- redpanda start
- --overprovisioned
- --smp 1
- --memory 1G
- --kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092
- --advertise-kafka-addr PLAINTEXT://redpanda:29092,OUTSIDE://localhost:9092
ports: ["9092:9092", "9644:9644"]
volumes:
- rpdata:/var/lib/redpanda/data
volumes:
pgdata:
miniodata:
rpdata:
Why
${VAR:?message}rather than a default. Compose fails with your message if the variable is unset, so a missing credential is a startup error rather than a container running withminioadmin.validate.pyfails the build on a hardcoded credential anywhere in this book, and this is the pattern it expects.
Create .env beside the compose file, and add it to .gitignore:
POSTGRES_PASSWORD=$(openssl rand -hex 16)
MINIO_ROOT_USER=kestrel
MINIO_ROOT_PASSWORD=$(openssl rand -hex 16)
A.3 Bringing It Up
docker compose up -d
docker compose ps # all should be healthy
docker compose logs -f postgres # ctrl-c to detach
# the four buckets Chapter 5 section 5.9 specifies
docker compose exec minio sh -c '
mc alias set local http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" &&
mc mb -p local/bronze local/silver local/gold local/scratch'
Verify each one:
psql "postgresql://kestrel@localhost:5432/kestrel" -c "SELECT version();"
curl -s localhost:9000/minio/health/live && echo " minio ok"
docker compose exec redpanda rpk cluster info
python -c "import duckdb; print(duckdb.sql('SELECT 42 AS answer'))"
Tear down, keeping data: docker compose down
Tear down, destroying data: docker compose down -v
A.4 Python Environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv/Scripts/activate
pip install -r requirements.txt
requirements.txt, pinned — the versions this book was written and tested against:
apache-airflow==2.10.5
dbt-core==1.9.1
dbt-duckdb==1.9.1
pyspark==3.5.3
pandas==2.2.3
polars==1.17.1
duckdb==1.1.3
pyarrow==17.0.0
psycopg[binary]==3.2.3
boto3==1.35.36
confluent-kafka==2.5.3
great-expectations==0.18.22
requests==2.32.3
pytest==8.3.3
🧭 On the pins. Every version above is a version this book's code was run against. Newer will usually work and the failures are specific: pandas 3.x changes several defaults Chapter 22 relies on for its measurements, and Airflow 3.x renames concepts Chapter 24 teaches (see §D.11). If you are learning, pin. If you are building, upgrade deliberately and read the changelog.
Airflow needs its own constraint file, because it pins transitive dependencies aggressively:
AF=2.10.5; PY=$(python -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}")')
pip install "apache-airflow==${AF}" \
--constraint "https://raw.githubusercontent.com/apache/airflow/constraints-${AF}/constraints-${PY}.txt"
A.5 Seeding the Kestrel Data
python sandbox/seed.py --orders 2400000 --days 365 --seed 1
Generates the frozen anchors — 2.4M orders, 6,575/day, AOV $75.83, 2.70 lines per order, and a Black Friday at 6.28× the annual average day. Takes about four minutes and produces roughly 900 MB in Postgres.
A smaller run for a laptop under pressure:
python sandbox/seed.py --orders 120000 --days 60 --seed 1 # ~40 MB, 20 seconds
Every ratio is preserved at the smaller size, so the chapters' arithmetic still works; only the absolute figures change. The capstone requires the full set, because its sealed figures are for a specific month.
A.6 What Each Chapter Needs
| Chapters | Needs | Notes |
|---|---|---|
| 1–3 | nothing | reading and design |
| 6–7 | Postgres | |
| 8–11 | MinIO + DuckDB | |
| 13–14 | Postgres + MinIO | ch14 needs wal_level=logical, already set |
| 15, 29 | Redpanda | |
| 18–20, 23 | DuckDB + dbt | |
| 21–22 | pyspark, pandas, polars, duckdb | ch21 runs Spark local mode |
| 24, 27 | Airflow | |
| 28 | Terraform + a local backend | no cloud account needed |
| 30–40 | the above | mostly Python, no new services |
Spark in local mode needs a JDK: 17 for Spark 3.5.x. java -version should print 17.
A.7 Troubleshooting
Postgres will not start after a config change. The volume holds the old data directory. docker
compose down -v and re-seed, or edit postgresql.conf inside the volume.
MinIO buckets disappear. You ran down -v. They are recreated by the mc mb command in §A.3; put it
in a script.
Redpanda exits immediately. Almost always memory. Lower --memory to 512M, or raise Docker
Desktop's allocation.
Spark: java.lang.UnsupportedClassVersionError. Wrong JDK. Spark 3.5 wants 17.
Spark on Windows: HADOOP_HOME unset. Either run under WSL2 or install winutils.exe for the
matching Hadoop version. WSL2 is much less trouble.
dbt cannot find the DuckDB file. profiles.yml paths are relative to where you invoke dbt, not to
the project directory. Use an absolute path while learning.
Airflow DAG does not appear. Check dags_folder in airflow.cfg, then airflow dags list-import-errors
— which is the command that tells you about the exception the UI is silently swallowing.
docker compose says docker-compose not found. Compose v2 is a Docker subcommand: a space, not a
hyphen.
A.8 Running the Book's Code
Every chapter's code/ directory is standalone and dependency-free unless it says otherwise:
cd part-06-advanced-topics/chapter-31-privacy-engineering/code
python pii_scan.py --self-check # 80 assertions
python pii_scan.py --demo
Run --self-check first, always. It asserts every figure the chapter prints against the fixture, so
a passing self-check means the chapter's numbers describe the code in front of you. A failing one on an
unmodified file is a bug worth reporting.
Verify the whole book:
python scripts/validate.py # structure, anchors, sealed figures
python scripts/xref_audit.py # every cross-reference resolves
for f in part-*/chapter-*/code/*.py; do python "$f" --self-check; done