Short answer: for a small-business application with a single database of a few dozen gigabytes, a managed database (OVHcloud, Scaleway, Amazon RDS) costs between €25 and €120 per month and spares you any on-call duty for backups and version upgrades. CloudNativePG inside your Kubernetes cluster costs the price of the nodes and storage — often 2 to 3 times less — but makes you responsible for PITR, failover and major upgrades. The right criterion isn't the price: it's the number of people able to restore the database at 3 a.m.
This is the question that comes up every single time a team finishes migrating its application to Kubernetes. The application pods are stateless, the deployment is clean, CI pushes the image — and then there's that PostgreSQL database still running on a VM from 2021, with a pg_dump in a cron job and nobody having tested a restore in eighteen months.

The three possible options, and the one to rule out
There are in fact three scenarios, not two.
- PostgreSQL in the cluster, managed by an operator — CloudNativePG, Percona Operator, Zalando postgres-operator. The operator handles replication, failover, continuous backups and minor upgrades.
- A managed database from a provider — OVHcloud Databases for PostgreSQL, Scaleway Database for PostgreSQL, Amazon RDS / Aurora, Neon or Supabase for the serverless variants.
- PostgreSQL in the cluster without an operator — a hand-written
StatefulSet, or worse, the Bitnami Helm chart left atreplicaCount: 1with backups "to be done later".
Scenario 3 should be ruled out. Not because PostgreSQL runs badly in a container — it runs very well — but because everything that makes a production database valuable (WAL archiving, promoting a replica, point-in-time recovery) is precisely what a bare StatefulSet does not do. And that's what you find out on the day of an incident.
Field rule: if nobody on the team can answer "what is our RPO, and when did we last verify it?", the issue isn't where to host the database, it's the lack of a restore procedure.
CloudNativePG: what the operator does for you
CloudNativePG is the PostgreSQL operator originally developed by EDB, accepted as a CNCF sandbox project in January 2025 and promoted to incubating in 2026. It is now the reference choice on the Kubernetes-native side: no Patroni, no external etcd, primary election relies on the Kubernetes API itself.
Installation is a single command (check the current version number in the repository before running it):
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.27/releases/cnpg-1.27.0.yaml
kubectl get deploy -n cnpg-system cnpg-controller-manager
A three-instance cluster with continuous archiving to an S3-compatible bucket (Scaleway Object Storage here; the OVHcloud equivalent works the same way) is described like this:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: app-db
namespace: production
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:17.6
storage:
size: 50Gi
storageClass: scw-bssd
walStorage:
size: 10Gi
postgresql:
parameters:
shared_buffers: 512MB
max_connections: "200"
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
memory: 2Gi
backup:
retentionPolicy: "30d"
barmanObjectStore:
destinationPath: s3://mon-bucket-backups/app-db
endpointURL: https://s3.fr-par.scw.cloud
s3Credentials:
accessKeyId:
name: backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: backup-creds
key: ACCESS_SECRET_KEY
wal:
compression: gzip
Two points deserve emphasis.
A separate walStorage. Putting the WAL files on a volume distinct from the data directory prevents an archiving backlog from filling the main volume and blocking writes. This is the most common failure on a self-hosted database.
barmanObjectStore = PITR. Continuous WAL archiving to object storage is what enables point-in-time recovery. Without it, you have base backups only, so an RPO equal to the interval between two backups — typically 24 hours.
Restoring to a point in time
A restore does not modify the existing cluster: it creates a new cluster from the archive. That's safer, and it can be tested in staging risk-free.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: app-db-restore
spec:
instances: 1
storage:
size: 50Gi
bootstrap:
recovery:
source: app-db-archive
recoveryTarget:
targetTime: "2026-11-06 14:25:00.00000+01"
externalClusters:
- name: app-db-archive
barmanObjectStore:
destinationPath: s3://mon-bucket-backups/app-db
endpointURL: https://s3.fr-par.scw.cloud
s3Credentials:
accessKeyId:
name: backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: backup-creds
key: ACCESS_SECRET_KEY
wal:
maxParallel: 8
Put this exercise in your calendar once a quarter. A restore that has never been performed isn't a backup, it's an intention.
Major upgrades remain your problem
CloudNativePG handles minor upgrades (17.5 → 17.6) through a rolling restart: replicas first, then a primary switchover, with a few seconds of downtime. Moving from PostgreSQL 17 to 18 is another matter: since version 1.26, the operator can trigger a declarative pg_upgrade, but that implies a service interruption and a rollback plan. Factor in the PostgreSQL support schedule: each major version is maintained for five years, which forces a major migration every three to four years at the latest.

What a managed database really buys you
A managed service doesn't sell compute power, it sells on-call time avoided. Concretely, here's what's included with the three providers most often encountered in France:
| OVHcloud Databases | Scaleway Database | Amazon RDS | |
|---|---|---|---|
| High availability | replica nodes optional depending on plan | HA option (secondary node) | Multi-AZ optional |
| Automatic backups | included, retention depends on plan | included, configurable retention | included, 1 to 35 days |
| PITR | yes | yes | yes |
| Major version upgrade | assisted by the provider | assisted | pg_upgrade triggered from the console |
| Superuser access | no | no | no |
| Extensions | allowlist | allowlist | allowlist |
The three limits in the last row are the real constraints, well ahead of price. No SUPERUSER, therefore no extension outside the allowlist. If your application needs pg_cron, timescaledb or an in-house extension, check before you sign: this is the number one reason teams move back to self-hosting.
A second point often overlooked: network latency. A managed database in the same region as your cluster typically adds one network round trip per query. For an API that issues 40 SQL queries per page — which happens fast with a poorly configured ORM — that detail becomes visible. Measure with pg_stat_statements before concluding that the database is the slow part.
The bill, line by line
Price lists change; orders of magnitude, much less so. Always check the public OVHcloud, Scaleway and AWS pages when making your decision, but reason with this structure.
Managed option, 50 GB database, 2 vCPU / 8 GB RAM, with a secondary node:
- the primary instance and its replica: the main line item, billed hourly;
- storage, billed per gigabyte-month;
- backups beyond the included retention;
- egress traffic if the database isn't in the same region as the applications.
For that sizing, you're in practice looking at a range of €60 to €150 per month with OVHcloud or Scaleway, and appreciably more with AWS in Multi-AZ, since storage and provisioned IOPS are billed separately.
CloudNativePG option, same sizing, three instances:
- no licence: the operator is Apache 2.0;
- the CPU/RAM share consumed on your existing nodes — if your cluster already has headroom, the marginal cost is low, otherwise budget for one extra node;
- three persistent data volumes plus three WAL volumes, billed per gigabyte-month by the block storage provider;
- object storage for backups: a few euros per month for a few hundred gigabytes of compressed WAL.
You often land between €25 and €60 per month in infrastructure costs. The apparent gap is clear — but it deliberately omits the most expensive line item.
The line item nobody quantifies
Set the monthly saving against the cost of operations: metrics monitoring (replication lag, WAL archiving success rate, volume fill levels), a quarterly restore test, a major migration every three years, and the availability of a competent person in case of an incident. At typical consulting day rates in the Paris region, two days of intervention wipe out a year of savings at that sizing.
The conclusion is therefore less economic than organisational:
- only one person on the team knows PostgreSQL → managed database, no debate;
- several databases, several environments, a need for non-standard extensions → CloudNativePG, which pays off from three or four databases onwards because the operator is shared;
- a seasonal load peak → managed resizes in a few minutes from the console, whereas self-hosting means adding a node to the cluster.
The most common mistakes
The same provider for the database and the backups. Sending the WAL files of an OVHcloud database to an OVHcloud bucket in the same region means accepting that a regional incident takes out both. Choose at minimum another region, ideally another provider, and enable object immutability where available.
Backups that are never restored. Already said, and it remains the leading cause of actual data loss.
No connection pooling. PostgreSQL allocates one process per connection. A Node.js application opening a pool of 20 connections per pod, multiplied by 10 pods, saturates a default max_connections. CloudNativePG exposes a Pooler based on PgBouncer; on the managed side, check whether a pooler is provided, otherwise deploy one in the cluster.
No coherent memory limit. If shared_buffers plus the cumulative work_mem exceeds the pod's memory limit, the kernel kills the process (OOMKill) and you get an unwanted failover. Keep a margin of at least 25% between the PostgreSQL parameters and the container limit.
Poorly chosen network storage. A standard block volume is enough for most small-business workloads, but a transactional database on a low-IOPS volume translates into erratic response times. Look at your storageClass storage tier before blaming the queries.
If you're tackling this topic alongside the rest of the cluster, the two neighbouring projects are the HTTP entry point — see our article on migrating from ingress-nginx to the Gateway API — and image registry authentication, covered in private GitLab images on Kubernetes. Our team supports these trade-offs as part of our infrastructure and operations services, together with application development when the data schema also needs rethinking.
Frequently asked questions
Is PostgreSQL in a container serious for production?
Yes, provided you use an operator. The container isn't the problem: persistent storage and failover orchestration are. CloudNativePG solves both by relying on persistent volumes and the Kubernetes API. Multi-terabyte databases run this way in production. A hand-written StatefulSet, on the other hand, is not serious.
What's the difference between RPO and RTO, concretely?
The RPO is the amount of data you accept losing: with continuous WAL archiving, it's measured in seconds; with a nightly pg_dump, in hours. The RTO is the time to restore service: promoting a replica takes tens of seconds, rebuilding a 500 GB database from object storage takes tens of minutes. Put both figures in writing before choosing an architecture.
Can you migrate from a managed database to CloudNativePG with no downtime?
Almost. CloudNativePG can bootstrap a cluster from an external database via logical replication (bootstrap.initdb.import or a subscription to a publication). You replicate continuously, verify, then switch the application over during a window of a few minutes. The catch: logical replication doesn't carry sequences — remember to resynchronise them — and requires PostgreSQL 10 or later on both sides.
Do you need three instances, or will two do?
Three. With two instances, losing the replica leaves you without redundancy during the rebuild, and node maintenance becomes a hard stop. Three instances let you drain a node (kubectl drain) without ever dropping below one available synchronous replica.
What about serverless offerings like Neon?
They make sense for ephemeral pre-production environments — database branching per Git branch is a genuine win — and for very irregular workloads. For a production application with steady traffic, usage-based billing rarely works out cheaper, and the wake-up latency after idling isn't acceptable on a public page.
In summary
- Never a hand-written PostgreSQL
StatefulSet: either an operator or a managed service. - CloudNativePG brings declarative replication, failover, WAL archiving and PITR, under the Apache 2.0 licence, for the cost of nodes and storage alone — often €25 to €60/month at small-business sizing.
- A managed database (OVHcloud, Scaleway, RDS) costs more like €60 to €150/month at the same sizing but removes the on-call burden; check the extension allowlist before committing.
- The decisive criterion is human: two days of intervention wipe out a year of savings.
- Separate the WAL files from the data volume and send backups to another provider or at minimum to another region.
- Test the restore every quarter and write your RPO and RTO down in black and white.
- Connection pooling is mandatory (PgBouncer, or CloudNativePG's
Poolerobject) as soon as several application pods connect.
Unsure which sizing to choose, or whether a zero-downtime migration is feasible? Write to us with your database size and your availability requirements.

