Skip to content

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.