Lab: Postgres Replication¶
Pairs with: Replication
A real primary + 2 streaming replicas, cloned via pg_basebackup. Flip synchronous/asynchronous replication live, watch a synchronous write hang when its standby is down, and cause a real split-brain by running pg_promote() — the naive failover Replication warns about.
docker-compose.yml¶
name: labs-pg-replication
services:
primary:
image: postgres:16
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: app_pass
POSTGRES_DB: appdb
# Starts fully async. The README walks you through flipping replica1 to
# a synchronous standby live (ALTER SYSTEM + reload) once it's caught
# up — turning it on before any standby exists would deadlock the
# primary's own startup (it would wait forever for an ACK from a
# standby that can't exist until the primary finishes starting).
command: >
postgres
-c wal_level=replica
-c max_wal_senders=10
volumes:
- ./init-primary.sh:/docker-entrypoint-initdb.d/init-primary.sh:ro
ports:
- "5432:5432"
healthcheck:
test: ["CMD", "pg_isready", "-U", "appuser"]
interval: 5s
timeout: 5s
retries: 20
replica1:
image: postgres:16
hostname: replica1
depends_on:
primary:
condition: service_healthy
environment:
PGDATA: /var/lib/postgresql/data
PRIMARY_HOST: primary
APP_NAME: replica1
entrypoint: ["/replica-entrypoint.sh"]
volumes:
- ./replica-entrypoint.sh:/replica-entrypoint.sh:ro
ports:
- "5433:5432"
replica2:
image: postgres:16
hostname: replica2
depends_on:
primary:
condition: service_healthy
environment:
PGDATA: /var/lib/postgresql/data
PRIMARY_HOST: primary
APP_NAME: replica2
entrypoint: ["/replica-entrypoint.sh"]
volumes:
- ./replica-entrypoint.sh:/replica-entrypoint.sh:ro
ports:
- "5434:5432"
replica-entrypoint.sh¶
The replica's custom entrypoint — clones from the primary via pg_basebackup on first boot, then hands off to the standard postgres entrypoint:
#!/bin/bash
# Custom entrypoint for the replicas: clone from the primary via
# pg_basebackup on first boot (empty data dir), then hand off to the
# standard postgres entrypoint. -R writes standby.signal and
# primary_conninfo for us — that's what makes this a streaming replica.
set -e
if [ -z "$(ls -A "$PGDATA" 2>/dev/null)" ]; then
echo "[$APP_NAME] data dir empty — waiting for primary, then cloning"
until pg_isready -h "$PRIMARY_HOST" -U repl_user -d postgres; do sleep 1; done
pg_basebackup -h "$PRIMARY_HOST" -U repl_user -D "$PGDATA" -Fp -Xs -P -R \
-d "application_name=${APP_NAME}"
chmod 700 "$PGDATA"
fi
exec docker-entrypoint.sh postgres
Exercises¶
The full walkthrough (confirm replication, flip sync/async live, watch a sync write hang, promote a replica and observe the resulting split-brain) lives in the lab's README: