Tutorial
Deploy on Kubernetes (Helm)
Helm chart, health probes, scaling, and production deployment patterns.
The Helm chart is the primary way to deploy pgagroal to Kubernetes. It runs pgagroal as a standalone Deployment with a ClusterIP Service and ships production-ready defaults for security contexts, resource limits, probes, and pod disruption budgets. If you prefer plain manifests instead, the raw-manifest example below covers that path.
Install with Helm
helm install pgagroal helm/pgagroal/ \
--set postgresql.host=your-postgres-service \
--set credentials.username=app \
--set credentials.password=secret \
-n pgagroal --create-namespaceReplace your-postgres-service with the Kubernetes Service name or hostname of your PostgreSQL backend.
Minimal production values
Most deployments need only a few overrides. The chart ships with production-ready defaults for security contexts, resource limits, probes, and pod disruption budgets.
# values-production.yaml
replicaCount: 2
image:
repository: elevarq/pgagroal
tag: "0.2.0"
postgresql:
host: "pg-primary.database.svc.cluster.local"
port: 5432
pgagroal:
maxConnections: 50
logLevel: warn
credentials:
existingSecret: "pgagroal-credentials"
service:
type: ClusterIP
port: 6432Store credentials in a Kubernetes Secret, not in values files. The chart reads PG_USERNAME and PG_PASSWORD from the Secret named in credentials.existingSecret.
Health probes
The chart configures liveness and readiness probes using the same command as the container's built-in health check:
pgagroal-cli -c /etc/pgagroal/pgagroal.conf pingThis checks that the pgagroal daemon is running and responsive. It does not verify backend connectivity — a healthy pooler with an unreachable backend will still pass the probe. This is intentional: the pooler should stay running so it can recover when the backend returns.
| Probe | Delay | Interval | Failure threshold |
|---|---|---|---|
| Liveness | 5s | 10s | 3 (restart after 30s of failure) |
| Readiness | 3s | 5s | 2 (stop traffic after 10s of failure) |
Security context
The chart enforces a hardened security posture by default. These settings are applied out of the box — you do not need to configure them.
- Non-root — runs as UID/GID 1000
- No privilege escalation —
allowPrivilegeEscalation: false - All capabilities dropped —
capabilities.drop: [ALL] - Read-only root filesystem — writable paths use emptyDir volumes
- Seccomp —
RuntimeDefaultprofile
Scaling
The chart defaults to 2 replicas with a PodDisruptionBudget of minAvailable: 1. This ensures at least one pooler remains available during rolling updates and node drains.
Replica count
Each replica maintains its own connection pool to the backend. Two replicas with maxConnections: 50each means up to 100 total backend connections. Plan your replica count and pool size together so the total does not exceed PostgreSQL's max_connections.
Resource limits
The chart defaults to 100m CPU request / 1 CPU limit and 64Mi memory request / 256Mi limit. pgagroal is lightweight — these defaults are generous for most workloads. Increase CPU if you run more than 100 pooled connections per replica.
Common patterns
Sidecar vs dedicated deployment
The Helm chart deploys pgagroal as a standalone Deployment with its own Service. This is the recommended pattern — it lets multiple application Deployments share one pool and makes scaling and monitoring independent.
A sidecar pattern (pooler in each application pod) is possible but rarely useful. It defeats the purpose of connection pooling because each pod maintains a separate pool that cannot share connections.
Connecting applications
Point your application's database connection string at the pgagroal Service:
postgresql://app:secret@pgagroal.pgagroal.svc.cluster.local:6432/appdbThe Service name and namespace depend on your Helm release name and -n flag.
Apply raw manifests
As an alternative to Helm, you can deploy pgagroal as a standalone Kubernetes Deployment with a ClusterIP Service using plain manifests. This assumes PostgreSQL is already running in the cluster or reachable from it.
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgagroal
namespace: pgagroal
spec:
replicas: 2
selector:
matchLabels:
app: pgagroal
template:
metadata:
labels:
app: pgagroal
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: pgagroal
image: elevarq/pgagroal:1.1.0
ports:
- containerPort: 6432
name: pooler
env:
- name: PG_BACKEND_HOST
value: "postgres.database.svc.cluster.local"
- name: PG_BACKEND_PORT
value: "5432"
- name: MAX_CONNECTIONS
value: "50"
- name: PGAGROAL_LOG_LEVEL
value: "warn"
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
readinessProbe:
exec:
command:
- pgagroal-cli
- "-c"
- /etc/pgagroal/pgagroal.conf
- ping
initialDelaySeconds: 3
periodSeconds: 5
failureThreshold: 2
livenessProbe:
exec:
command:
- pgagroal-cli
- "-c"
- /etc/pgagroal/pgagroal.conf
- ping
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: "1"
memory: 256MiService
apiVersion: v1
kind: Service
metadata:
name: pgagroal
namespace: pgagroal
spec:
type: ClusterIP
selector:
app: pgagroal
ports:
- port: 6432
targetPort: pooler
protocol: TCP
name: poolerApply and verify
# Create namespace and apply
kubectl create namespace pgagroal
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Wait for pods to be ready
kubectl -n pgagroal rollout status deployment/pgagroal
# Verify from inside the cluster
kubectl -n pgagroal run test --rm -it --image=postgres:17 -- \
psql -h pgagroal.pgagroal.svc.cluster.local -p 6432 \
-U app -d appdb -c 'SELECT 1'Applications connect to pgagroal.pgagroal.svc.cluster.local:6432. Replace with your namespace if different.
Scaling and connection limits
Each replica maintains its own pool. Two replicas with MAX_CONNECTIONS=50 opens up to 100 total backend connections.
Make sure the total across all replicas does not exceed PostgreSQL's max_connections, leaving room for admin and monitoring connections.
# Scale to 3 replicas (3 x 50 = 150 backend connections)
kubectl -n pgagroal scale deployment/pgagroal --replicas=3Common adjustments
| Change | How |
|---|---|
| Backend address | Change PG_BACKEND_HOST to your PostgreSQL Service or RDS endpoint |
| Pool size | Change MAX_CONNECTIONS and verify total across replicas |
| Credentials | Add PG_USERNAME and PG_PASSWORD from a Secret via envFrom |
| Resource limits | Increase CPU if running >100 connections per replica |
| Expose externally | Change Service type to LoadBalancer — but prefer ClusterIP with application-level access |
See also: Configuration for environment variables and pool sizing, or Docker Compose for local development.