Skip to main content
pgAgroal Enterprise docs · Install with Helm

pgAgroal Enterprise · Tutorial

Install with Helm

Install the Kubernetes operator, create your first Pgagroal resource, and confirm the managed pooler pod is running and hardened.

The pgagroal-enterprise Helm chart installs the Elevarq pgAgroal Enterprise control layer: the Pgagroal custom resource definition (CRD), the Kubernetes operator (RBAC plus a hardened deployment), and an optional fleet control plane. The chart deploys the operator — you create poolers by applying Pgagroal resources, which the operator reconciles into standard, unmodified upstream pgagroal pods. For background, see the pgAgroal Enterprise overview.

New to connection pooling? Start with what a connection pooler is, and why Postgres needs one — it explains the terms used here for readers who know Kubernetes but not PostgreSQL. Unfamiliar words are defined in the glossary.

Prerequisites

  • A Kubernetes cluster and kubectl configured against it, with permission to create CRDs, ClusterRoles, and namespaces.
  • helm v3 installed locally.
  • A reachable PostgreSQL backend (a Service hostname or external endpoint) for the pooler to front.
  • A released operator image version to pin — production installs should pin by tag or, ideally, by digest.

Get the chart

The chart is published to an OCI Helm registry and is cosign-signed on release, so you install it directly with helm— no Git checkout is required. The CRD ships inside the chart’s crds/ directory and installs automatically with the release; you do not apply it separately.

Installing from source instead? If you have the repository checked out, the chart also lives at charts/pgagroal-enterprise — substitute that local path for the oci://… reference in the commands below.

Choose your image registry

The operator and pooler images come from a different registry depending on how you obtained the product, because AWS Marketplace entitlement resolves images through a Marketplace-managed registry rather than the public one. The registry is a Helm value, so the same chart works either way:

  • Direct / open users ghcr.io/elevarq. These Enterprise images and the chart are private: you authenticate to GHCR with a GitHub token that has read:packages, and the pods pull them with an image pull secret (next section).
  • AWS Marketplace customers — the Marketplace-managed ECR path tied to your subscription: TODO(frank): exact Marketplace ECR registry string.

The examples below use the direct GHCR registry. Marketplace customers replace the registry portion of operator.image.repository and the pooler image with their entitled ECR path (and authenticate to ECR instead of GHCR).

Authenticate to the registry

The chart and images are private, so log in to GHCR for Helm and create a pull secret the pods use to pull the images. Use a GitHub token (classic, scope read:packages; or a fine-grained token with package read) as the password:

# Helm (pulls the chart)
helm registry login ghcr.io -u <github-username> -p <token>

# Pull secret in the install namespace (the pods use it to pull images)
kubectl create namespace pgagroal-system
kubectl create secret docker-registry ghcr-pull \
  --docker-server=ghcr.io \
  --docker-username=<github-username> \
  --docker-password=<token> \
  --namespace pgagroal-system
The pull secret name (ghcr-pull here) is referenced from the Helm install (imagePullSecrets) and from the Pgagroal resource (spec.imagePullSecrets) below. Marketplace customers create an ECR pull secret instead.

Install the operator

Install the published chart into a dedicated namespace, pinning the operator image to a released version. This command needs only helm and a kubeconfig — no repository checkout:

helm install pgagroal-enterprise \
  oci://ghcr.io/elevarq/charts/pgagroal-enterprise --version <released-version> \
  --namespace pgagroal-system \
  --set operator.image.repository=ghcr.io/elevarq/pgagroal-enterprise-operator \
  --set operator.image.tag=<released-version> \
  --set 'imagePullSecrets[0].name=ghcr-pull'
Replace <released-version> with the latest release tag without the leading v (for example 1.0.0-beta.5) — see the releases page. The single-quotes around the imagePullSecrets[0].name set are needed so zsh does not glob the brackets.

This deploys the operator (one replica by default), creates its ServiceAccount, and installs the ClusterRole and binding it needs to reconcile Pgagroal resources cluster-wide. Pin a released tag — or, ideally, a digest — in production rather than tracking a moving tag.

Provide exporter and database credentials through Kubernetes Secrets, never in values.yaml. The chart is built for that pattern.
Upgrades and the CRD. The chart installs the Pgagroal CRD from crds/ on first install, but Helm does not upgrade CRDs on helm upgrade — so a later chart can add fields the live CRD schema still rejects. After upgrading the chart, apply the new CRD explicitly:
helm pull oci://ghcr.io/elevarq/charts/pgagroal-enterprise \
  --version <released-version> --untar -d /tmp/pgae
kubectl apply -f /tmp/pgae/pgagroal-enterprise/crds/pgagroals.yaml
GitOps tools should manage the CRD with a replace/apply strategy (Argo CD Replace=true, Flux force) for the same reason.

Optional: the fleet control plane

The chart can also deploy the fleet control plane alongside the operator. It is off by default because it needs a PostgreSQL DSN and operator tokens, which you supply through existing Secrets. Enable it only when you are ready to wire those in:

helm install pgagroal-enterprise \
  oci://ghcr.io/elevarq/charts/pgagroal-enterprise --version <released-version> \
  --namespace pgagroal-system \
  --set operator.image.repository=ghcr.io/elevarq/pgagroal-enterprise-operator \
  --set operator.image.tag=<released-version> \
  --set 'imagePullSecrets[0].name=ghcr-pull' \
  --set controlPlane.enabled=true

Reference the control plane’s DSN and token Secrets via controlPlane.env. For the full list of tunables, see the Helm values reference.

Create a Pgagroal resource

With the operator running, create a pooler by applying a Pgagroal custom resource. Point backendHost at your PostgreSQL Service:

apiVersion: pgagroal.elevarq.com/v1alpha1
kind: Pgagroal
metadata:
  name: pg1
spec:
  image: ghcr.io/elevarq/pgagroal-enterprise:<released-version>
  backendHost: my-postgres.default.svc.cluster.local
  replicas: 3
  # the managed pooler pods pull the private image with this secret
  imagePullSecrets:
    - ghcr-pull
The pooler image: uses the same registry choice as the operator. Marketplace customers set it to their entitled ECR path (see Choose your image registry).

Save this as pg1.yaml and apply it:

kubectl apply -f pg1.yaml

The operator reconciles the resource into a managed pooler deployment. It hardens every managed pod — non-root, dropped capabilities, seccomp, and resource limits — and creates a PodDisruptionBudget plus an ingress NetworkPolicy.

Verify the pooler is running and hardened

Confirm the Pgagroal resource and the pods the operator created for it:

# The custom resource the operator is reconciling
kubectl get pgagroal pg1

# The managed pooler pods
kubectl get pods -l app.kubernetes.io/instance=pg1

Check that the operator applied the hardened pod settings. The managed pods run with these defaults:

  • Non-root runAsNonRoot: true
  • No privilege escalation allowPrivilegeEscalation: false
  • All capabilities dropped capabilities.drop: [ALL]
  • Read-only root filesystem readOnlyRootFilesystem: true
  • Seccomp RuntimeDefault profile

Inspect a managed pod’s security context to confirm:

kubectl get pod -l app.kubernetes.io/instance=pg1 \
  -o jsonpath='{.items[0].spec.securityContext}{"\n"}'

Connect through the pooler and run a query

Now see the payoff: send real traffic. Applications connect to the operator-created Service named after the resource (pg1) on the pooler port 6432 — not to PostgreSQL on 5432 directly. The in-cluster connection target is pg1.<namespace>.svc.cluster.local:6432; the pooler forwards to the backend you set in backendHost.

Run a one-shot psql pod in the same namespace as pg1 and execute a trivial query (use a database role that exists on your backend):

kubectl run psql-check --rm -it --restart=Never \
  --image=postgres:17-alpine \
  --env="PGPASSWORD=<password>" -- \
  psql "host=pg1 port=6432 user=<user> dbname=<db>" -c 'SELECT version();'

On success you see the backend’s version row, returned through the pooler:

                                          version
--------------------------------------------------------------------------------
 PostgreSQL 17.4 on x86_64-pc-linux-gnu, compiled by gcc ...
(1 row)
pod "psql-check" deleted
Most common failure: the connection hangs or returns a backend error. That is almost always the pooler-to-backend hop — an unreachable backendHost, or credentials the backend rejects. Look at the managed pod’s logs, which name the backend it tried to reach:
kubectl logs -l app.kubernetes.io/instance=pg1 --tail=50

Verify the chart supply chain

The chart is helm lint and helm template validated in CI and cosign-signed on release. Verify the signature with cosign verify before installing in environments where provenance matters — this supports your audit and compliance readiness.

Next steps

To manage Pgagroal resources declaratively from a Git repository, follow the GitOps tutorial. For the full set of chart values, see the Helm values reference. For pgagroal’s own configuration and operation, see the pgagroal manual.