Infra & Compose
What we’re building
Section titled “What we’re building”One docker-compose.yml under deploy/compose/ that brings up the three backing services every ShopMicro service depends on: PostgreSQL (with two databases created on first boot), Kafka running in KRaft mode (no ZooKeeper), and RabbitMQ with its management UI. Nothing here runs application code yet — this is purely the infrastructure Module 1’s .env.example already points at.
Running Postgres, Kafka, and RabbitMQ as native daemons on your machine means installing and version-managing three separate pieces of software, each with its own quirks on macOS. Docker Compose collapses all three into one file and one command, with every setting — users, passwords, ports — explicit and shared identically across every contributor’s machine.
KRaft, explained. Historically Kafka needed a separate ZooKeeper cluster just to store broker metadata and elect a controller. Since Kafka 3.3, KRaft (Kafka Raft) replaces ZooKeeper with a built-in Raft-based controller quorum — the brokers themselves manage metadata and leader election. For local development that means one container instead of two, which is exactly what KAFKA_PROCESS_ROLES: broker,controller below configures: a single node acting as both broker and controller.
Database-per-service, explained. Catalog and Order each get their own PostgreSQL database — not their own schema inside one database, a genuinely separate database. Catalog can’t run a query against Order’s tables even if it wanted to; the only way across that boundary is a gRPC call. That isolation, enforced even in local dev, is what keeps the services independently deployable later.
The reserved-word note. order is a reserved SQL keyword in PostgreSQL. Naming a database order would force psql, migration tools, and every raw SQL string to quote it as "order" forever. We sidestep that entirely by naming the database orders — which is also why .env.example’s ORDER_DB_URL (from Repo Layout →) ends in /orders.
Pros & cons
Section titled “Pros & cons”Pros
docker compose up -dbrings up all three services with one command, andhealthcheckblocks let dependent tooling wait for “actually ready,” not just “container started.”- Named volumes (
pgdata) persist Postgres data across restarts without polluting your host filesystem. - Everyone on the team gets byte-identical infrastructure — same versions, same users, same ports.
Cons
- A single-node KRaft broker is not a production Kafka cluster — there’s no replication, no multi-broker failover, and it’s only meant to model the shape of the real thing for local development.
- Docker abstracts away real resource constraints; a service that works fine against a container on a powerful laptop may behave differently against a properly sized production cluster.
- This Compose file and the Helm chart built in Module 14 describe the same infrastructure two different ways — they can drift apart if one is updated without the other.
Set it up
Section titled “Set it up”1. The Postgres init script
Section titled “1. The Postgres init script”On first boot (and only on first boot — this doesn’t re-run if the volume already has data), Postgres runs every .sql file mounted into /docker-entrypoint-initdb.d/. We use that to create both application databases:
CREATE DATABASE catalog;CREATE DATABASE orders;Save this as deploy/compose/initdb.sql.
2. docker-compose.yml
Section titled “2. docker-compose.yml”services: postgres: image: postgres:16 environment: POSTGRES_USER: shopmicro POSTGRES_PASSWORD: shopmicro POSTGRES_DB: shopmicro ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data - ./initdb.sql:/docker-entrypoint-initdb.d/initdb.sql healthcheck: test: ["CMD-SHELL", "pg_isready -U shopmicro"] interval: 5s timeout: 5s retries: 5
kafka: image: apache/kafka:3.8.0 environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093 KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 ports: - "9092:9092"
rabbitmq: image: rabbitmq:3-management environment: RABBITMQ_DEFAULT_USER: shopmicro RABBITMQ_DEFAULT_PASS: shopmicro ports: - "5672:5672" - "15672:15672" healthcheck: test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] interval: 10s timeout: 5s retries: 5
volumes: pgdata:Save this as deploy/compose/docker-compose.yml.
A note on KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092: this tells clients “reconnect to localhost:9092,” which is correct for services running directly on your host machine (as they do throughout this course, until Module 13’s Docker module containerizes them too). At that point the advertised listener changes to the in-network container hostname — this localhost form is specifically a development-mode convenience.
3. Bring it up
Section titled “3. Bring it up”cd deploy/composedocker compose up -dVerify
Section titled “Verify”Check that all three containers report healthy:
docker compose psPostgreSQL — connect and confirm both databases exist:
docker compose exec postgres psql -U shopmicro -d shopmicro -c '\l'You should see catalog and orders in the database list, alongside the default shopmicro database.
Kafka — list topics (empty is expected; nothing has published yet):
docker compose exec kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --listAn empty response with no error means the broker is up and reachable.
RabbitMQ — open the management UI at http://localhost:15672 and log in with shopmicro / shopmicro. You should land on the overview dashboard.
deploy/compose/docker-compose.yml brings up three backing services with docker compose up -d: PostgreSQL 16, initialized via initdb.sql with two databases — catalog and orders (named orders, not order, because order is a reserved SQL keyword); Kafka in KRaft mode, a single node acting as both broker and controller with no ZooKeeper; and RabbitMQ with its management UI on port 15672. Each service owning its own database is the local-dev version of the isolation that keeps ShopMicro’s services independently deployable. That’s Module 1 done — the monorepo is scaffolded, the Go module builds, buf is configured, and the infrastructure is up and verified. Module 2 writes the first real .proto contracts and generates actual Go code from them.