Skip to content

PostgreSQL

Production-ready PostgreSQL deployment with support for standalone and streaming replication architectures.

Key Features

  • Standalone and replication — Single instance or primary with streaming replicas
  • Automatic initialization — Init scripts, extensions, and custom configuration
  • S3 backup — Scheduled backups to S3-compatible storage via CronJob
  • Metrics — Prometheus exporter with ServiceMonitor
  • Security — Non-root containers, network policies, TLS support
  • Persistent storage — Configurable PVCs with storage class selection

Architecture

Standalone

Single PostgreSQL instance with persistent storage and optional S3 backup.

Application client TCP:5432 PostgreSQL StatefulSet (1 pod) primary PVC (data) Backup CronJob pg_dump → S3

Replication

Primary with streaming replicas, read-only service, and backup CronJob.

Application read/write Primary read + write StatefulSet pod-0 PVC (data) WAL Replica read-only StatefulSet pod-1 PVC (data) Read Client read-only svc-read load-balanced Backup CronJob pg_dump → S3

Installation

HTTPS repository:

helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install my-pg helmforge/postgresql

OCI registry:

helm install my-pg oci://ghcr.io/helmforgedev/helm/postgresql

Deployment Examples

# values.yaml
architecture: standalone

auth:
  postgresPassword: 'my-secret-password'
  database: myapp
  username: myuser
  password: 'user-password'

standalone:
  persistence:
    enabled: true
    size: 20Gi

metrics:
  enabled: true
  serviceMonitor:
    enabled: true
# values.yaml
architecture: replication

auth:
  postgresPassword: 'my-secret-password'
  database: myapp
  username: myuser
  password: 'user-password'
  replicationPassword: 'repl-password'

replication:
  primary:
    persistence:
      enabled: true
      size: 20Gi
  readReplicas:
    replicaCount: 2
    persistence:
      enabled: true
      size: 20Gi
# values.yaml
architecture: standalone

auth:
  postgresPassword: 'my-secret-password'
  database: myapp

standalone:
  persistence:
    enabled: true
    size: 20Gi

backup:
  enabled: true
  schedule: '0 3 * * *'
  s3:
    endpoint: https://s3.amazonaws.com
    bucket: my-pg-backups
    accessKey: '<set-me>'
    secretKey: '<set-me>'
# values.yaml
architecture: standalone

auth:
  postgresPassword: 'my-secret-password'
  database: myapp

standalone:
  persistence:
    enabled: true
    size: 20Gi

tls:
  enabled: true
  existingSecret: postgresql-tls
  sslMode: require

Operational Notes

  • The chart keeps internal health checks, metrics, and built-in backup on PostgreSQL’s standard postgres database.
  • If a reused PVC contains a valid data directory but is missing the default postgres database, the primary pod repairs that database during startup before normal readiness checks rely on it.
  • auth.database remains the application bootstrap database. It is created only on first initialization of a fresh data directory.
  • docker-entrypoint-initdb.d scripts only run when the data directory is empty. Existing PVCs keep their current roles and databases during upgrades.

Dual-stack Networking

PostgreSQL Services accept Kubernetes dual-stack configuration. By default, both service.ipFamilyPolicy and service.ipFamilies are unset and the chart inherits whatever the cluster advertises (matching prior behavior). Setting them propagates to every chart-managed Service: client, primary, replicas, metrics, and the headless StatefulSet services.

service:
  ipFamilyPolicy: PreferDualStack
  ipFamilies:
    - IPv4
    - IPv6

PreferDualStack is the safer choice for clusters that may be single- or dual-stack: omit ipFamilies and the cluster auto-populates whatever it supports. Set ipFamilies explicitly only when the cluster is configured for both families — the Kubernetes API rejects an explicit family the cluster does not advertise. RequireDualStack enforces both.

Configuration Reference

This reference covers the complete values.yaml surface for the PostgreSQL chart.

Core

ParameterTypeDefaultDescription
architecturestringstandaloneDeployment architecture: standalone or replication.
nameOverridestring""Override the chart name.
fullnameOverridestring""Override the full release name.
commonLabelsobject{}Extra labels added to all rendered resources.
clusterDomainstringcluster.localKubernetes cluster domain used in generated DNS names.

Image

ParameterTypeDefaultDescription
image.repositorystringdocker.io/library/postgresPostgreSQL runtime image repository.
image.tagstring"18.3-trixie"PostgreSQL runtime image tag.
image.pullPolicystringIfNotPresentImage pull policy for PostgreSQL containers.
imagePullSecretsarray[]Optional image pull secrets for private registries.

Authentication

ParameterTypeDefaultDescription
auth.postgresPasswordstring""PostgreSQL superuser password when not using an existing secret.
auth.databasestringappApplication database created on first bootstrap.
auth.usernamestringappApplication username created on first bootstrap.
auth.passwordstring""Application user password when not using an existing secret.
auth.replicationUsernamestringreplicatorReplication username used by read replicas.
auth.replicationPasswordstring""Replication password when not using an existing secret.
auth.existingSecretstring""Existing secret containing PostgreSQL passwords.
auth.existingSecretPostgresPasswordKeystringpostgres-passwordSecret key holding the PostgreSQL superuser password.
auth.existingSecretUserPasswordKeystringuser-passwordSecret key holding the application user password.
auth.existingSecretReplicationPasswordKeystringreplication-passwordSecret key holding the replication user password.

PostgreSQL Configuration

ParameterTypeDefaultDescription
config.presetstringnoneOptional PostgreSQL tuning preset: none, small, medium, or large.
config.postgresqlstring""Raw postgresql.conf content appended after generated settings.
config.pgHbaEntriesarray[]Structured pg_hba.conf entries appended after generated defaults.
config.pgHbastring""Raw pg_hba.conf content appended after generated rules.

TLS

ParameterTypeDefaultDescription
tls.enabledbooleanfalseEnable TLS for PostgreSQL and internal clients.
tls.existingSecretstring""Existing secret containing server TLS material.
tls.certFilenamestringtls.crtCertificate filename inside the TLS secret.
tls.keyFilenamestringtls.keyPrivate key filename inside the TLS secret.
tls.caFilenamestringca.crtCA certificate filename inside the TLS secret.
tls.sslModestringrequirelibpq SSL mode used by internal probes, replication, and exporter.
tls.minProtocolVersionstringTLSv1.2Minimum accepted PostgreSQL TLS protocol version.

Initialization

ParameterTypeDefaultDescription
initdb.scriptsobject{}Additional scripts written into docker-entrypoint-initdb.d.
initdb.existingConfigMapstring""Existing ConfigMap mounted into docker-entrypoint-initdb.d.

Standalone

ParameterTypeDefaultDescription
standalone.resourcesPresetstringnoneOptional resource preset for standalone mode.
standalone.persistence.enabledbooleantrueEnable a PVC for standalone mode.
standalone.persistence.storageClassstring""StorageClass for the standalone PVC.
standalone.persistence.accessModesarray["ReadWriteOnce"]Access modes for the standalone PVC.
standalone.persistence.sizestring8GiPVC size for standalone mode.
standalone.resourcesobject{}Explicit resource requests and limits for standalone mode.

Replication - Primary

ParameterTypeDefaultDescription
replication.primary.resourcesPresetstringnoneOptional resource preset for the primary pod.
replication.primary.persistence.enabledbooleantrueEnable PVCs for the primary StatefulSet.
replication.primary.persistence.storageClassstring""StorageClass for primary PVCs.
replication.primary.persistence.accessModesarray["ReadWriteOnce"]Access modes for primary PVCs.
replication.primary.persistence.sizestring20GiPVC size for the primary pod.
replication.primary.resourcesobject{}Explicit resource requests and limits for the primary pod.
replication.primary.probes.requireWritablebooleantrueIn replication mode, require primary readiness to confirm the pod is not in recovery mode.

Replication - Read Replicas

ParameterTypeDefaultDescription
replication.readReplicas.resourcesPresetstringnoneOptional resource preset for replica pods.
replication.readReplicas.replicaCountinteger2Number of asynchronous read replica pods.
replication.readReplicas.persistence.enabledbooleantrueEnable PVCs for replica StatefulSets.
replication.readReplicas.persistence.storageClassstring""StorageClass for replica PVCs.
replication.readReplicas.persistence.accessModesarray["ReadWriteOnce"]Access modes for replica PVCs.
replication.readReplicas.persistence.sizestring20GiPVC size for each replica.
replication.readReplicas.resourcesobject{}Explicit resource requests and limits for replica pods.
replication.readReplicas.probes.requireRecoveryModebooleantrueIn replication mode, require replica readiness to confirm the pod is in recovery mode.

Replication - WAL, PDB, and Scheduling

ParameterTypeDefaultDescription
replication.wal.keepSizestring512MBWAL retention kept locally for replicas and recovery workflows.
replication.wal.maxSendersinteger10Maximum WAL sender processes.
replication.wal.maxReplicationSlotsinteger10Maximum replication slots.
replication.pdb.enabledbooleantrueEnable a PodDisruptionBudget by default in replication mode.
replication.pdb.minAvailableinteger1Minimum available pods for the replication PDB.
replication.pdb.maxUnavailablestring""Maximum unavailable pods for the replication PDB.
replication.scheduling.enableDefaultPodAntiAffinitybooleantrueEnable opinionated anti-affinity defaults in replication mode.
replication.scheduling.enableDefaultTopologySpreadbooleantrueEnable opinionated topology spread defaults in replication mode.
replication.scheduling.topologyKeystringkubernetes.io/hostnameTopology key used by the default replication spread rules.

Service

ParameterTypeDefaultDescription
service.typestringClusterIPService type for PostgreSQL client access.
service.annotationsobject{}Annotations applied to the client Service.
service.primaryAnnotationsobject{}Annotations applied to the dedicated primary Service.
service.replicasAnnotationsobject{}Annotations applied to the dedicated replicas Service.
service.portinteger5432PostgreSQL listener port.
service.metricsPortinteger9187Metrics port exposed when metrics are enabled.
service.ipFamilyPolicystringomittedService IP family policy: SingleStack, PreferDualStack, or RequireDualStack. Omit for cluster default.
service.ipFamiliesarrayomittedOrdered list of IP families (IPv4, IPv6). Omit for cluster default.

Metrics

ParameterTypeDefaultDescription
metrics.enabledbooleanfalseEnable the postgres_exporter sidecar.
metrics.resourcesPresetstringnoneOptional resource preset for postgres_exporter.
metrics.service.annotationsobject{}Annotations applied to metrics Services.
metrics.image.repositorystringquay.io/prometheuscommunity/postgres-exporterMetrics sidecar image repository.
metrics.image.tagstring"v0.18.0"Metrics sidecar image tag.
metrics.image.pullPolicystringIfNotPresentImage pull policy for postgres_exporter.
metrics.resourcesobject{}Explicit resource requests and limits for postgres_exporter.
metrics.serviceMonitor.enabledbooleanfalseCreate a ServiceMonitor for Prometheus Operator.
metrics.serviceMonitor.intervalstring30sScrape interval for the ServiceMonitor.
metrics.serviceMonitor.labelsobject{}Extra labels applied to the ServiceMonitor.

Backup

ParameterTypeDefaultDescription
backup.enabledbooleanfalseEnable the built-in backup CronJob.
backup.schedulestring"0 3 * * *"Backup schedule in cron format.
backup.suspendbooleanfalseSuspend backup execution.
backup.concurrencyPolicystringForbidCronJob concurrency policy.
backup.successfulJobsHistoryLimitinteger3Successful backup Jobs retained by the CronJob.
backup.failedJobsHistoryLimitinteger3Failed backup Jobs retained by the CronJob.
backup.backoffLimitinteger1Job backoff limit for backup execution.
backup.archivePrefixstringpostgresqlPrefix used in generated backup archive names.
backup.resourcesobject{}Resources applied to backup dump and upload containers.
backup.images.uploader.repositorystringdocker.io/helmforge/mcUploader image repository for S3 copies.
backup.images.uploader.tagstring"1.0.0"Uploader image tag.
backup.images.uploader.pullPolicystringIfNotPresentPull policy for the uploader image.
backup.images.postgresql.repositorystringdocker.io/library/postgresPostgreSQL client image used for backup jobs.
backup.images.postgresql.tagstring"18.3-trixie"PostgreSQL client image tag used for backup jobs.
backup.images.postgresql.pullPolicystringIfNotPresentPull policy for the PostgreSQL backup image.
backup.s3.endpointstring""S3-compatible endpoint URL.
backup.s3.bucketstring""Target bucket name.
backup.s3.prefixstringpostgresqlOptional key prefix inside the bucket.
backup.s3.createBucketIfNotExistsbooleantrueCreate the bucket automatically when it does not exist.
backup.s3.existingSecretstring""Existing secret containing backup access and secret keys.
backup.s3.existingSecretAccessKeyKeystringaccess-keySecret key name for the S3 access key.
backup.s3.existingSecretSecretKeyKeystringsecret-keySecret key name for the S3 secret key.
backup.s3.accessKeystring""Inline S3 access key when not using an existing secret.
backup.s3.secretKeystring""Inline S3 secret key when not using an existing secret.
backup.database.pgDumpAllArgsstring"--clean --if-exists"Extra arguments passed to pg_dumpall.

NetworkPolicy

ParameterTypeDefaultDescription
networkPolicy.enabledbooleanfalseCreate an ingress-only NetworkPolicy for PostgreSQL pods.
networkPolicy.ingress.allowSameNamespacebooleantrueAllow ingress from the same namespace.
networkPolicy.ingress.extraFromarray[]Additional ingress from rules appended to the generated policy.

Pod Disruption Budget

ParameterTypeDefaultDescription
pdb.enabledbooleanfalseEnable a PodDisruptionBudget outside replication defaults.
pdb.minAvailableinteger1Minimum available pods when the PDB is enabled.
pdb.maxUnavailablestring""Maximum unavailable pods when the PDB is enabled.

Service Account

ParameterTypeDefaultDescription
serviceAccount.createbooleanfalseCreate a dedicated ServiceAccount.
serviceAccount.namestring""Override the ServiceAccount name.
serviceAccount.annotationsobject{}Annotations applied to the ServiceAccount.

Probes

ParameterTypeDefaultDescription
livenessProbe.enabledbooleantrueEnable the liveness probe.
livenessProbe.initialDelaySecondsinteger60Liveness probe initial delay.
livenessProbe.periodSecondsinteger20Liveness probe period.
livenessProbe.timeoutSecondsinteger5Liveness probe timeout.
livenessProbe.failureThresholdinteger6Liveness probe failure threshold.
readinessProbe.enabledbooleantrueEnable the readiness probe.
readinessProbe.initialDelaySecondsinteger20Readiness probe initial delay.
readinessProbe.periodSecondsinteger10Readiness probe period.
readinessProbe.timeoutSecondsinteger5Readiness probe timeout.
readinessProbe.failureThresholdinteger6Readiness probe failure threshold.
startupProbe.enabledbooleantrueEnable the startup probe.
startupProbe.initialDelaySecondsinteger10Startup probe initial delay.
startupProbe.periodSecondsinteger10Startup probe period.
startupProbe.timeoutSecondsinteger5Startup probe timeout.
startupProbe.failureThresholdinteger60Startup probe failure threshold.

Scheduling and Security

ParameterTypeDefaultDescription
podSecurityContext.fsGroupinteger999Filesystem group applied to PostgreSQL pods.
podSecurityContext.fsGroupChangePolicystringOnRootMismatchFilesystem group change policy.
podSecurityContext.seccompProfile.typestringRuntimeDefaultSeccomp profile applied at pod level.
securityContext.runAsUserinteger999Runtime UID for PostgreSQL containers.
securityContext.runAsGroupinteger999Runtime GID for PostgreSQL containers.
securityContext.runAsNonRootbooleantrueRequire containers to run as non-root.
securityContext.allowPrivilegeEscalationbooleanfalseDisable privilege escalation.
securityContext.readOnlyRootFilesystembooleanfalseKeep the root filesystem writable for PostgreSQL runtime needs.
securityContext.capabilities.droparray["ALL"]Linux capabilities dropped from PostgreSQL containers.
podLabelsobject{}Extra labels applied to PostgreSQL pods.
podAnnotationsobject{}Extra annotations applied to PostgreSQL pods.
annotationsobject{}Extra annotations applied to chart-managed resources that support them.
nodeSelectorobject{}Node selector applied to PostgreSQL pods.
tolerationsarray[]Tolerations applied to PostgreSQL pods.
affinityobject{}Explicit affinity rules.
topologySpreadConstraintsarray[]Explicit topology spread constraints.
priorityClassNamestring""PriorityClass applied to PostgreSQL pods.
terminationGracePeriodSecondsinteger120Termination grace period for PostgreSQL pods.

Extra

ParameterTypeDefaultDescription
extraEnvarray[]Extra environment variables injected into PostgreSQL containers.
extraVolumesarray[]Extra volumes appended to PostgreSQL pods.
extraVolumeMountsarray[]Extra volume mounts appended to PostgreSQL containers.

Upgrade Notes

Upgrading from 0.x to 1.x

If upgrading across major versions, check the

changelog

for breaking changes in values structure. Always back up your data before upgrading.

  • When switching from standalone to replication, set auth.replicationPassword before upgrading
  • PVC resize requires the storage class to support volume expansion (allowVolumeExpansion: true)
  • Metrics sidecar changes may cause a pod restart during upgrade

Common Issues

Pod stuck in CrashLoopBackOff

Usually caused by incorrect auth.postgresPassword on upgrade. PostgreSQL does not re-initialize auth when the data directory already exists. Check logs with kubectl logs <pod> and verify the password matches the existing data.

Slow replication sync

For large databases, initial replica sync can take time. Increase readReplicas.resources and ensure the storage class provides adequate IOPS. Monitor replication lag with the Prometheus exporter (pg_replication_lag metric).

Using with connection poolers

For high-connection workloads, deploy PgBouncer alongside PostgreSQL. Point your application to PgBouncer and configure it to connect to the PostgreSQL service.

More Information