Deploying
Run Cerbos next to each service, ship decision logs, migrate the access schema, keep caches fresh, and upgrade safely.
Cerbos as a sidecar
Each service instance gets its own Cerbos PDP on localhost: a decision costs a loopback hop, and a Cerbos outage only affects its own instance. Cerbos is stateless; it reads the generated policies and nothing else.
server:
httpListenAddr: ":3592"
storage:
driver: disk
disk:
directory: /policies
watchForChanges: true
schema:
enforcement: reject
audit:
enabled: true
accessLogsEnabled: false
decisionLogsEnabled: true
backend: file
file:
path: stdout- Policies: ship
out.policies(including_schemas/andorgs/) with the service image, or mount them read-only, so a service and the policies it was built with always deploy together. - Schema enforcement
reject: a check with missing or extra attributes is denied, never evaluated against partial data. - Pin the version zynactra/access tests against:
ghcr.io/cerbos/cerbos:0.57.0. - Leave
engine.lenientScopeSearchoff, so a missing org rule policy denies instead of falling back to the product policy. - Readiness: gate the service's readiness on the sidecar with
CerbosHTTP.Healthy(ctx)(Go) orcerbos.healthy()(TypeScript), which call/_cerbos/health.
On Kubernetes this is a second container in the pod; with Docker Compose, a second service (see the examples' docker-compose.yml).
Decision logs
With the config above, Cerbos writes every check and list plan to stdout as JSON: who asked, about what, the effect, the org scope, and the outputs naming the scope that allowed it or the rule that denied it. Ship them to your log pipeline: they answer "who looked at what" for audits. Checks send only the permissions they decide, so entries don't carry a person's whole access.
The database
The access schema lives in the product's own Postgres. Apply its migrations before the service starts serving: migrate.Apply(ctx, db) / migrate(pool) at startup, or zaccess migrate as a deploy step. Migrations are append-only and safe to run on every start.
Then sync the catalog: SyncCatalog / syncCatalog at startup (or zaccess sync) records the manifest's permissions and system roles. zaccess sync -org <id> also seeds the system roles into an org.
The service's database user needs to read and write the access tables and LISTEN; it never needs to alter the change log, which the database refuses anyway.
Cache invalidation
Run the NOTIFY listener on every instance: pqlisten.Listen(ctx, dsn, access, log) (Go) or listen(databaseUrl, access) (TypeScript). It holds one connection and drops cached grants when an admin change commits. If the connection drops, the whole cache is cleared and the listener reconnects. With a connection pooler in transaction mode, point the listener at the database directly: LISTEN needs a session.
Timeouts and failure
Each Cerbos call has a 250 ms timeout and one retry. A failure denies (reason unavailable, 503 from the access API); there is no allow fallback. Watch the rate of unavailable denials, and alert on it.
Upgrading
- zynactra/access: the four npm packages and the Go module share a version. Upgrade them together, run
zaccess genwith the matching CLI, and read the changelog: a major version says what to change. - Manifests: deploy generated policies together with the code that uses them. Removing a permission is a two-step change if old instances are still serving: stop using it in code first, remove it from the manifest in a later deploy.
- Cerbos: upgrade the pinned version only when zynactra/access does.