Pb
Developer Tools Azure

Pact Broker on Ubuntu 24.04 on Azure User Guide

| Product: Pact Broker on Ubuntu 24.04 LTS on Azure

Overview

Pact Broker is the open source contract testing hub for the Pact ecosystem. Consumer test runs publish their contracts to it, providers fetch those contracts and publish their verification results back, and can-i-deploy answers the question a release pipeline actually needs answered: is this version safe to deploy, given everything it talks to.

It keeps the full history of every contract, the matrix of who verified what against which version, tags and branches that describe where each version has reached, and a web interface for reading all of it. Webhooks let it drive the provider build when a consumer publishes a new contract, so the two sides of an integration stay honest without anyone co-ordinating a release.

The cloudimg image delivers Pact Broker 2.121.2 on Ubuntu 24.04 as a single node, served over TLS, with a write credential and a separate read-only credential generated on the first boot of your VM. Backed by 24/7 cloudimg support.

What is included:

  • Pact Broker 2.121.2, the official upstream container image pinned by digest, run by systemd. The unit runs with --pull=never, so the instance can never silently fetch a different build of the same version
  • nginx terminating TLS on port 443 with a certificate generated for your VM. Pact Broker speaks plain HTTP only, and HTTP Basic sends the credential on every request, so it never leaves the loopback interface
  • PostgreSQL 16, native from the Ubuntu archive so it is patched by the same updates as the rest of the VM, with no TCP listener at all — the broker reaches it over a Unix socket, so port 5432 is absent from every interface
  • Authentication required on every request, including the dashboard, the API, the status badges and the heartbeat. Pact Broker has no authentication of its own by default; this image never lets an unauthenticated broker reach the network
  • Two accounts, not one. A write credential for your pipeline and a separate read-only credential for people and dashboards, each with its own per-VM password
  • The base URL the broker renders into every link resolved per VM, so the pact CLI tools and your CI jobs work against it immediately
  • The upstream example contracts a new broker normally seeds itself with switched off, so the instance you receive is genuinely empty
  • An on-VM self test that publishes a contract, reads it back, records a verification result, checks can-i-deploy both ways and removes everything it created
  • The postgresql, docker, pact-broker and nginx systemd services, enabled and active

Pact, Pact Broker and Ubuntu are trademarks of their respective owners. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them. This image packages the unmodified open source software, which is distributed under the MIT License.

The four services active and the broker container published only to the loopback interface

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • An SSH key pair
  • A network security group allowing inbound TCP 443 (the web interface and API) and 22 (SSH) from the addresses you trust. Nothing else needs to be open

Step 1: Deploy from the Azure Marketplace

Search the Azure Marketplace for Pact Broker on Ubuntu 24.04 LTS by cloudimg, choose Create, and select a VM size. Standard_B2s (2 vCPU, 4 GiB) is the recommended size and is comfortable for a single team's broker. Add your SSH public key, allow inbound 443 and 22, and create the VM.

Step 2: Deploy from the Azure CLI

Accept the image terms once per subscription, then deploy:

az vm image terms accept --urn cloudimg:pact-broker-ubuntu-24-04:default:latest

az group create --name pact-broker-rg --location eastus

az vm create \
  --resource-group pact-broker-rg \
  --name pact-broker-vm \
  --image cloudimg:pact-broker-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

az vm open-port --resource-group pact-broker-rg --name pact-broker-vm --port 443 --priority 1010

Standard_B2s (2 vCPU, 4 GiB) is the recommended size and is comfortable for a single team's broker.

Step 3: Connect to your VM

ssh azureuser@<vm-public-ip>

The build account used to create the image does not exist on your VM. azureuser (or whichever administrator name you chose) is the only login.

Step 4: Confirm the broker is running

First boot generates this VM's credentials and only then releases the broker, so the first start takes a little longer than later ones while the database schema is created.

sudo systemctl is-active postgresql@16-main docker pact-broker nginx

All four report active. To see the broker itself:

sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

The PORTS column reads 127.0.0.1:9292->9292/tcp. The broker is published to the loopback interface only; nginx is what the network reaches.

Step 5: Check what the network can reach

sudo /usr/local/sbin/pact-broker-port-check.sh

This asserts the exact set of ports reachable from outside the VM, and fails if anything has changed. The shipped set is exactly 22 and 443. There is deliberately no port 80: a port that exists only to redirect is still a port, and every URL this broker publishes is https from the first character. PostgreSQL has no TCP listener at all.

Step 6: Retrieve the first-boot credentials

Every credential this broker uses was generated on this VM's first boot and exists on no other instance. They are written to a file only root can read:

sudo sed -E 's/^(PACT_BROKER_WRITE_PASSWORD=|PACT_BROKER_READ_ONLY_PASSWORD=|PACT_BROKER_DATABASE_PASSWORD=).*/\1<redacted>/' \
  /root/pact-broker-credentials.txt

That shows the file with the secrets masked, so you can see its shape before you read the real values. The fields are:

Field What it is
PACT_BROKER_URL the web interface and API base URL for this VM
PACT_BROKER_HOST this VM's customer-facing address, resolved at first boot
PACT_BROKER_WRITE_USER the write account name, always pactbroker
PACT_BROKER_WRITE_PASSWORD the write password for this VM
PACT_BROKER_READ_ONLY_USER the read-only account name, always pactbroker-readonly
PACT_BROKER_READ_ONLY_PASSWORD the read-only password for this VM
PACT_BROKER_DATABASE_USER the PostgreSQL role the broker connects as
PACT_BROKER_TLS_CERT this VM's certificate, for clients that verify it

Read the non-secret values when you need them:

sudo grep -E '^PACT_BROKER_(URL|HOST|WRITE_USER|READ_ONLY_USER)=' /root/pact-broker-credentials.txt

The per-VM credentials file with the three passwords redacted, and the permissions on the credentials and environment files

Which credential goes where. The write credential can publish contracts, record verification results and delete data — it belongs in your pipeline and nowhere else. The read-only credential can browse the interface, read the matrix and answer can-i-deploy questions — that is the one to give to people and to dashboards. Keeping them separate is the point: it means a person who only needs to look at the matrix never holds the credential that could rewrite it.

Step 7: Confirm the door refuses everything but your credentials

sudo /usr/local/sbin/pact-broker-verify-auth.sh /root/pact-broker-credentials.txt

This is the check that matters most on this product. Pact Broker has no authentication at all unless credentials are supplied, so the question is whether the image you received really refuses everyone except you. The script proves, against the real TLS front door:

  • the dashboard, the API, the heartbeat, the API browser and a status badge are all refused 401 with no credential
  • weak and product-shaped password guesses are refused
  • this VM's write credential is accepted
  • this VM's read-only credential can read, and is refused when it tries to write
  • the links the broker hands back carry https and this VM's own address, with no plaintext link anywhere
  • the running broker reports version 2.121.2
  • the broker's own port is unreachable from outside the VM, and nothing answers on 80 or 5432

Every anonymous request refused, the read-only credential refused on write, and the running broker reporting its version

Step 8: Sign in to the web interface

Open https://<PACT_BROKER_HOST>/ in a browser. Because the broker is protected with HTTP Basic authentication there is no login form — the browser prompts for a username and password. Use the read-only account for day to day viewing.

The certificate is generated for your VM, so a browser will warn about it until you install your own (see Step 14).

The Pact Broker dashboard listing integrations with their latest verification status

Each row is an integration between a consumer and a provider. The colour of the last-verified cell is the headline: green means the provider has verified the consumer's latest contract, red means it has not.

Step 9: Publish a contract from CI

A consumer's test run produces a contract file. Publishing it is a single authenticated PUT. Set the address and the write credential first:

export PACT_BROKER_BASE_URL="https://<PACT_BROKER_HOST>"
export PACT_BROKER_USERNAME="<PACT_BROKER_WRITE_USER>"
export PACT_BROKER_PASSWORD="<PACT_BROKER_WRITE_PASSWORD>"

Those three variables are exactly what the official pact-broker CLI, the pact-cli container and every Pact client library read, so a pipeline needs no other configuration. Publishing a contract directly with curl looks like this:

sudo bash -c 'CF=/root/pact-broker-credentials.txt
H=$(grep "^PACT_BROKER_HOST=" $CF | cut -d= -f2-)
U=$(grep "^PACT_BROKER_WRITE_USER=" $CF | cut -d= -f2-)
P=$(grep "^PACT_BROKER_WRITE_PASSWORD=" $CF | cut -d= -f2-)
curl -sS --cacert /etc/ssl/pact-broker/pact-broker.crt --resolve "$H:443:127.0.0.1" \
  -u "$U:$P" -o /dev/null -w "publish -> HTTP %{http_code}\n" \
  -X PUT -H "Content-Type: application/json" \
  -d "{\"consumer\":{\"name\":\"example-consumer\"},\"provider\":{\"name\":\"example-provider\"},\"interactions\":[{\"description\":\"a request for the example resource\",\"request\":{\"method\":\"GET\",\"path\":\"/example\"},\"response\":{\"status\":200}}],\"metadata\":{\"pactSpecification\":{\"version\":\"2.0.0\"}}}" \
  "https://$H/pacts/provider/example-provider/consumer/example-consumer/version/1.0.0"'

A 201 means the contract is stored. The consumer and provider are created automatically the first time they appear in a contract, so there is nothing to register in advance.

A published contract rendered in the web interface, with its interactions and verification badge

When you have finished with the worked example, remove it. Deleting a consumer or a provider removes its contracts, versions and verification results with it:

sudo bash -c 'CF=/root/pact-broker-credentials.txt
H=$(grep "^PACT_BROKER_HOST=" $CF | cut -d= -f2-)
U=$(grep "^PACT_BROKER_WRITE_USER=" $CF | cut -d= -f2-)
P=$(grep "^PACT_BROKER_WRITE_PASSWORD=" $CF | cut -d= -f2-)
for n in example-consumer example-provider; do
  curl -sS --cacert /etc/ssl/pact-broker/pact-broker.crt --resolve "$H:443:127.0.0.1" \
    -u "$U:$P" -o /dev/null -w "delete $n -> HTTP %{http_code}\n" -X DELETE \
    "https://$H/pacticipants/$n"
done'

Step 10: Record a verification result and ask can-i-deploy

The provider's build fetches the contract, runs it against the real provider, and publishes the outcome back. can-i-deploy then reads the matrix those results build up. Both halves, and both answers, are exercised by the on-VM self test:

sudo /usr/local/sbin/pact-broker-roundtrip.sh /root/pact-broker-credentials.txt

It publishes a contract with the write credential, reads it back with the read-only one, records a verification result, and then asks can-i-deploy twice: once for the version it verified, which must be allowed, and once for a version whose contract was never verified, which must not be. Finally it deletes everything it created and confirms the broker is empty again.

The contract round trip: publish, read back, verify, can-i-deploy answered both ways, and the broker left empty

In a real pipeline the same question is a single command before a deploy step:

export PACT_BROKER_BASE_URL="https://<PACT_BROKER_HOST>"
export PACT_BROKER_USERNAME="<PACT_BROKER_READ_ONLY_USER>"
export PACT_BROKER_PASSWORD="<PACT_BROKER_READ_ONLY_PASSWORD>"

Reading the matrix only needs the read-only credential, which is why a deploy gate can run without the credential that could change the answer.

Step 11: Read the matrix

The matrix is the full picture of which consumer version was verified against which provider version, and it is what can-i-deploy consults. It is reachable from any integration row in the dashboard.

The matrix for an integration, showing consumer and provider versions, their tags and the verification outcome

Step 12: Explore the API

Every page in the interface is backed by a link following JSON API, and the broker ships an API browser for it. Open https://<PACT_BROKER_HOST>/hal-browser/browser.html.

The API browser showing the broker's entry point, its available links and the live response headers

The links in each response carry this VM's own address, because the base URL is resolved at first boot. That is what lets a client follow them from a developer machine or a CI runner without any rewriting.

Step 13: Back up your broker

Everything the broker knows — every contract, verification result, tag, branch and webhook — lives in one PostgreSQL database, so a backup is a single dump:

sudo -u postgres pg_dump -Fc pact_broker > /var/tmp/pact-broker-backup.dump && sudo ls -lh /var/tmp/pact-broker-backup.dump

Copy that file off the VM with scp and keep it somewhere durable. Restoring it onto a fresh instance restores the whole broker. Note that the credentials are not in the dump: they live in /etc/pact-broker/pact-broker.env, and a restored instance keeps whatever credentials its own first boot generated, so the restore never hands anyone an old password.

Step 14: Use your own certificate and domain

The shipped certificate is generated for your VM's address. To use a real domain, point DNS at the VM, replace the certificate and key, and tell the broker its new address so the links it publishes match:

sudo cp /path/to/fullchain.pem /etc/ssl/pact-broker/pact-broker.crt
sudo cp /path/to/privkey.pem   /etc/ssl/pact-broker/pact-broker.key
sudo chmod 0600 /etc/ssl/pact-broker/pact-broker.key
sudo sed -i 's#^PACT_BROKER_BASE_URL=.*#PACT_BROKER_BASE_URL=https://pact.your-domain.com#' /etc/pact-broker/pact-broker.env
sudo systemctl restart pact-broker nginx

Changing PACT_BROKER_BASE_URL matters as much as the certificate: it is the address the broker writes into every link it returns, so a pipeline that follows those links is following whatever is set here.

Step 15: Keep the image patched

The VM applies Ubuntu security updates automatically. Everything that terminates TLS, stores your data or faces the network — nginx, PostgreSQL and the container runtime — is an ordinary Ubuntu package, so it is covered:

sudo grep -h 'APT::Periodic' /etc/apt/apt.conf.d/20auto-upgrades

Both lines read "1", which is what keeps the package lists refreshed and security updates installed without you doing anything.

Pact Broker itself is a pinned upstream container image rather than an Ubuntu package, so apt will never update it and will never warn you that it is old. The unit runs it with --pull=never and by digest, which is what stops an instance quietly changing underneath you. To move to a newer release: take the Step 13 backup, pull the tag you want with sudo docker pull, replace the image reference in /etc/systemd/system/pact-broker.service with the new digest, then sudo systemctl daemon-reload and sudo systemctl restart pact-broker. The database schema migrates automatically on the next start.

Step 16: Rotate the credentials

Both broker passwords can be changed at any time; nothing is baked into the data.

sudo bash -c 'NEW=$(openssl rand -hex 16)
sed -i "s#^PACT_BROKER_BASIC_AUTH_PASSWORD=.*#PACT_BROKER_BASIC_AUTH_PASSWORD=${NEW}#" /etc/pact-broker/pact-broker.env
sed -i "s#^PACT_BROKER_WRITE_PASSWORD=.*#PACT_BROKER_WRITE_PASSWORD=${NEW}#" /root/pact-broker-credentials.txt
systemctl restart pact-broker
echo "write password rotated"'

Confirm the new credential works and the whole posture still holds:

sudo /usr/local/sbin/pact-broker-verify-auth.sh /root/pact-broker-credentials.txt

Rotate the read-only password the same way, using PACT_BROKER_BASIC_AUTH_READ_ONLY_PASSWORD and PACT_BROKER_READ_ONLY_PASSWORD.

Troubleshooting

The broker will not start. It is deliberately gated on first boot having completed, because a broker with no credentials file would be a broker with no authentication. Check both:

sudo systemctl show -p ActiveState -p Result pact-broker-firstboot.service
sudo test -f /var/lib/cloudimg/pact-broker-firstboot.done && echo "first-boot sentinel present"

Result=success together with the sentinel means first boot completed and the broker has been released. If the sentinel is missing, sudo journalctl -u pact-broker-firstboot.service shows why.

Everything returns 401. That is the intended behaviour without a credential. Check you are using the right account for what you are doing: reads accept either credential, writes accept only the write one.

The links point at the wrong address. The broker publishes whatever PACT_BROKER_BASE_URL says. Check it:

sudo grep '^PACT_BROKER_BASE_URL=' /etc/pact-broker/pact-broker.env

Check the whole appliance at once.

sudo /usr/local/sbin/pact-broker-selftest.sh /root/pact-broker-credentials.txt

Support

cloudimg provides 24/7 support for this image: deployment, upgrades, TLS certificates and custom domains, CI and pipeline integration, credential and webhook management, database backup and scaling. Contact support through the Azure Marketplace listing or at cloudimg.co.uk.