PostgreSQL databases¶
On GRN.CLOUD, every project runs its own PostgreSQL cluster, inside its own namespace, on storage the project owns. It is not a managed service in which you rent a slice of a shared cluster. This page explains why it is built that way, because the consequences show up in how you create and delete things.
Why there is no shared cluster¶
A managed-database product is normally one large cluster the provider operates, carved into databases sold per tenant. That design is cheaper to run, and it is the usual one.
It is also incompatible with sovereignty. If the provider owns the cluster, the provider holds the superuser, the storage, the backups and the encryption keys. "Your data" then means a logical boundary inside someone else's system, enforced by their configuration and their good behaviour.
So there is no shared cluster here. Instead:
- Each project initialises its own
Cluster(postgresql.cnpg.io/v1) at the start. - The cluster runs in your namespace, on your persistent volumes.
- The operator reconciles it; it does not own it. Delete the project and the data goes with it, because there is nowhere else it lives.
The cost is that you have at least one Postgres cluster's worth of resources per project rather than a shared pool. That is the trade being made deliberately: capacity efficiency given up for a boundary that is structural rather than configured.
Cluster and Database are different objects¶
flowchart TB
subgraph P["your project - your namespace, your storage"]
direction TB
CL["Cluster (CNPG)<br/>the server: pods, PVCs, failover"]
OWN["cluster owner<br/>one, implicit"]
DB1["Database: orders"]
DB2["Database: billing"]
U1["user: orders-user"]
U2["user: billing-user"]
SVC["Service"]
CL --- OWN
CL --> DB1
CL --> DB2
DB1 --- U1
DB2 --- U2
CL --- SVC
end
APP["your workload"] -->|password from its Secret| U1
APP -->|connects| SVC
A Cluster is the running server: pods, volumes, failover, backups. You need a minimum of
one, and one is enough for many databases.
A Database is a request against that server. The operator creates the database inside the
cluster you point it at, owned by the cluster owner, and the user your application connects
as is created alongside it.
They are created at different times. The cluster arrives once, when the project is initialised. Databases are added whenever an application needs one.
If no cluster exists when you create a database¶
You are not blocked and you do not have to go and build one first. A Kyverno policy watches
Database creation, and when the project has no cluster to satisfy it, one is created
ad-hoc before the request lands.
flowchart TB
D["you create a Database"] --> Q{"does the project<br/>have a Cluster?"}
Q -->|yes| USE["the Database is created on it"]
Q -->|no| GEN["Kyverno creates a Cluster<br/>in this project"]
GEN --> USE
The result is the same either way: a cluster in your namespace that you own. The policy removes the ordering requirement, not the ownership model - it will never attach your database to someone else's cluster, because there is no shared cluster to attach it to.
The Database Cluster field makes the same decision explicit
The field offers the clusters in your project and a Create new ... entry, selected already when the project has none. Applying YAML gets the policy instead, which reaches the same result without asking.
An automatically created cluster takes the platform defaults
A cluster created this way takes the platform defaults for instance count and storage. If you know the workload, create the cluster deliberately and size it, then create the database against it.
One owner per cluster, one user per database¶
Two roles, with different jobs, and it matters which one an application gets.
| Created | Can | Use it for | |
|---|---|---|---|
| Cluster owner | once, with the cluster | create databases and roles | administering the cluster |
| Database user | with each database | log in and use its own database | your application |
The cluster owner is implicit. You do not name it and the console does not offer it as a field, because it administers everything in the cluster and no application should hold it. It owns each database that gets created.
The database user is what your application connects as: a plain login role, scoped to one database, with no rights to create anything. Each database gets its own, so two applications on the same cluster cannot reach each other's data by holding the wrong credential.
Both passwords are generated rather than chosen and land in Secrets - the owner's beside the cluster, each user's beside its database. Read them from there; do not copy them into a manifest.
CNPG has no per-database role object
A login role exists only if the cluster lists it in spec.managed.roles. Creating a
database therefore has to add the user there as well, which a Template cannot do on its
own - so a policy appends it when the user's Secret appears. That is why
oc process postgresql-database | oc apply -f - produces a working user without the
console being involved.
What is fixed after creation¶
| Changeable | |
|---|---|
| Database user's password | yes - update its Secret |
| Database name | no - create a new one and move the data |
| Which cluster it lives on | no |
| The database's owner | no - it is the cluster owner |
| Reclaim policy | yes |
Deleting the object may not drop the database
spec.databaseReclaimPolicy decides what happens to the data when the Database object
is deleted, and it defaults to retain: the object goes, the data stays in the cluster,
still occupying storage and still holding the name.
That default is deliberate - an accidental oc delete should not destroy a production
database - but it means "I deleted it" and "it is gone" are different states. See
Delete for how to tell which you are in.
oc get databases is ambiguous
MariaDB installs a Database kind too, so in a project running both the short form
returns whichever the API server resolves first. Use databases.postgresql.cnpg.io.
In a subnet, a cluster runs without backups¶
A project that is a subnet has a network of its own, and a workload in it cannot open the platform's S3 storage. A cluster there starts, reports healthy and serves workloads in the same subnet, but its backups and its write-ahead log are not written anywhere. Run a cluster whose data you need back in a normal project. See what a workload in a subnet can reach.
Related¶
- Step-by-step guide - create, list, change and delete as YAML
- Console walkthrough - the same operations in the console
- MariaDB - the same model, with a server per project
- S3 buckets - where cluster backups are written