Quickstart¶
Getting started with Pacto: from zero to a published contract in about five minutes. Everything here runs on your machine — no account, no registry of your own, nothing to sign up for.
You need the Pacto CLI (step 1) and, from step 6 onwards, Docker to run a throwaway registry. Step 9 deletes everything this page creates.
1. Install Pacto¶
Or via Go:
See Installation for the other methods, for
installing without sudo, and for
what to do if the script exits with Failed to fetch latest version — that is
the anonymous GitHub API rate limit, not a broken release.
2. Scaffold a contract¶
$ pacto init my-service
Created my-service/
my-service/pacto.yaml
my-service/interfaces/
my-service/configuration/
The argument is the service name, not a path — it becomes the directory and
service.name, so pacto init fleet/checkout-web scaffolds a bundle that fails
validation. cd into the directory you want and pass the bare name.
Three files, all valid as they stand:
| File | What it is |
|---|---|
pacto.yaml |
the contract — the only required file |
interfaces/openapi.yaml |
a placeholder OpenAPI spec with one path, /health |
configuration/schema.json |
a placeholder JSON Schema for the service's configuration |
pacto.yaml also ships a readiness: block (step 5) and a metadata: block,
both filled with placeholders.
If your service has no interfaces or no configuration, remove the directory
and the matching interfaces: / configurations: section in pacto.yaml —
deleting the directory alone breaks validation. The scaffold's health and
metrics capabilities are declared without an interface binding, so they need
no changes.
These are standard formats — OpenAPI for interfaces, JSON Schema for
configuration — so you can drop in files you already own (for example your Helm
chart's values.schema.json) instead of authoring new ones.
3. Validate¶
Validation runs three layers — structural, cross-field and policy enforcement. See the Contract Reference for the full rules.
4. Customize your contract¶
Edit my-service/pacto.yaml to match your service. A minimal contract only
requires pactoVersion and service:
Add sections as needed — interfaces, workload, state, capabilities, dependencies, configuration, policy, readiness. See the Contract Reference for every available field.
service.version is the contract's version, and it becomes the registry tag in
step 6. The scaffold starts at 0.1.0; the rest of this page uses that value.
5. Set the readiness claims¶
pacto init scaffolds a readiness: block with minScore: 80 and two claims
that start at not-done: runbook (weight 40) and security-review
(weight 60). Readiness is scored separately from validity, so plain
pacto validate ignores it. Ask for it explicitly:
$ pacto validate my-service --readiness
my-service is invalid
ERROR [READINESS_GATE_UNMET] readiness: score below gate: score 0, minScore 80 (0 done, 0 partial, 2 not-done, 0 deferred)
validation failed with 1 error(s)
That is the gate working, not a broken scaffold: nothing is done yet, so the
score is 0. Replace the placeholder claims with your service's real ones, then
set each status: to done, partial, not-done or deferred as you go. With
both scaffolded claims at done:
The gate is opt-in because the result is time-dependent — an assessment past its
expires: date scores 0. See
Contract Reference for the scoring
rules, including how partial claims earn part of their weight.
6. Publish to a registry¶
Start a throwaway registry so you can do the whole round trip with no account:
Docker has to be running, not just installed: if the daemon (or Docker
Desktop) is down, docker run fails with a connection error naming the Docker
socket rather than anything about Pacto.
$ docker run -d --rm -p 127.0.0.1:5001:5000 --name pacto-registry registry:3
6202c6df98fc5f50cf3b3375ea112d0928b274cb3bd6aa7b7d25d1e8e6b56aa2
registry:3 is the official registry image — CNCF Distribution
(github.com/distribution/distribution/v3), the reference OCI registry. Docker
pulls it the first time and prints download progress, then prints the container
ID as above; yours will differ. It holds nothing on disk and disappears in
step 9. Port 5001 rather than 5000 because macOS binds 5000 for AirPlay, and
127.0.0.1: rather than a bare 5001: because the registry accepts anonymous
writes — published wide, anything on your network could push to it.
# Auto-tags with service.version; skips if that tag already exists (--force overwrites)
$ pacto push oci://localhost:5001/demo/my-service-pacto -p my-service
Pushed my-service@0.1.0 -> localhost:5001/demo/my-service-pacto:0.1.0
Digest: sha256:<64 hex characters>
The digest is the full 64-character hash, and it is content-addressed: yours will differ from anyone else's the moment you edit the contract.
No pacto login here — a local registry needs no credentials. A real registry
needs two things you have to arrange yourself: an account you can publish to
(your-org must be a GitHub user or organisation you own) and, for GHCR, a
personal access token carrying the write:packages scope. With both in hand:
$ pacto login ghcr.io -u your-username
Password:
Login succeeded for ghcr.io
$ pacto push oci://ghcr.io/your-org/my-service-pacto -p my-service
Paste the token at the Password: prompt; it is not echoed. login stores the
credentials in ~/.config/pacto/config.json without contacting the registry, so
Login succeeded only means they were saved — a wrong token or a missing scope
surfaces on the push.
pacto pack is not a step on this path
pacto pack my-service writes my-service-0.1.0.tar.gz, a bundle you can
hand to someone with no registry access. pacto push reads the directory
directly and rejects a tarball (my-service-0.1.0.tar.gz is not a
directory), so packing before pushing does nothing. No other Pacto command
reads the archive either — whoever receives it extracts it first.
7. Read it back¶
explain takes a registry reference, so you can read what you published
without downloading it first:
$ pacto explain oci://localhost:5001/demo/my-service-pacto:0.1.0
Service: my-service@0.1.0
Owner: my-team
Pacto Version: 2.0
Workload: service
State:
Type: stateless
Persistence: local/ephemeral
Data Criticality: low
Capabilities (2):
- health
- metrics
Interfaces (1):
- api (openapi: interfaces/openapi.yaml, internal)
Readiness:
...
The Readiness: block is abridged above; the real output continues with the
score, the gate result, a per-claim table and the revision history.
To get the files themselves, pull the bundle. pull writes to a directory named
after the service unless you say otherwise — pass -o so it does not overwrite
the copy you are editing:
$ pacto pull oci://localhost:5001/demo/my-service-pacto:0.1.0 -o pulled
Pulled my-service@0.1.0 -> pulled/
You can also browse a contract instead of reading it:
8. Detect a breaking change¶
Remove the /health path from my-service/interfaces/openapi.yaml so the file
ends with an empty paths: map:
Then diff your working copy against the version you published:
$ pacto diff oci://localhost:5001/demo/my-service-pacto:0.1.0 my-service
Classification: BREAKING
Changes (1):
[BREAKING] openapi.paths[/health] (removed): API path /health removed [- /health]
breaking changes detected
The exit code is 1 when the classification is BREAKING — that is the CI gate.
A BREAKING verdict
means this change cannot ship under a compatible version: release it as a new
major (1.4.2 → 2.0.0), because that is what every consumer's
compatibility: "^1.0.0" range is reading. Nothing enforces that — bumping
service.version is itself classified NON_BREAKING, so pacto diff will not
notice whether you did it. What the version buys you is the consumers' side:
pinned to a major, they keep resolving the old contract until they choose to move.
Both sides of a diff can be local, so this check needs no registry at all:
pacto diff ./v1 ./v2 compares two directories and prints the same
classification. That is the form to reach for when comparing a release branch
against main in CI.
Removing an API path is one rule out of the full table:
Change classification lists every field pacto diff
compares and the verdict it reaches for each. See
Detecting breaking changes and the
GitHub Actions integration for wiring it into a pipeline.
9. Clean up¶
Nothing published to localhost:5001 outlives the container. Two things do
survive, both outside this directory:
- resolved bundles cached under
~/.cache/pacto/oci/, safe to delete; - credentials, if you ran
pacto loginagainst a real registry —pacto logout ghcr.ioremoves them from~/.config/pacto/config.json.
What to do next¶
| Goal | Guide |
|---|---|
| Understand every contract field | Contract Reference |
| Write and maintain contracts | For Developers |
| Consume contracts for deployment | For Platform Engineers |
| See contracts for real services | Examples (PostgreSQL, Redis, RabbitMQ, NGINX, gRPC and more) |
| Integrate with CI/CD | GitHub Actions |
| Explore contracts visually | Run pacto dashboard to launch the web UI with dependency graph |
| Runtime compliance in Kubernetes | Kubernetes Operator |
| Build a generation plugin | Plugin Development |