Hookway

Docker compose — replay/docker-compose

# Core with the optional replay service: two core replicas, two replay
# replicas, PostgreSQL, a one-shot schema migration, and two HTTP sinks that
# stand in for the destinations (`docker compose stop audit-sink` makes the
# audit publish fail while orders still succeeds, so the failure is captured). Docker's service DNS round-robins "core" and
# "replay" across their replicas, so each side reaches the other through one
# name. See docs/operations/replay-service.md.
#
#   docker compose -f examples/replay/docker-compose.yaml up --build
#
# The token defaults are for local use only; export your own before `up`.
x-replay-env: &replay-env
  REPLAY_CAPTURE_TOKEN: ${REPLAY_CAPTURE_TOKEN:-dev-capture-token-0123456789abcdef}
  REPLAY_CONTROL_TOKEN: ${REPLAY_CONTROL_TOKEN:-dev-control-token-0123456789abcdef}
  REPLAY_OPERATOR_TOKEN: ${REPLAY_OPERATOR_TOKEN:-dev-operator-token-0123456789abcde}
  REPLAY_METRICS_TOKEN: ${REPLAY_METRICS_TOKEN:-dev-metrics-token-0123456789abcdefg}
  REPLAY_CONSOLE_ORIGIN: ${REPLAY_CONSOLE_ORIGIN:-http://localhost:8082}
  REPLAY_DATABASE_URL: postgres://replay:replay@postgres:5432/replay?sslmode=disable

services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: replay
      POSTGRES_PASSWORD: replay
      POSTGRES_DB: replay
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U replay -d replay"]
      interval: 2s
      timeout: 3s
      retries: 30

  migrate:
    build:
      context: ../..
      dockerfile: Dockerfile
      target: replay
    command: ["migrate", "--config", "/etc/hookway-replay/replay.yaml"]
    environment: *replay-env
    volumes:
      - ./replay.yaml:/etc/hookway-replay/replay.yaml:ro
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"

  replay:
    build:
      context: ../..
      dockerfile: Dockerfile
      target: replay
    command: ["serve", "--config", "/etc/hookway-replay/replay.yaml"]
    # Longer than workers.execution_timeout, so in-flight runs finalize.
    stop_grace_period: 70s
    deploy:
      replicas: 2
    environment: *replay-env
    volumes:
      - ./replay.yaml:/etc/hookway-replay/replay.yaml:ro
    ports:
      - "8090-8091:8080"
    depends_on:
      migrate:
        condition: service_completed_successfully

  core:
    build:
      context: ../..
      dockerfile: Dockerfile
      target: core
    # Exceed core server.shutdown_total_timeout (60s), including redrives,
    # publisher drains and capture cleanup.
    stop_grace_period: 70s
    deploy:
      replicas: 2
    environment:
      HOOKWAY_CONFIG: /etc/hookway/config.yaml
      ORDERS_WEBHOOK_SECRET: ${ORDERS_WEBHOOK_SECRET:-change-me}
      REPLAY_CAPTURE_TOKEN: ${REPLAY_CAPTURE_TOKEN:-dev-capture-token-0123456789abcdef}
      REPLAY_CONTROL_TOKEN: ${REPLAY_CONTROL_TOKEN:-dev-control-token-0123456789abcdef}
    volumes:
      - ./core.yaml:/etc/hookway/config.yaml:ro
    ports:
      - "8080-8081:8080"
    depends_on:
      replay:
        condition: service_started
      orders-sink:
        condition: service_started
      audit-sink:
        condition: service_started

  orders-sink:
    image: hashicorp/http-echo:1.0
    command: ["-listen=:8080", "-text=ok"]

  audit-sink:
    image: hashicorp/http-echo:1.0
    command: ["-listen=:8080", "-text=ok"]

  console:
    build:
      context: ../..
      dockerfile: web/Dockerfile
    environment:
      # Browser-reachable URL, never Docker service DNS. No API keys here.
      REPLAY_API_URL: ${REPLAY_API_URL:-http://localhost:8090}
    ports:
      - "8082:8080"