Skip to content

Installation

How to obtain the binaries, the container image, or the Helm chart.

Overview

Every tagged release (git tag vX.Y.Z, matching CI/CD's release.yml workflow) publishes five container images and one Helm chart to ghcr.io/joao00001 — see Option 3 and Option 4 below. Building from source (cloning the repo, or go install directly from the module proxy) still works exactly as before and is the only option for anything unreleased. This page covers obtaining pg-regression-radar; see Getting Started for actually running it once installed.

Prerequisites

  • Go 1.22+ (to build from source)
  • Docker (to build the container image)
  • A Postgres cluster with pg_stat_statements enabled — see Support Matrix for officially supported PostgreSQL versions (16, 17, 18) and distributions (community, CloudNativePG, EDB Postgres Advanced/Extended Server)
  • kubectl and/or helm (only for the Kubernetes-deployed paths)

Creating a least-privilege Postgres role

pg-regression-radar only needs to read pg_stat_statements and a handful of catalog views — it never writes to user data. Do not use a superuser or a role with broad privileges for --dsn. If the connection string is ever leaked (e.g. via a misrouted Secret), the blast radius should be limited to read-only statistics access.

Create a dedicated role before installing pg-regression-radar:

-- Create the role with login but no superuser privileges.
CREATE ROLE pgrr_monitor WITH LOGIN PASSWORD 'change-me';

-- Built-in role that grants read access to pg_stat_statements, pg_stat_*
-- views, and other monitoring functions — exactly what pg-regression-radar needs.
GRANT pg_monitor TO pgrr_monitor;

-- If pg_stat_statements is installed in a non-public schema, also grant USAGE.
-- GRANT USAGE ON SCHEMA extensions TO pgrr_monitor;

Using spec.capturePlans / --capture-plans? pg_monitor alone is not enough

pg_monitor grants read access to catalog/statistics views, but not SELECT on ordinary user tables. Plan-diff capture runs EXPLAIN (FORMAT JSON, GENERIC_PLAN) <query> against every tracked query, and PostgreSQL checks table-level SELECT privilege for every table an EXPLAIN'd statement references — even though EXPLAIN never executes the statement. Without it, capture fails per-query with pq: permission denied for table ... (42501), logged at DEBUG (so it's easy to miss), and planDiffSummary is left permanently at "no plan captured on either side of the regression" for any query touching a real table. (Queries with no table reference, like SELECT pg_sleep($1), are unaffected — this is why capture can appear to "work" in a quick smoke test and then silently fail on real workload queries.)

If you enable plan-diff capture, also grant read access to the tables you want plan diffs for:

-- Existing tables in the schema:
GRANT SELECT ON ALL TABLES IN SCHEMA public TO pgrr_monitor;

-- So newly created tables get it automatically too (adjust FOR ROLE to
-- whatever role actually creates your application's tables):
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public
  GRANT SELECT ON TABLES TO pgrr_monitor;

Then use that role in your DSN:

******host:5432/mydb?sslmode=require

Warning

Never pass a superuser or application-owner connection string. A leaked DSN with superuser access gives an attacker full control of the database; a pg_monitor DSN limits exposure to read-only statistics.

The simplest way to get pg-regression-radar: a single unified CLI, pg-regression-radar, with each run mode as a subcommand. No clone needed — go install resolves the tagged release directly from the repository's tags via the Go module proxy:

go install github.com/joao00001/pg-regression-radar/cmd/pg-regression-radar@latest
# or pin a release: @v0.1.0

This places a single pg-regression-radar binary in $(go env GOPATH)/bin. From there:

pg-regression-radar operator --dsn "postgres://user:pass@host:5432/dbname?sslmode=disable"
pg-regression-radar manager --leader-elect=true
pg-regression-radar collector --dsn "..."
pg-regression-radar ingester --source-type argocd
pg-regression-radar version
pg-regression-radar help

Note: The DSN values above are placeholders. Replace them with a full PostgreSQL connection URI of the form postgres://username:password@host:5432/dbname?sslmode=disable. Every example in this documentation that shows ****** in place of credentials is for documentation safety only and cannot be used as-is — supply your real connection string.

Every subcommand accepts its own --help, --version, and (except manager) --dry-run — see Configuration Reference.

If you'd rather have four separate binaries (e.g. to match the container images described in Option 3), the same code is also available as four standalone commands:

go install github.com/joao00001/pg-regression-radar/cmd/operator@latest
go install github.com/joao00001/pg-regression-radar/cmd/manager@latest
go install github.com/joao00001/pg-regression-radar/cmd/collector@latest
go install github.com/joao00001/pg-regression-radar/cmd/ingester@latest

Both forms run identical code (internal/cli) — pg-regression-radar operator ... and the standalone operator ... behave the same way.

Option 2: Build from source

git clone https://github.com/joao00001/pg-regression-radar.git
cd pg-regression-radar
go build -o bin/pg-regression-radar ./cmd/pg-regression-radar
# or the four standalone binaries individually:
go build -o bin/operator  ./cmd/operator
go build -o bin/manager   ./cmd/manager
go build -o bin/collector ./cmd/collector
go build -o bin/ingester  ./cmd/ingester

Useful if you want to build against an unreleased commit, or need the CRD manifests and Helm chart alongside the binaries (both live in the same clone — see options 3 and 4).

Option 3: Pull or build the container image

Pull a published release (every vX.Y.Z tag publishes all five images — see CI/CD):

docker pull ghcr.io/joao00001/pg-regression-radar/operator:v0.3.0
docker pull ghcr.io/joao00001/pg-regression-radar/manager:v0.3.0
docker pull ghcr.io/joao00001/pg-regression-radar/collector:v0.3.0
docker pull ghcr.io/joao00001/pg-regression-radar/ingester:v0.3.0
docker pull ghcr.io/joao00001/pg-regression-radar/cli:v0.3.0

Substitute the actual latest tag from the Releases page, or use :latest for the most recent release (there is no rolling/"edge" image — every published tag corresponds to a real vX.Y.Z release). docker run ghcr.io/joao00001/pg-regression-radar/cli:v0.3.0 operator --dsn ... runs the unified CLI image; the other four are single-mode images matching the Dockerfile's other --target values.

Verify the signature and SBOM before trusting a pulled image. Every published image is keyless-signed with cosign using the publishing GitHub Actions run's own OIDC identity (no shared private key to leak or rotate), and carries an attached SBOM attestation:

# Verify the image was signed by this repo's release.yml workflow, specifically
# (not just "signed by someone with a GitHub account").
cosign verify ghcr.io/joao00001/pg-regression-radar/operator:v0.3.0 \
  --certificate-identity-regexp "^https://github.com/joao00001/pg-regression-radar/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

# Fetch and inspect the attached SPDX SBOM attestation.
cosign verify-attestation ghcr.io/joao00001/pg-regression-radar/operator:v0.3.0 \
  --type spdxjson \
  --certificate-identity-regexp "^https://github.com/joao00001/pg-regression-radar/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  | jq -r '.payload | @base64d | fromjson | .predicate'

Every image is also scanned with Trivy at release time and the release job fails outright on any CRITICAL-severity finding (see CI/CD) — a published image never carries a known CRITICAL CVE as of its release date, though new CVEs can of course be disclosed later against the same fixed image bytes.

Build locally, e.g. to test an unreleased commit:

git clone https://github.com/joao00001/pg-regression-radar.git
cd pg-regression-radar
docker build --target operator -t pg-regression-radar/operator .
docker build --target manager  -t pg-regression-radar/manager  .

See the Dockerfile for the other --target values (collector, ingester, and cli for an image running the unified pg-regression-radar binary).

Option 4: Install the Helm chart

From the published OCI chart (published alongside the images by every release — see CI/CD):

helm install pg-regression-radar oci://ghcr.io/joao00001/charts/pg-regression-radar \
  --version 0.3.0 \
  --set postgres.dsn="postgres://user:pass@cnpg-cluster-rw.production:5432/mydb?sslmode=disable" \
  --set postgres.clusterName=cnpg-cluster \
  --set alerting.slackWebhookUrl=https://hooks.slack.com/services/XXX/YYY/ZZZ

Note: The postgres.dsn value above is a placeholder. Replace it with a full PostgreSQL connection URI, for example postgres://username:password@cnpg-cluster-rw.production:5432/mydb?sslmode=disable. Redacted DSN examples are shown for documentation safety only and cannot be used as-is.

--version here is the chart's SemVer version (the release tag without its leading v — release v0.3.0 publishes chart version 0.3.0; see CI/CD for why the two differ by that one character). No helm repo add is needed for an OCI registry — helm install/helm pull reference oci:// URLs directly. The chart's image.tag values default to the chart's own appVersion (the release tag with the v, e.g. v0.3.0), which already matches the images published in the same release, so you don't need to set image.tag/manager.image.tag explicitly unless you want to run a different image tag than the chart's own release.

The chart also exposes alert destination-policy controls used by both entrypoints:

  • alerting.destinationPolicy: "" (default permissive), allowlist, or relay-only
  • alerting.allowedDestinations: manager/operator allowlist used by allowlist policy
  • alerting.destinationPolicyRelayUrl: required when policy is relay-only

Example (mode=manager, force all alerts through a relay):

helm upgrade --install pg-regression-radar oci://ghcr.io/joao00001/charts/pg-regression-radar \
  --version 0.3.0 \
  --set mode=manager \
  --set alerting.destinationPolicy=relay-only \
  --set alerting.destinationPolicyRelayUrl=https://relay.example.com/webhook

Verify the chart's signature before trusting it, the same way as the container images above — the published Helm OCI artifact is keyless-signed with cosign too, using the chart's SemVer version as the tag (same one --version above installs):

cosign verify ghcr.io/joao00001/charts/pg-regression-radar:0.3.0 \
  --certificate-identity-regexp "^https://github.com/joao00001/pg-regression-radar/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

From a local clone, e.g. to test unreleased chart changes:

git clone https://github.com/joao00001/pg-regression-radar.git
helm install pg-regression-radar ./pg-regression-radar/deploy/helm/deploylens \
  --set postgres.dsn="postgres://user:pass@cnpg-cluster-rw.production:5432/mydb?sslmode=disable" \
  --set postgres.clusterName=cnpg-cluster \
  --set alerting.slackWebhookUrl=https://hooks.slack.com/services/XXX/YYY/ZZZ

See Getting Started for the full set of values, including switching to manager mode.

Pod/container security defaults

podSecurityContext/securityContext default to runAsNonRoot: true (UID/GID 65532), allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [ALL], and seccompProfile: RuntimeDefault — matching how every image this chart deploys already runs regardless (all five binaries build on gcr.io/distroless/static-debian12:nonroot, which sets USER nonroot:nonroot and has no writable-filesystem tooling to begin with), just enforced by Kubernetes instead of merely true by accident. None of the binaries write to the local filesystem, so readOnlyRootFilesystem costs nothing. Override either value back to {} only if your environment's own admission policy conflicts with these fields.

serviceAccount.automountServiceAccountToken defaults to true for backward compatibility. Keep it true in mode: manager (the manager talks to the Kubernetes API for leader election and reconciliation). In mode: operator, you can safely set it to false when you do not need in-cluster API access.

If ingester.webhookSecret is left empty, helm install/helm upgrade prints a warning that the deploy-event webhook (and, in mode: operator, GET /events) are unauthenticated — see Deploy Sources & Webhooks: Webhook authentication. That's expected and fine as long as the Service stays cluster-internal (the default service.type is ClusterIP); set the secret before exposing it further.

See also

  • Getting Started — running each of these once installed.
  • Configuration Reference — every flag and CRD field.
  • Support Matrix — officially supported PostgreSQL versions and distributions.
  • CI/CD — how release.yml builds, scans, signs, and publishes every image and the Helm chart.
  • Roadmap — remaining operational follow-ups.