Declarative PostgreSQL access control. Define roles, grants, and memberships in YAML — pgroles diffs against your live database and generates the exact SQL to converge it.
Anything not in the manifest gets revoked or dropped. Same model as Terraform, applied to PostgreSQL.
Define a policy. Profiles are reusable privilege templates that expand across schemas:
profiles:
editor:
grants:
- privileges: [USAGE]
on: { type: schema }
- privileges: [SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER]
on: { type: table, name: "*" }
- privileges: [USAGE, SELECT, UPDATE]
on: { type: sequence, name: "*" }
- privileges: [EXECUTE]
on: { type: function, name: "*" }
default_privileges:
- privileges: [SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER]
on_type: table
- privileges: [USAGE, SELECT, UPDATE]
on_type: sequence
- privileges: [EXECUTE]
on_type: function
viewer:
grants:
- privileges: [USAGE]
on: { type: schema }
- privileges: [SELECT]
on: { type: table, name: "*" }
schemas:
- name: inventory
profiles: [editor, viewer]
- name: catalog
profiles: [viewer]
roles:
- name: app-service
login: true
memberships:
- role: inventory-editor
members:
- name: app-serviceThis generates roles inventory-editor, inventory-viewer, and catalog-viewer, each scoped to their schema. app-service gets inventory-editor membership.
Run pgroles diff to see exactly what SQL will be executed:
CREATE ROLE "inventory-editor"
NOLOGIN NOSUPERUSER INHERIT;
GRANT USAGE ON SCHEMA "inventory"
TO "inventory-editor";
GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER
ON ALL TABLES IN SCHEMA "inventory"
TO "inventory-editor";
GRANT USAGE, SELECT, UPDATE
ON ALL SEQUENCES IN SCHEMA "inventory"
TO "inventory-editor";
GRANT EXECUTE
ON ALL FUNCTIONS IN SCHEMA "inventory"
TO "inventory-editor";
-- Roles removed from the manifest get cleaned up:
REVOKE ALL ON SCHEMA "legacy"
FROM "old-reader";
DROP ROLE "old-reader";Then pgroles apply to execute it.
# Already have a database with roles? Generate a manifest from it:
pgroles generate --database-url postgres://... > pgroles.yaml
# See what SQL pgroles would run:
pgroles diff -f pgroles.yaml --database-url postgres://...
# Apply the changes:
pgroles apply -f pgroles.yaml --database-url postgres://...--database-url can also be set via the DATABASE_URL environment variable.
Pre-built binaries from GitHub Releases (Linux x86_64/aarch64, macOS x86_64/aarch64).
Cargo CLI:
cargo install pgroles-cliRust crates:
pgroles-cli— end-user CLIpgroles-core— manifest model, diff engine, SQL renderingpgroles-inspect— database inspection and managed-provider detectionpgroles-operator— Kubernetes operator crate, controller runtime, and CRD types
Docker:
docker run --rm ghcr.io/hardbyte/pgroles --help- Convergent — the manifest is the desired state. Missing roles get created, extra roles get dropped, drifted grants get fixed.
- Profiles — define privilege templates once, apply them across schemas. Each
schema x profilepair becomes a role. - Safer privilege bundles — common application profiles can pair table, sequence, and function privileges so identity columns and trigger-driven routines are covered together.
- Brownfield adoption —
pgroles generateintrospects an existing database and produces a manifest you can refine. - Drift detection —
pgroles diff --exit-codereturns exit code 2 on drift, designed for CI gates. - Safe role removal — preflight checks for owned objects, active sessions, and dependencies before dropping roles. Explicit
retirementsdeclare cleanup steps. - Managed PostgreSQL — works with RDS, Aurora, Cloud SQL, AlloyDB, and Azure Database for PostgreSQL. Detects provider-specific reserved roles and warns about privilege limitations.
- Kubernetes operator — reconcile
PostgresPolicycustom resources continuously. Install via Helm:Usehelm install pgroles-operator oci://ghcr.io/hardbyte/charts/pgroles-operator
spec.mode: planto inspect drift without executing SQL.
Full documentation is published at hardbyte.github.io/pgroles.
MIT