Warning
Pre-alpha. OpenEverest v2 and this provider are under active development. CRD schemas, chart values and defaults change frequently, including in breaking ways, and there is no supported upgrade path between versions yet. Not for production use.
Run PostgreSQL on Kubernetes through OpenEverest, backed by CloudNativePG.
OpenEverest providers translate a single, technology-agnostic Instance custom resource into
the native custom resources of an upstream Kubernetes operator — for databases, but equally
for caches, message queues, object storage, or model-serving runtimes. This repository is the
provider for PostgreSQL via CloudNativePG: it owns the technology-specific knowledge —
topologies, versions, parameters, backup wiring — so that users, the API server, and the UI
stay technology-agnostic.
Important
This provider is not standalone. It requires an OpenEverest installation (core CRDs and controller) in the cluster. Installing this chart on its own does nothing. See Install OpenEverest.
flowchart LR
U([User / API / UI]) -->|creates| I["Instance<br/>core.openeverest.io"]
I --> P["provider-cloudnative-pg<br/>(this repository)"]
P -->|reconciles into| O["Cluster<br/>postgresql.cnpg.io/v1"]
O --> W["CloudNativePG"]
W --> R[("Pods, Services,<br/>Secrets, PVCs")]
P -->|status, endpoints,<br/>credentials| I
The provider watches Instance resources whose spec.providerRef.name is
provider-cloudnative-pg, and reports workload health back onto Instance.status. It never
manages pods directly — all lifecycle work is delegated to the operator.
| provider-cloudnative-pg | OpenEverest | CloudNativePG | Kubernetes |
|---|---|---|---|
0.2.x |
2.0.0-dev.2 |
1.29.x |
1.30 – 1.34 |
main |
2.0.0-dev.4 |
1.30.x |
1.34 – 1.36 |
What you can do to a running instance through the Instance API. Upgrading the
provider itself is covered under Installation.
| Capability | Status | Notes |
|---|---|---|
| Provisioning | ✅ | |
| Horizontal scaling | ✅ | spec.components.engine.replicas |
| Vertical scaling (CPU / memory) | ✅ | spec.components.engine.resources |
| Version upgrades | ✅ | of the deployed PostgreSQL version — change spec.version; see Versions |
| Custom configuration | ✅ | PostgreSQL GUCs via spec.components.engine.parameters.postgresql |
Bootstrap (initdb) |
✅ | database name, owner, and credentials secret |
| Managed roles | ✅ | CloudNativePG managed roles with readiness gating in Status() |
| Synchronous replication | ✅ | parameters.postgresql.synchronous; see Synchronous replication |
| TLS-only connections | ✅ | parameters.requireTLS: true rejects unencrypted client connections; see Require TLS |
| Pod scheduling | ✅ | spec.components.engine.schedulingPolicy (all fields), or parameters.affinity — not both |
| Pod priority | ✅ | parameters.priorityClassName → CloudNativePG spec.priorityClassName; the PriorityClass must exist, and changing it rolls the instance pods |
| Monitoring | ✅ | Prometheus: a PodMonitor for the instance pods via spec.topology.parameters.monitoring.prometheus; requires the Prometheus Operator. See Scrape metrics with Prometheus |
First-class toggles under spec.components.engine.parameters.extensions.<name>.enabled. The
provider runs CREATE EXTENSION for each enabled one in the application database through a
single CNPG Database resource (<instance>-extensions). Extensions not in the standard
operand images are mounted as image volumes: these need PostgreSQL 18 and Kubernetes
ImageVolume (1.35+, or 1.33/1.34 + feature gate), and adding one restarts the instances.
The image-volume images come from a CloudNativePG ClusterImageCatalog that the chart renders
from extensionCatalog in its values (default: the official CloudNativePG trixie builds plus
AGE, pinned by digest). The provider copies the catalog entry of each enabled extension (image,
paths, env) into the Cluster and passes the sqlVersion value to CREATE EXTENSION, so admins
can bump or mirror images with helm upgrade, or set extensionCatalog.create=false and point
extensionCatalog.name at a catalog they manage (the SQL versions go in
sql-version.extensions.openeverest.io/<major>.<name> annotations), without upgrading the
provider. A new image restarts the instances that use it.
| Extension | Toggle | Status | Enable on a running instance | Disable | Notes |
|---|---|---|---|---|---|
| pgvector | pgvector |
✅ | ✅ no restart | ❌ | PG 14–18, bundled in the standard images: no image change. See examples/instance-pgvector.yaml |
| pgAudit | pgaudit |
✅ | ✅ restarts | ✅ the operator drops it | PG 14–18, bundled; sets pgaudit.log: "ddl, role" unless a value is set in postgresql.parameters, and the operator then preloads it and creates it in every database |
| TimescaleDB OSS | timescaledb |
✅ | ❌ creation only ¹ | ❌ | Image volume, preloaded. See examples/instance-timescaledb.yaml |
| PostGIS | postgis |
✅ | ✅ restarts | ❌ | Image volume |
| pgRouting | pgrouting |
✅ | ✅ restarts | ❌ | Image volume; requires postgis |
| pg_ivm | pgivm |
✅ | ❌ creation only ¹ | ❌ | Image volume, preloaded |
| wal2json | wal2json |
✅ | ✅ restarts | ✅ restarts | Image volume; a logical decoding output plugin (no CREATE EXTENSION) — drop the replication slots that use it before disabling |
| Apache AGE | age |
✅ | ❌ creation only ² | ❌ | Image volume from solanicaio/postgresql-extensions, preloaded. Run SET search_path = ag_catalog, "$user", public before using it |
Extensions that are created in the database (everything but pgAudit and wal2json) cannot be disabled: dropping them would fail or destroy data, so the provider rejects it.
¹ Enforced by the provider: CloudNativePG advises against rolling out a new extension image and a
shared_preload_libraries change together, and enabling them on a running instance does exactly
that.
² Enforced by the provider: the application owner gets USAGE on ag_catalog through default
privileges set in postInitApplicationSQL, which only runs at bootstrap. Default privileges on
schemas cannot be limited to one schema, so the owner also gets USAGE on any other schema the
postgres superuser creates later in the application database.
Stateful workloads additionally report:
| Capability | Status | Notes |
|---|---|---|
| Persistent storage | ✅ | spec.components.engine.storage |
| Storage expansion | ✅ | when the StorageClass allows volume expansion (resizeInUseVolumes) |
| Separate WAL volume | ✅ | spec.components.engine.parameters.walStorage maps directly to CNPG spec.walStorage |
| Backups (on demand) | ✅ | operator-native (executionMode: ProviderManaged) via the Barman Cloud Plugin; one backup storage per Instance (CNPG supports a single WAL archive) |
| Restore | ✅ | from a succeeded Backup via Barman |
| Scheduled backups | ✅ | spec.backup.storages[].schedules → CNPG ScheduledBackup; mirrored as OpenEverest Backup CRs |
| Schedule retention | ✅ | time-based recovery windows are delegated to CNPG/Barman via ObjectStore retentionPolicy; count-based retention is rejected |
| Backup deletion | ✅ | deletionPolicy: Delete removes the Barman base backup; Retain removes only the CNPG Backup resource |
| PITR | ✅ | restore to a point in time via spec.dataSource |
CNPG retention uses the Barman Cloud Plugin's native recovery window:
retention:
type: time
duration: 30dAll schedules targeting the same storage, including disabled schedules, must
specify the same duration or all omit retention (keep all). The window applies
to that cluster's entire Barman archive, including on-demand backups and WAL,
not independently to each schedule. Barman preserves the base backup needed to
recover from the beginning of the window. Count retention is not supported.
Instances in one namespace that use the same backup storage share its
ObjectStore, so they must also agree on retention. An Instance whose retention
would change the policy already stored is refused with a
BackupRetentionConflict on its BackupConfigured condition.
The OpenEverest dev.4 schedule form edits only count retention ("retention
copies"), which this provider rejects. Leave copies at 0 and set time retention
through the API or CLI.
deletionPolicy: Retain skips explicit data deletion; it does not exempt a
backup from Barman's archive-wide retention policy.
If CNPG removes a backup resource during native catalog cleanup, its Everest
record is reported as failed/unavailable rather than starting a replacement backup.
deletionPolicy: Delete runs a one-off barman-cloud-backup-delete Job
(delete-backup-<name>) for the backup's Barman ID. The Job meets the
restricted Pod Security Standard. Backups that never received a Barman ID
(not started, or failed early) have no data to delete. If the Job fails, inspect
its logs and delete the Job to retry. Data deletion runs only while the source
Instance exists; delete Backups before deleting their Instance.
The backup integration and e2e suites check native policy configuration and per-backup deletion in the object store, not the passage of a multi-day recovery window.
Configure WAL independently of the data volume:
In the UI, the Resources section exposes a Separate WAL storage toggle (OpenEverest dev.4 or later). It is off by default for new instances, keeping the single-volume layout. Enabling it reveals a required WAL disk size and an optional WAL storage class (selectable when WAL storage is first added, then fixed). Existing WAL settings turn the toggle on automatically. Dev.4 cannot lock the switch in edit mode, so validation blocks saving with the toggle off once separate WAL storage is configured.
spec:
components:
engine:
storage:
size: 20Gi
parameters:
walStorage:
size: 5Gi
storageClass: fast-storage
resizeInUseVolumes: truewalStorage uses CNPG's native storage configuration, including optional
pvcTemplate. Omit it to keep WAL on the data volume. Each PostgreSQL instance
gets its own WAL PVC. Set a positive size (or a pvcTemplate storage request,
as CNPG allows); omit storageClass to use the default StorageClass. The
data-volume storage class is not inherited.
CNPG manages provisioning, migration when adding a WAL volume, and expansion.
Once configured, WAL storage cannot be removed. CNPG validates updates and
rejects shrinking volumes; expansion requires a StorageClass/CSI driver that
supports it (allowVolumeExpansion: true). Adding a volume to an existing
cluster can restart instances; follow CNPG's storage lifecycle guidance.
This setting provisions storage, not a WAL disk-usage alert or monitoring policy.
The provider chart is published as an OCI artifact:
helm install provider-cloudnative-pg \
oci://ghcr.io/adityapimpalkar/charts/provider-cloudnative-pg \
--version 0.3.2 \
--namespace everest-system- The CloudNativePG operator and the Barman Cloud Plugin (and their CRDs) are bundled as chart
dependencies and are installed automatically. Set
cloudnative-pg.enabled=falseorplugin-barman-cloud.enabled=falseto use externally managed installs instead.
Important
The Barman Cloud Plugin's admission webhooks require cert-manager to be installed in the cluster before installing this chart (when the plugin subchart is enabled).
Upgrade and uninstall:
helm upgrade provider-cloudnative-pg \
oci://ghcr.io/adityapimpalkar/charts/provider-cloudnative-pg --version 0.3.2
helm uninstall provider-cloudnative-pg --namespace everest-systemUninstalling the chart does not delete running Instance resources or their data.
Verify that the provider registered itself:
kubectl get providers.core.openeverest.io provider-cloudnative-pgCreate an instance:
apiVersion: core.openeverest.io/v1alpha1
kind: Instance
metadata:
name: my-instance
spec:
providerRef:
name: provider-cloudnative-pg
version: "17.10"
components:
engine:
type: postgresql
replicas: 3
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "1"
memory: 1Gi
storage:
size: 5GiComponent names are defined by this provider — see definition/provider.yaml.
spec.version and spec.topology are optional; the provider defaults apply.
More examples live in examples/, including
TimescaleDB and pgvector.
Watch it come up and read the connection details:
kubectl get instance my-instance -w
kubectl get instance my-instance -o jsonpath='{.status.connectionSecretRef.name}'The credentials (host, port, username, password, uri, sslmode,
host-r, host-ro) live in the connection Secret named by
.status.connectionSecretRef.name.
hostis<cluster>-rw.<namespace>.svc, the read-write Service.host-ris<cluster>-r.<namespace>.svc. That Service targets every ready instance, including the primary. It is omitted when the read Service is disabled.host-rois<cluster>-ro.<namespace>.svc. That Service targets ready replicas and excludes the primary, so it is published only when the read-only Service is enabled andreplicasis at least 2.portis theportkey of the credential Secret. CloudNativePG writes5432there. A user-supplied basic-auth Secret may omit it, and then the port is5432. The Service port is not configurable.sslmodeisrequire, anduriincludes it. CloudNativePG always serves TLS, so this works whether or notrequireTLSis set. libpq's defaultpreferis not published.
node-postgres (pg-connection-string 2.7 and later) treats sslmode=prefer,
require, and verify-ca as verify-full (ssl: true). That check fails
for CloudNativePG's operator-managed server certificate. The CA certificate
is the ca.crt key in the <cluster>-ca Secret, or in the Secret named by
certificates.serverCASecret when you bring your own. Use that file as
sslrootcert with sslmode=verify-full to authenticate the server.
pg-connection-string also accepts sslmode=no-verify, which encrypts
without checking the certificate. That value is not a libpq sslmode.
Every CloudNativePG instance pod serves its metrics exporter on port metrics (9187).
With the Prometheus Operator in the cluster, enable
Prometheus (the Monitoring → Prometheus toggle in the UI) to get a PodMonitor
(<instance>-metrics) that scrapes it, over HTTPS when
spec.components.engine.parameters.monitoring.tls.enabled is set. podMonitorLabels lets
your Prometheus podMonitorSelector pick it up. Turning it off removes the PodMonitor.
spec:
topology:
type: replicaSet
parameters:
monitoring:
prometheus:
enabled: true
podMonitorLabels:
release: kube-prometheus-stackThe PodMonitor carries app.kubernetes.io/managed-by=everest and
app.kubernetes.io/instance=<instance>, which the OpenEverest metrics plugin uses to find it.
Don't combine it with CloudNativePG's deprecated monitoring.enablePodMonitor, which would
scrape every pod twice; the provider rejects that combination.
| Topology | Default | Description |
|---|---|---|
replicaSet |
✅ | A CloudNativePG primary + standby cluster built from the engine component (default 3 instances) |
The backupAgent component is optional. Prometheus scraping is a topology
parameter (spec.topology.parameters.monitoring.prometheus), not a component.
| Version bundle | Default | postgresql |
|---|---|---|
17.10 |
✅ | 17.10 (ghcr.io/cloudnative-pg/postgresql:17.10-standard-trixie) |
18.4 |
18.4 |
|
16.14 |
16.14 |
|
15.18 |
15.18 |
|
14.23 |
14.23 |
Source of truth: definition/versions.yaml.
Images follow CloudNativePG recommended practice (official CNPG operands, standard image type
on Debian trixie). The operator must already support the target version — upgrade the
provider chart first.
- Chart values: charts/provider-cloudnative-pg/values.yaml
- Instance parameters: per-component and per-topology
parametersschemas, defined under definition/ and published on theProviderresource (kubectl get provider provider-cloudnative-pg -o yaml). The API server and the UI validate user input against these schemas.
The technology-specific knobs worth knowing about:
| Parameter | Applies to | Purpose |
|---|---|---|
bootstrap.initdb |
engine |
Initial database name, owner, and optional credentials Secret |
postgresql |
engine |
Full CloudNativePG PostgresConfiguration (GUCs, sync replicas, …) |
extensions.* |
engine |
First-class extensions; see Extensions. TimescaleDB also defaults timescaledb.telemetry_level=off and max_locks_per_transaction=128 unless set in postgresql.parameters |
affinity |
engine |
CloudNativePG AffinityConfiguration; rejected together with schedulingPolicy affinity, nodeSelector or tolerations |
managed |
engine |
Managed PostgreSQL roles |
resizeInUseVolumes |
engine |
Allow PVC expansion on running instances |
priorityClassName |
engine |
Kubernetes PriorityClass of the instance pods |
certificates |
engine |
TLS certificate configuration passthrough |
requireTLS |
engine |
Reject unencrypted client connections; see Require TLS |
monitoring |
engine |
CloudNativePG monitoring configuration (custom queries, metrics TLS, …) |
monitoring.prometheus |
topology | Create a PodMonitor for the instance; see Scrape metrics with Prometheus |
Replication is asynchronous by default. To make each commit wait for standbys,
set CloudNativePG's
synchronous
configuration (the UI exposes it as the Synchronous replication toggle under
Resources):
spec:
components:
engine:
replicas: 3
parameters:
postgresql:
synchronous:
method: any # any = quorum, first = priority order
number: 1 # standbys that must confirm each commit
dataDurability: requirednumbermust be lower thanreplicas(plus any listed standby names); the provider rejects other values before applying the Cluster.- With the default
synchronous_commit = on, a commit returns once the required standbys have flushed its WAL to durable storage. Setpostgresql.parameters.synchronous_committoremote_applyto also wait until the change is visible on them, orremote_writefor a weaker guarantee. Applications can lowersynchronous_commitper transaction, and those transactions are not covered by the guarantee. dataDurability: requiredstops writes when too few standbys are available.preferredfalls back to asynchronous replication and restores synchronous replication when standbys return. The provider does not alert on that fallback; watch the replication metrics.
CloudNativePG always serves TLS, but its default pg_hba.conf also accepts
unencrypted connections. Set requireTLS: true to refuse them:
spec:
components:
engine:
parameters:
requireTLS: trueThe provider places hostnossl all all all reject before any rules in
postgresql.pg_hba. CloudNativePG applies it with a configuration reload, so
instances are not restarted. Replication and local connections are unaffected:
CloudNativePG's own rules for them come first and use TLS or the local socket.
Clients must connect with TLS. libpq's default sslmode=prefer does;
sslmode=disable is refused. Use sslmode=verify-full with the cluster CA to
also authenticate the server. The CA certificate is the ca.crt key in the
<cluster>-ca Secret, or in the Secret named by certificates.serverCASecret
when you bring your own. The connection Secret's uri uses sslmode=require;
see Connection details for how node-postgres treats
that value.
The UI enables this by default for new instances. Unset means off, so existing instances keep accepting unencrypted clients until you turn it on.
Requires Go (see go.mod), Docker, Helm, kubectl, and a Kubernetes cluster you can
reach. dev/README.md covers the environment end to end: the recommended
local k3d setup, running against a cluster you already have, and every dev/.env setting.
make dev-up # local cluster + Tilt dev environment (see dev/README.md)
make generate # RBAC, provider spec, Helm chart sync
make run # run the provider locally against the cluster
make test # unit tests
make test-integration # chainsaw suites under test/integration/
make test-integration-backup # backup integration suite against S3Mock
make dev-downTo work against a cluster you already have — kind, GKE, a shared OpenEverest Tilt cluster —
skip make dev-up and point Tilt at it:
cp dev/.env.example dev/.env # set K8S_CONTEXT, and DOCKER_REGISTRY_URL for a remote registry
tilt up -f dev/Tiltfilemake help lists every target. make verify fails when generated files are stale — run
make generate and commit the result.
The provider contract (Validate / Sync / Status / Cleanup), RBAC markers, watches,
code generation, and the backup/restore interfaces are documented once for all providers in
PROVIDER_DEVELOPMENT.md.
| Path | Purpose |
|---|---|
cmd/provider/ |
Entry point |
internal/provider/ |
ProviderInterface implementation, backup mirror, RBAC markers |
internal/cnpg/ |
CloudNativePG helpers — TimescaleDB, roles; barman/ for backup, restore, and schedules |
internal/common/ |
Component name constants |
definition/ |
Provider identity, component types, versions, topologies, backup classes |
charts/provider-cloudnative-pg/ |
Helm chart (generated/ is produced by make generate) |
config/rbac/role.yaml |
Generated ClusterRole — do not edit |
test/integration/ |
Chainsaw suites: core/replicaset, backup, extensions/bundled, extensions/imagevolume, tls-only, wal-storage |
test/e2e-cluster/ |
Chainsaw e2e suites on the full OpenEverest platform: backup-restore, backup-deletion |
test/testdata/ |
Fixtures shared by the suites (S3Mock object store) |
test/e2e/ |
Playwright UI tests against the OpenEverest UI |
test/vars.sh |
Pinned versions used by tests |
examples/ |
Example Instance resources |
dev/ |
Tilt dev environment, .env configuration, k3d cluster config |
.github/workflows/ |
CI: lint, build, unit and integration tests, release |
- Unit tests —
make test. - Integration tests — chainsaw suites under test/integration/.
Individual suites are also exposed as make targets (
make test-integration-core,make test-integration-backup,make test-integration-core-replicaset,make test-integration-extensions, …). The extension suites run a one-instance PostgreSQL and use each extension throughpsql.extensions/imagevolumeneeds Kubernetes ImageVolume (1.35+ with containerd 2.1+); the dev and CI k3d cluster (dev/k3d_config.yaml) runs k3s 1.36, somake test-integrationcovers it (alone:make test-integration-imagevolume).wal-storage(make test-integration-wal, needsjq) grows the WAL volume. On a StorageClass without expansion, such as k3d'slocal-path, it uses an expandable copy and checks that the resize request reaches the WAL PVC; the physical resize is checked only where the default StorageClass expands volumes (e.g. a CSI driver with a resizer). - E2E cluster tests — chainsaw suites under test/e2e-cluster/ that run
user journeys on the full platform: the released OpenEverest core (server, controller and CRDs,
at the version pinned in
go.mod) installed bymake install-openeverest, plus this provider.backup-restorewrites data, takes a Backup, restores it into a new Instance throughspec.dataSource, and checks the data.backup-deletionchecks that schedule retention reaches the Barman ObjectStore, that deleting aRetainBackup keeps its objects in the object store, and that deleting aDeleteBackup removes them. Run withmake test-e2e-cluster. - E2E UI tests — Playwright tests under test/e2e/ drive the OpenEverest UI: they
create a PostgreSQL instance through the provider's generated form, wait for it to be Ready, and
delete it; a second test turns on Separate WAL storage and checks the WAL volume. Run with
make test-e2e(installs Chromium for Playwright) against a UI atEVEREST_URL(defaulthttp://localhost:8080, e.g.kubectl -n everest-system port-forward svc/everest 8080:8080); the test namespace defaults toe2e-ui(E2E_NAMESPACE) and must exist. SetE2E_SKIP_KUBECTL=trueif the test host cannot reach the cluster API: the CNPG Cluster checks are then reported as skipped, and an instance left by a failed test is not deleted. - CI — .github/workflows/build.yaml runs lint, build, unit
tests, generated-file verification, and Helm lint;
.github/workflows/test.yaml runs the integration suites;
.github/workflows/e2e-cluster.yaml runs the e2e cluster
suites on every pull request; .github/workflows/e2e.yaml runs the
UI tests nightly, on demand, and on pull requests labelled
test/e2e.
kubectl logs -n everest-system deploy/provider-cloudnative-pg -f| Symptom | Where to look |
|---|---|
Instance stuck in Creating |
kubectl describe instance <name> conditions, then the provider logs |
No Provider resource in the cluster |
Is the chart installed? Check the provider deployment logs |
Instance ignored entirely |
spec.providerRef.name must be provider-cloudnative-pg |
Cluster created but not reconciled |
Confirm CloudNativePG is running; for Barman, confirm cert-manager is installed |
Cluster created but no pods |
Inspect the CloudNativePG Cluster status — the failure is upstream in the operator |
| Backup / restore stuck | Check Barman plugin pods, ObjectStore / Backup CRs, and BackupStorage credentials |
Issues and pull requests are welcome. See PROVIDER_DEVELOPMENT.md and the OpenEverest Code of Conduct.
Report vulnerabilities per the OpenEverest security policy. Please do not open public issues for security reports.
Apache License 2.0 — see LICENSE for details.