Skip to content
Get started
Get started
Docker: PostgreSQL and Three Brokers
examples · docker topology

PostgreSQL plus three active brokers.

A local topology that behaves like a broker fleet: private east-west traffic, unique broker identities, readiness gates, loopback-only host ports, and no bind mounts.

This is the exact file used by the automated example gate.

examples/postgres-multibroker/compose.yaml
x-broker-environment: &broker-environment
DATA_PATH: ''
BUNQUEUE_STORAGE_DRIVER: postgres
BUNQUEUE_POSTGRES_URL: postgres://bunqueue:local-demo-only@postgres:5432/bunqueue
BUNQUEUE_POSTGRES_NAMESPACE: realistic-example
BUNQUEUE_POSTGRES_POOL_SIZE: '4'
BUNQUEUE_POSTGRES_LEASE_DURATION_MS: '10000'
BUNQUEUE_POSTGRES_POLL_INTERVAL_MS: '50'
AUTH_TOKENS: demo-token
METRICS_AUTH: 'true'
TCP_PORT: '6789'
HTTP_PORT: '6790'
SHUTDOWN_TIMEOUT_MS: '15000'
x-broker: &broker
build:
context: ../..
dockerfile: Dockerfile
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ['CMD-SHELL', 'wget --spider -q http://127.0.0.1:6790/ready']
interval: 2s
timeout: 3s
retries: 30
start_period: 5s
restart: unless-stopped
networks: [demo]
services:
postgres:
image: postgres:18.6-alpine
environment:
POSTGRES_DB: bunqueue
POSTGRES_USER: bunqueue
POSTGRES_PASSWORD: local-demo-only
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U bunqueue -d bunqueue']
interval: 2s
timeout: 3s
retries: 30
volumes:
- postgres-data:/var/lib/postgresql
restart: unless-stopped
networks: [demo]
broker-a:
<<: *broker
environment:
<<: *broker-environment
BUNQUEUE_BROKER_ID: broker-a
ports:
- '127.0.0.1:${BROKER_A_TCP_PORT:-16789}:6789'
- '127.0.0.1:${BROKER_A_HTTP_PORT:-16790}:6790'
broker-b:
<<: *broker
environment:
<<: *broker-environment
BUNQUEUE_BROKER_ID: broker-b
ports:
- '127.0.0.1:${BROKER_B_TCP_PORT:-17789}:6789'
- '127.0.0.1:${BROKER_B_HTTP_PORT:-17790}:6790'
broker-c:
<<: *broker
environment:
<<: *broker-environment
BUNQUEUE_BROKER_ID: broker-c
ports:
- '127.0.0.1:${BROKER_C_TCP_PORT:-18789}:6789'
- '127.0.0.1:${BROKER_C_HTTP_PORT:-18790}:6790'
sdk-example:
profiles: [tools]
build:
context: ../..
dockerfile: examples/postgres-multibroker/client.Dockerfile
entrypoint: ['bun', 'run', 'examples/postgres-multibroker/run.ts']
command: ['all']
environment:
BUNQUEUE_TOKEN: demo-token
BROKER_A_HOST: broker-a
BROKER_A_PORT: '6789'
BROKER_A_HTTP_URL: http://broker-a:6790
BROKER_B_HOST: broker-b
BROKER_B_PORT: '6789'
BROKER_B_HTTP_URL: http://broker-b:6790
BROKER_C_HOST: broker-c
BROKER_C_PORT: '6789'
BROKER_C_HTTP_URL: http://broker-c:6790
BUNQUEUE_EXAMPLE_SCENARIO_TIMEOUT_MS: ${BUNQUEUE_EXAMPLE_SCENARIO_TIMEOUT_MS:-60000}
depends_on:
broker-a:
condition: service_healthy
broker-b:
condition: service_healthy
broker-c:
condition: service_healthy
networks: [demo]
volumes:
postgres-data:
networks:
demo:
internal: true
DetailReason
postgres:18.6-alpineThe pinned and recommended PostgreSQL release
DATA_PATH: ''Prevents the image’s SQLite default from conflicting with PostgreSQL mode
One shared URL and namespaceMakes PostgreSQL the authoritative state for all three brokers
Unique BUNQUEUE_BROKER_IDPrevents session and lease-owner identity collisions
/ready health checkGates traffic on PostgreSQL and maintenance-loop health, not process life alone
AUTH_TOKENS and METRICS_AUTHExercises authenticated TCP and Prometheus access
internal: true networkStops runtime containers from reaching the public internet
Loopback host bindingsKeeps local demonstration ports off external interfaces
Named PostgreSQL volumeMakes persistence explicit and teardown auditable
Startup and scenario deadlinesTurn a stalled health gate or runner into a failing, cleanable command
  1. Start from the repository root

    Docker must be running. No pre-existing bunqueue or PostgreSQL process is used.

  2. Execute the verifier

    Terminal window
    ./examples/postgres-multibroker/verify.sh
  3. Read one JSON result per scenario

    { "durationMs": 7, "scenario": "topology", "status": "PASS" }

    The duration is diagnostic functional timing, not a publishable benchmark.

  4. Confirm teardown

    The script independently attempts resource teardown and local-image removal, then preserves the original scenario failure status. A cleanup failure makes an otherwise successful run fail.

The trap is registered before the first Docker resource is created.

examples/postgres-multibroker/verify.sh
#!/bin/sh
set -eu
EXAMPLE_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
PROJECT_NAME=${BUNQUEUE_EXAMPLE_PROJECT:-bunqueue-pg-example-$(date +%s)-$$}
case "$PROJECT_NAME" in
bunqueue-pg-example-?*) ;;
*)
echo "BUNQUEUE_EXAMPLE_PROJECT must start with bunqueue-pg-example-" >&2
exit 2
;;
esac
case "$PROJECT_NAME" in
*[!a-z0-9_-]*)
echo "BUNQUEUE_EXAMPLE_PROJECT may contain only lowercase letters, digits, _ and -" >&2
exit 2
;;
esac
compose() {
docker compose --project-name "$PROJECT_NAME" --file "$EXAMPLE_DIR/compose.yaml" "$@"
}
cleanup() {
original_status=$?
trap - EXIT INT TERM
set +e
compose down --volumes --remove-orphans
resources_status=$?
compose down --volumes --remove-orphans --rmi local
images_status=$?
if [ "$resources_status" -ne 0 ] || [ "$images_status" -ne 0 ]; then
echo "Example cleanup failed" >&2
fi
if [ "$original_status" -ne 0 ]; then
exit "$original_status"
fi
if [ "$resources_status" -ne 0 ] || [ "$images_status" -ne 0 ]; then
exit 1
fi
exit 0
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
compose config --quiet
compose --progress plain build
compose up --detach --wait --wait-timeout 120 postgres broker-a broker-b broker-c
for scenario in topology multi-queue reliability flow; do
compose run --rm --no-deps sdk-example "$scenario"
done
compose ps

The SDK runner uses service DNS on the internal network. For manual inspection from the host, the Compose file publishes these loopback-only endpoints:

BrokerTCPHTTP
A127.0.0.1:16789127.0.0.1:16790
B127.0.0.1:17789127.0.0.1:17790
C127.0.0.1:18789127.0.0.1:18790

Override a host port with BROKER_A_TCP_PORT, BROKER_A_HTTP_PORT, and the corresponding B or C variables. Internal container ports remain 6789 and 6790.

BUNQUEUE_EXAMPLE_SCENARIO_TIMEOUT_MS overrides the positive 60,000 ms runner deadline. A custom BUNQUEUE_EXAMPLE_PROJECT must begin with bunqueue-pg-example- and contain only lowercase letters, digits, _, and -. The verifier deliberately destroys every resource owned by that selected project, so never reuse the name of a project you intend to retain.

  • /healthz answers whether the broker process can serve HTTP.
  • /ready returns 503 when persistent storage or a critical maintenance loop is degraded, so a load balancer should remove that broker.
  • /prometheus exposes operational metrics; this example requires the bearer token.

Clients should be routed only to ready brokers. Killing an unhealthy process just because readiness failed can make a transient database incident noisier; keep liveness and readiness probes separate.

Next: connect multiple queues and workers.