Setting Up Pact Broker and PactFlow for Contract Testing

Setting Up Pact Broker and PactFlow for Contract Testing

The Pact Broker is the hub of your contract testing workflow. Consumers publish pacts to it. Providers fetch pacts from it, run verification, and publish results back. The broker tracks which consumer/provider version pairs are compatible and answers the critical question: "can this version be safely deployed?"

You have two options: self-host the open-source Pact Broker, or use PactFlow (the hosted SaaS from the Pact maintainers). This guide covers both.

Self-Hosting with Docker Compose

The Pact Broker is a Ruby/Rack application backed by PostgreSQL. Running it locally or on a VM is straightforward with Docker Compose.

# docker-compose.yml
version: "3.8"

services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: pact
      POSTGRES_PASSWORD: pact_password
      POSTGRES_DB: pact_broker
    volumes:
      - pact_postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pact"]
      interval: 10s
      timeout: 5s
      retries: 5

  pact-broker:
    image: pactfoundation/pact-broker:latest
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "9292:9292"
    environment:
      PACT_BROKER_DATABASE_URL: "postgres://pact:pact_password@postgres/pact_broker"
      PACT_BROKER_BASIC_AUTH_USERNAME: admin
      PACT_BROKER_BASIC_AUTH_PASSWORD: admin_secret
      PACT_BROKER_BASIC_AUTH_READ_ONLY_USERNAME: readonly
      PACT_BROKER_BASIC_AUTH_READ_ONLY_PASSWORD: readonly_secret
      PACT_BROKER_LOG_LEVEL: INFO
      PACT_BROKER_BASE_URL: "http://localhost:9292"
      # Allow public read (optional — useful for OSS projects)
      # PACT_BROKER_ALLOW_PUBLIC_READ: "true"

volumes:
  pact_postgres_data:

Start it:

docker compose up -d
open http://localhost:9292

The broker UI is available at http://localhost:9292. Log in with the admin credentials defined above.

Production Hardening

For a production deployment, add these environment variables:

environment:
  # TLS termination happens at a load balancer; tell the broker its public URL
  PACT_BROKER_BASE_URL: "https://pact.your-company.com"
  # Disable the UI in favor of API-only access
  # PACT_BROKER_DISABLE_SSL_VERIFICATION: "false"
  # Webhook configuration
  PACT_BROKER_WEBHOOK_SCHEME_WHITELIST: "https"
  PACT_BROKER_WEBHOOK_HOST_WHITELIST: "api.github.com,hooks.slack.com"
  # Allow specific IPs for webhook calls (CIDR notation)
  # PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST: "POST"

The PACT_BROKER_WEBHOOK_HOST_WHITELIST is a security control — it prevents the broker from being used as an SSRF proxy by limiting which hosts webhooks can call.

Configuring Webhooks

Webhooks are what make the contract testing feedback loop fast. Without webhooks, providers need to poll for new pacts. With webhooks, the broker notifies providers the moment a consumer publishes a contract change.

GitHub Actions Webhook

In the Pact Broker UI, navigate to WebhooksCreate webhook:

{
  "events": [
    {"name": "contract_content_changed"},
    {"name": "contract_published_with_no_provider_versions"}
  ],
  "request": {
    "method": "POST",
    "url": "https://api.github.com/repos/your-org/product-service/dispatches",
    "headers": {
      "Content-Type": "application/json",
      "Accept": "application/vnd.github.v3+json",
      "Authorization": "Bearer ${user.GitHubToken}"
    },
    "body": {
      "event_type": "pact_changed",
      "client_payload": {
        "pact_url": "${pactbroker.pactUrl}",
        "consumer_version": "${pactbroker.consumerVersionNumber}",
        "consumer_branch": "${pactbroker.consumerVersionBranch}",
        "provider": "${pactbroker.providerName}"
      }
    }
  }
}

Or via the Pact CLI:

pact-broker create-webhook \
  --url "https://api.github.com/repos/your-org/product-service/dispatches" \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer $GITHUB_TOKEN" \
  --data '{"event_type":"pact_changed"}' \
  --request POST \
  --description "Trigger provider verification on pact change" \
  --contract-content-changed \
  --broker-base-url http://localhost:9292 \
  --broker-username admin \
  --broker-password admin_secret

Slack Notification Webhook

Add a Slack notification for verification failures:

{
  "events": [{"name": "provider_verification_failed"}],
  "request": {
    "method": "POST",
    "url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
    "headers": {"Content-Type": "application/json"},
    "body": {
      "text": ":x: Provider verification failed!\n*Provider:* ${pactbroker.providerName}\n*Consumer:* ${pactbroker.consumerName}\n*Pact URL:* ${pactbroker.verificationResultUrl}"
    }
  }
}

Using PactFlow (Hosted SaaS)

PactFlow is the enterprise version of the Pact Broker, hosted by the Pact maintainers. It adds:

  • Secret management for webhook credentials (no plaintext tokens in webhook config)
  • Role-based access control
  • Team management
  • Advanced can-i-deploy with environment tracking
  • Bi-directional contract testing (OpenAPI + consumer tests without running provider)

Sign up at pactflow.io — the free tier supports 5 integrations (consumer/provider pairs), which is enough to evaluate the workflow.

After signing up, create a system account token under SettingsAPI Tokens. Copy the read/write token and store it in your CI secrets as PACT_BROKER_TOKEN.

Your broker URL will be https://your-org.pactflow.io.

All pact-broker CLI commands work identically against PactFlow — just replace the URL and use a Bearer token instead of basic auth:

pact-broker publish \
  --consumer-app-version $GITHUB_SHA \
  --branch $GITHUB_REF_NAME \
  --broker-base-url https://your-org.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN \
  pacts/

Can-I-Deploy CLI Usage

can-i-deploy is the gate before deployment. It queries the broker for the verification matrix and returns a non-zero exit code if the deployment would be unsafe.

Basic usage:

# Can this version of order-service be deployed to production?
pact-broker can-i-deploy \
  --pacticipant order-service \
  --version $GITHUB_SHA \
  --to-environment production \
  --broker-base-url https://your-org.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN

Output when safe:

Computer says yes \o/

CONSUMER       | C.VERSION | PROVIDER        | P.VERSION | SUCCESS?
---------------|-----------|-----------------|-----------|--------
order-service  | abc123f   | product-service | def456a   | true

All verification results are published and successful

Output when unsafe:

Computer says no ¯\_(ツ)_/¯

CONSUMER       | C.VERSION | PROVIDER        | P.VERSION | SUCCESS?
---------------|-----------|-----------------|-----------|--------
order-service  | abc123f   | product-service | MISSING   | false

There is no verified pact between order-service (abc123f) and product-service

Recording Deployments

When you actually deploy, record it in the broker:

pact-broker record-deployment \
  --pacticipant order-service \
  --version $GITHUB_SHA \
  --environment production \
  --broker-base-url https://your-org.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN

This is what makes --to-environment production meaningful in subsequent can-i-deploy calls.

Environment-Based Verification Tags

In PactFlow, environments are first-class objects. Register them once:

pact-broker create-environment \
  --name production \
  --display-name Production \
  --broker-base-url https://your-org.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN

pact-broker create-environment \
  --name staging \
  --display-name Staging \
  --broker-base-url https://your-org.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN

In your provider verification, use consumer_version_selectors to pull the right pacts:

// Verify pacts for consumers deployed to staging AND consumers on main branch
consumerVersionSelectors: [
  { deployedOrReleased: true },  // currently deployed anywhere
  { mainBranch: true },          // latest from main
  { matchingBranch: true },      // same branch name as provider (for feature branches)
],

The matchingBranch: true selector is particularly useful for feature branch workflows: if a consumer has a feature/new-checkout branch and the provider has a matching feature/new-checkout branch, they automatically get tested against each other.

Network Visualization

The broker UI includes a network diagram showing all consumer/provider relationships and verification status. This is especially valuable when you have many services and need to understand the blast radius of a change.

Navigate to Network in the broker UI to see:

  • All pacticipants (consumers and providers) as nodes
  • Pact relationships as edges
  • Color coding: green (verified), red (failed), grey (unverified)

For a self-hosted broker, the network view is available out of the box. For PactFlow, it's in the Matrix tab with additional filtering by environment.

Broker Maintenance

Cleaning Old Pact Versions

Pact versions accumulate over time. Clean up with:

# Keep only the latest 10 versions per pacticipant
pact-broker delete-pacticipant-version \
  --broker-base-url http://localhost:9292 \
  --broker-username admin \
  --broker-password admin_secret \
  --pacticipant order-service \
  --version old-sha-here

Or automate cleanup with a scheduled job:

# Delete all versions tagged with branches that no longer exist in git
git branch -r | grep -v HEAD | sed 's/origin\///' > /tmp/live-branches.txt
# Then iterate pact-broker delete calls for dead branches

Database Backup

For a self-hosted broker, back up PostgreSQL regularly:

docker exec pact-broker-postgres-1 \
  pg_dump -U pact pact_broker > pact_broker_backup_$(date +%Y%m%d).sql

Store backups in S3 or equivalent. A lost broker means re-publishing all pacts from CI history — painful but recoverable. A lost verification matrix means your can-i-deploy gates are blind until you rebuild it.

Self-Hosted vs PactFlow Decision

Factor Self-hosted PactFlow
Cost Infrastructure only Per-integration pricing
Maintenance Your team owns it Managed
Secrets in webhooks Plaintext in DB Encrypted secret store
Bi-directional contracts No Yes (Enterprise)
RBAC Basic (read-only user) Full team/role model
Setup time 30 minutes 5 minutes

For small teams (< 10 services), the free PactFlow tier is the fastest path. For large enterprises with compliance requirements around data residency, self-hosting gives you full control.

Read more

Start now free