svccat
Service catalog drift detection for multi-service repositories.
svccat reads your declared service manifest and compares it against what actually exists in the repo — flagging missing services, undeclared additions, and stale metadata before they become operational toil.
Why svccat?
In any multi-service repo the architecture docs, service inventory, and the
codebase evolve at different speeds. A new service appears in services/ but
never makes it into the manifest. A deprecated service stays in the YAML long
after the directory is gone. docs: and ci: references silently rot.
svccat makes drift visible in your terminal and in CI, so your declared architecture stays honest.
Installation
Or build from source:
# binary at target/release/svccat
Quick start
Commands are grouped by task. Every command runs against the auto-detected
manifest in the current repo unless you pass -m <file>.
Set up a manifest
svccat initscaffolds aservices.yamlfrom your repo (--forceto overwrite)svccat import --from backstageseeds it fromcatalog-info.yamlfilessvccat import --from docker-composeseeds it fromdocker-compose.ymlsvccat import --from openapiseeds it from OpenAPI / Swagger specssvccat lintvalidates the manifest for structural issuessvccat install-hooksinstalls a pre-commit hook (--hook pre-pushfor pre-push)svccat completions bashprints a shell completion script (alsozsh,fish)
Check for drift
svccat checkinspects drift in the current reposvccat check --fail-on-driftexits 1 on any drift (CI gate)svccat check --depth 2also discovers services nested one level deepersvccat check --team platformlimits the check to one team's servicessvccat check --ignore "examples/*"skips directories matching a patternsvccat check --since HEAD~1shows only drift new since a git refsvccat check --since HEAD~1 --fail-on-new-driftexits 1 only on new driftsvccat check --baseline baseline.jsonignores drift saved in a baselinesvccat check --pingverifies that each serviceurlis reachable
Add --format <fmt> to change the output, where <fmt> is one of compact, csv,
json, sarif, markdown, junit, github-annotation, slack, teams, or datadog.
Fix drift
svccat fixadds undeclared services to the manifestsvccat fix --prunealso removes declared services with missing directoriessvccat fix --dry-runpreviews the changes without writing
Visualize and report
svccat graphrenders a Mermaid diagram grouped by platformsvccat graph --team platform/--filter paymentsscopes the diagramsvccat graph --format dot|plantuml|markdownselects another formatsvccat reportwrites a full ownership and drift report (Markdown)svccat report --format html --output report.htmlwrites a self-contained pagesvccat report --history 5shows drift evolution across the last 5 commitssvccat report --badgeprints a Shields.io badge for your READMEsvccat scorecardscores each service on completeness, drift, and policy
Snapshots and diffs
svccat export --format json > snap.jsonsaves a catalog snapshot (csvalso works)svccat export --format json --since HEAD~1exports only services changed since a refsvccat diff before.json after.jsoncompares two snapshots (add--format markdown)svccat snapshot save|list|delete|diff <name>manages named drift snapshots
Watch and serve
svccat watchre-runs drift detection on every file changesvccat watch --since main/--notify/--interval 30refine watchingsvccat serveserves a live HTML report at http://localhost:7777svccat serve --port 9000 --refresh 10customizes the port and refresh interval
Audit, policy, and CI
svccat auditruns lint + drift + score in one pass (add--ping)svccat policychecks required/recommended fields (add--fail-on-violations)svccat ciruns lint + drift + policy in one CI-friendly passsvccat statsprints a field-coverage summary with ASCII bar chartssvccat webhookfires a configured Slack/webhook notification on drift
Query the catalog
svccat search authsearches by substring;svccat search team:platformby fieldsvccat depsanalyzes inter-service dependencies (add--format mermaid|json)svccat tag add auth-service critical/tag remove auth-service betaedits tags
Multiple repositories
svccat workspace checkchecks drift across every repo in a[workspace]configsvccat workspace check --filter backend,apilimits the run to named repossvccat workspace check --format markdown --fail-on-driftaggregates results for CI
Most commands accept --output <file> to write to a file and --format json for
machine-readable output.
Manifest is auto-detected: svccat tries svccat.yaml, svccat.yml,
services.yaml, services.yml in order.
GitHub Actions tip: When running inside a GitHub Actions workflow,
svccat checkautomatically switches togithub-annotationoutput (no--formatflag needed) so drift items appear as inline annotations on pull requests. Setupload-sarif: truein the Action step to also send results to GitHub Code Scanning.
svccat init
Bootstrap a services.yaml in seconds by letting svccat discover what's
already in your repo:
The generated file includes every detected service with language inferred from
marker files (Cargo.toml → Rust, go.mod → Go, package.json → TypeScript,
pyproject.toml / requirements.txt → Python), plus ~ placeholders for
platform, role, and url that you fill in before committing.
Example output:
# Generated by `svccat init`
# Fill in the ~placeholder~ fields and commit this file.
# Run `svccat check` to verify there is no drift.
version: "1"
discovery:
paths:
- services/*
- microservices/*
- apps/*
- packages/*
services:
- name: api-gateway
path: services/api-gateway
language: Go
platform: ~ # e.g. gcp-cloud-run, fly.io, vercel, aws-lambda
role: ~ # e.g. api, worker, frontend, database
url: ~ # e.g. https://my-service.example.com
- name: auth-service
path: services/auth-service
language: Rust
platform: ~
role: ~
url: ~
Manifest format
# svccat.yaml (or services.yaml for backwards compat)
version: "1"
# Optional: configure how svccat discovers services in the repo.
discovery:
paths: # glob patterns for candidate service directories
- "services/*"
- "microservices/*"
markers: # files that identify a directory as a service
- Cargo.toml
- Dockerfile
- go.mod
- package.json
- pyproject.toml
- requirements.txt
ignore: # paths to exclude from discovery
- "services/examples"
- "services/vendor/*"
policy:
require_fields: # every service must declare these fields (error if missing)
- url
- language
- platform
services:
- name: api-gateway # required
language: Go # recommended
platform: Cloud Run # recommended
role: Rate-limiting reverse proxy # required (error if missing)
url: https://gateway.example.com # optional: enables --ping health checks
team: platform # optional: owning team name
oncall: platform@example.com # optional: on-call contact (email, handle, PD service)
path: infra/gateway # optional: explicit path (overrides name matching)
submodule: go-gateway # optional: git submodule path (Portfolio-compatible)
docs: docs/api-gateway.md # optional: warn if file missing
ci: .github/workflows/api-gateway.yml # optional: warn if file missing
depends_on: # optional: rendered as edges in svccat graph
- auth-service
- postgres
Default discovery paths
When discovery.paths is empty svccat tries services/*, microservices/*,
apps/*, and packages/*.
Matching declared ↔ discovered
- If the entry has
path:→ check that path exists. - Else if the entry has
submodule:→ check that path exists. - Else → match by name against discovered service directory names.
Drift types
| Kind | Severity | Description |
|---|---|---|
declared_missing_from_repo |
error | Service is in the manifest but its directory is not found in the repo. |
undeclared_in_repo |
warning | A service directory was discovered but is not listed in the manifest. |
missing_field |
error / warning | A recommended metadata field is absent (role = error; language, platform = warning). |
missing_referenced_file |
warning | A docs: or ci: path is declared but the file does not exist. |
policy_violation |
error | A field required by policy.require_fields is absent. |
dangling_dependency |
error | A depends_on entry references a service not declared in the manifest. |
circular_dependency |
error | A cycle was detected in the depends_on graph. |
depends_on validation and cycle detection
svccat validates all depends_on references and checks for cycles in the
dependency graph:
services:
- name: api-gateway
depends_on: # ghost-service → DanglingDependency error
- name: auth-service
depends_on: # api-gateway → api-gateway = CircularDependency error
x [DEPENDS] 'api-gateway' depends_on 'ghost-service' which is not declared in the manifest
x [CYCLE] circular dependency detected: api-gateway → auth-service → api-gateway
Both kinds are error-severity and count toward --fail-on-drift.
SARIF output — GitHub Code Scanning integration
Use --format sarif to emit SARIF 2.1.0
output. Upload it to GitHub Code Scanning so drift items appear as inline
annotations on pull requests — no extra tooling required.
GitHub Actions integration with Code Scanning
# .github/workflows/catalog.yml
- name: Run svccat
run: svccat check --format sarif > results.sarif
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Each drift item becomes a code-scanning alert with:
- Rule ID matching the drift kind (e.g.
declared_missing_from_repo) - Severity (
errororwarning) - Location pointing to the manifest file
svccat graph --team — team-scoped diagrams
Filter the Mermaid diagram to a single team's services. Cross-team
depends_on targets are shown as external placeholder nodes so
dependencies are still visible.
graph TD
subgraph Cloud_Run["Cloud Run"]
api_gateway["api-gateway\nGo\nAPI gateway"]
auth_service["auth-service\nRust\nJWT issuance"]
end
subgraph External["External (other teams)"]
postgres["postgres\n[ext]"]
end
api_gateway --> auth_service
api_gateway --> postgres
svccat diff — compare snapshots
Track how your catalog evolves over time by diffing two JSON snapshots:
# Save a snapshot before making changes
# ... update services.yaml or add/remove services ...
# Save a snapshot after
# Show what changed
Example output:
svccat diff: before.json → after.json
Services added (1):
+ new-worker
Services removed (1):
- legacy-api
Services changed (1):
~ auth-service
language: Python → Rust
Resolved drift (1):
✓ [ERROR] legacy-api — 'legacy-api' is declared in the manifest but not found in the repo
Policy rules
Enforce field requirements across all services via the policy: section in
your manifest:
policy:
require_fields:
- url # every service must have a health-check URL
- language # documentation requirement
- platform # deployment target must be explicit
Any service missing a required field is flagged as an error-level drift item:
x [POLICY] 'worker-service' violates policy: required field 'url' is missing
Policy violations count toward --fail-on-drift and fail_on_drift in
svccat.toml.
Ownership metadata — team and oncall
Declare service ownership directly in your manifest so every entry has a clear owner:
services:
- name: api-gateway
team: platform # owning team name
oncall: platform@example.com # on-call contact (email, handle, or PD service)
- name: billing-service
team: growth
oncall: growth-pagerduty
Team-scoped checks
Pass --team <name> to limit drift detection to a single team's services.
Services belonging to other teams are excluded from analysis (no false
UndeclaredInRepo noise).
# CI step for the platform team — only checks platform-owned services.
Enforce ownership via policy
Require team and oncall on every service using policy.require_fields:
policy:
require_fields:
- team
- oncall
Any service missing either field becomes an error-level policy violation.
svccat watch — continuous drift detection
svccat watch monitors the manifest file and service directories for changes
and re-runs drift analysis automatically. Useful while actively editing a
manifest or onboarding services.
Example output when a service directory is added or the manifest changes:
svccat: 3 declared, 3 discovered [services.yaml]
OK No drift detected
● Watching services.yaml and service directories. Press Ctrl-C to stop.
[14:32:07 UTC] change detected — re-running drift check
svccat: 3 declared, 4 discovered [services.yaml]
DRIFT DETECTED (0 errors, 1 warning)
! [UNDECLARED] 'services/new-worker' exists in the repo but is not listed in the manifest
! 1 warning(s)
Press Ctrl-C to stop watching.
svccat report — ownership report
Generate a full Markdown (or HTML) ownership report: per-team service tables, drift status per service, dependency summary, and full drift details.
Example Markdown output:
The HTML format produces a self-contained styled page suitable for sharing with non-technical stakeholders.
svccat report --history <N> — drift evolution over time
Run the report across the last N git commits and emit a Markdown table showing how drift has evolved. Uses the current discovered services against each historical manifest.
Example output:
Useful for sprint reviews, architecture health dashboards, and understanding how long-running drift was introduced.
svccat lint — manifest validation
Check the manifest for structural problems that drift analysis won't catch:
Checks performed:
| Check | Severity |
|---|---|
| Duplicate service names | error |
| Blank or whitespace-only service name | error |
| Service depends_on itself | error |
| Blank entry in depends_on list | error |
| Duplicate entries in depends_on | warning |
| Unrecognised manifest version | warning |
Example output:
✗ [error] duplicate service name 'api' appears 2 times
✗ [error] 'worker' lists itself in depends_on
⚠ [warn] 'api' lists 'auth' more than once in depends_on
found 2 error(s), 1 warning(s)
Exit codes: 0 if no errors (warnings are allowed), 1 if any errors are found.
svccat check --since — PR-friendly drift diffs
Compare current drift against the manifest as it was at a given git ref. Outputs only what changed — new drift items or issues that were resolved.
Example output:
svccat --since HEAD~1 [services.yaml]
NEW drift since HEAD~1 (1):
x [MISSING] 'legacy-worker' is declared in the manifest but not found in the repo
RESOLVED since HEAD~1 (1):
✓ [UNDECLARED] 'services/old-api' exists in the repo but is not listed in the manifest
3 existing drift items unchanged
Ideal for a CI step on pull requests: only fails when the PR introduces new drift, not when existing drift is already tracked.
--fail-on-new-drift — gate CI on new drift only
Add --fail-on-new-drift to exit 1 only when --since reveals new drift items.
Pre-existing drift that was already present at the base ref is ignored.
# In CI: fail the PR only if it introduces new drift
# Can combine with --format markdown to post as a PR comment
--format markdown — Markdown output for PR comments
Emit the drift report as a Markdown table suitable for posting as a GitHub PR comment.
Example output:
**3 declared · 3 discovered** — `services.yaml`
❌ **DRIFT DETECTED** (1 error, 1 warning)
--format github-annotation — GitHub Actions annotations (v0.10.0)
Emit drift as GitHub Actions annotations for inline PR feedback:
This outputs:
::error file=services.yaml,title=svccat [MISSING]::legacy-worker: declared in manifest but directory not found
::warning file=services.yaml,title=svccat [FIELD]::api-gateway: missing recommended field: oncall
Annotations appear in PR checks and in the Annotations tab of the workflow run. See the included .github/workflows/svccat-pr.yml for a production-ready workflow template.
--format junit — JUnit XML output (v0.11.0)
Emit drift as JUnit XML for CI systems that ingest test reports:
This is useful for GitHub Actions, GitLab CI, Jenkins, CircleCI, and other systems that can display JUnit test results.
svccat report --badge — Shields.io badge (v0.10.0)
Emit a Markdown badge snippet for embedding in your README:
Output (example — green for clean, red/yellow for drift):
[](https://crates.io/crates/svccat)
Embed in your README to show catalog health at a glance.
svccat.toml — workspace defaults
Place a svccat.toml in your repo root to set persistent defaults so you
don't need to pass flags on every invocation. CLI flags always take precedence.
# svccat.toml
= "terminal" # default output format: "terminal" or "json"
= true # always exit 1 on drift (no need for --fail-on-drift)
= [
"services/examples",
"services/vendor/*",
"test-fixtures/*",
]
--ignore patterns
Exclude directories from drift detection on the fly:
Ignore patterns in svccat.toml and in discovery.ignore (manifest) are
merged with patterns from the CLI flag.
Shell completions
Generate tab-completion scripts for your shell:
# Bash (add to ~/.bashrc)
# Zsh (add to your $fpath)
# Fish
CI integration
svccat check --ping
Add url: to any service entry and pass --ping to verify each endpoint is
reachable at run time:
Example output:
svccat: 3 declared, 3 discovered [services.yaml]
OK No drift detected
Ping results:
✔ api-gateway https://gateway.example.com 200 OK
✔ auth-service https://auth.example.com 200 OK
✗ legacy-worker https://worker.example.com unreachable (connection refused)
depends_on graph edges
Declare explicit service dependencies and they are rendered as directed edges
in svccat graph:
services:
- name: api-gateway
depends_on:
- auth-service
- postgres
svccat graph
graph TD
subgraph Cloud_Run["Cloud Run"]
api_gateway["api-gateway\nGo\nreverse proxy"]
auth_service["auth-service\nRust\nJWT issuance"]
end
subgraph Cloud_SQL["Cloud SQL"]
postgres["postgres\nSQL\ndatabase"]
end
api_gateway --> auth_service
api_gateway --> postgres
GitHub Action
Use svccat in GitHub Actions without installing Rust first:
# .github/workflows/catalog.yml
name: Catalog check
on:
jobs:
catalog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rodmen07/svccat@v1
with:
fail-on-drift: 'true' # default — exits 1 on drift
Inputs:
| Input | Default | Description |
|---|---|---|
root |
. |
Path to repo root (where services.yaml lives) |
fail-on-drift |
true |
Exit 1 when drift is detected |
version |
latest |
svccat crates.io version to install |
The action caches the installed binary so subsequent runs skip the cargo install step.
Drift annotations + PR comments (v0.10.0)
For advanced workflows that emit GitHub Actions annotations and post drift summaries as PR comments, use the included workflow template:
The template runs svccat check with --format github-annotation, creates an annotated drift report, posts it as a PR comment (with automatic update on subsequent pushes), and gates the PR check on new drift.
Manual CI integration
Add a step to your pipeline to gate merges on zero drift:
# .github/workflows/catalog.yml
name: Catalog check
on:
jobs:
catalog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
- run: cargo install svccat
- run: svccat check --fail-on-drift
Exit codes:
0— no drift (or drift present but--fail-on-driftnot set)1— drift detected and--fail-on-driftis set2— fatal error (unreadable manifest, parse failure, etc.)
Example output
Terminal
svccat: 3 declared, 3 discovered [services.yaml]
OK No drift detected
svccat: 4 declared, 3 discovered [services.yaml]
DRIFT DETECTED (1 error, 2 warnings)
x [MISSING] 'legacy-worker' is declared in the manifest but not found in the repo
! [UNDECLARED] 'services/experimental-api' exists in the repo but is not listed in the manifest
! [FIELD] 'event-stream' is missing recommended field: platform
x 1 error(s)
! 2 warning(s)
Mermaid graph (svccat graph)
```mermaid
graph TD
subgraph Cloud_Run["Cloud Run"]
end
subgraph GitHub_Pages["GitHub Pages"]
end
```
svccat workspace: multi-repo drift
Check drift across several repositories in one pass. List the repos in a
[workspace] section of an svccat.toml at the workspace root:
# svccat.toml (workspace root)
[]
= [
{ name = "backend", path = "repo1", manifest = "services.yaml", enabled = true },
{ name = "frontend", path = "repo2", manifest = "services.yaml", enabled = true },
]
The report aggregates declared services, errors, and warnings per repository.
Cross-repo dependency analysis flags depends_on edges that dangle or form
cycles across repository boundaries.
Try the sample monorepo
Project status
Latest stable: v0.20.0 (2026-06-01): svccat audit --cost-estimate for monthly deployment-cost estimation by platform, performance benchmarking with criterion, and security integration tests.
In development: multi-repo workspaces (svccat workspace check) with aggregated cross-repo reporting and dependency analysis.
Notable recent releases:
v0.19.0: 10 critical and high-severity security fixes (git injection, SSRF, deserialization bombs, path traversal, symlink attacks, glob DoS, information disclosure, IPv6 detection), plus a SECURITY.md threat modelv0.18.0:svccat scorecard(per-service health scoring),svccat snapshot compare,svccat graph --format html(D3.js visualization),svccat ci --watch, webhook automationv0.17.0:svccat ci,svccat search,svccat snapshot diff,svccat depsdependency analysis,svccat tag add/removev0.16.0:svccat policyenforcement framework,svccat snapshotsave/list/deletev0.15.0:svccat audit(lint + drift + score in one pass),--pingreachability checksv0.14.0:svccat servelive HTML dashboard- Earlier: graph (Mermaid/Graphviz/PlantUML), report (Markdown/HTML), watch, import, init, drift detection, multiple output formats
Contributing
Bug reports and pull requests welcome.
Please run cargo clippy -- -D warnings and cargo fmt before opening a PR.
License
MIT — see LICENSE.