Once a database is running, the operational work starts: adding memory, adding read capacity, or moving to a new version — ideally without an outage. With the CYBERTEC PG Operator (CPO), each of those is a single field change in a manifest, and the operator performs the rollout while the cluster stays available.

Changing resources and parameters

Resizing is done by editing the resource requests and limits:

1

2

3

4

kubectl -n cpo patch pg pg-cluster --type=merge -p '{

  "spec": { "resources": {

    "requests": { "cpu": "500m", "memory": "1Gi" },

    "limits":   { "cpu": "1",    "memory": "1Gi"   } } } }'

Rather than restarting the whole cluster, the operator works through the instances incrementally, and the progress is visible as it goes:

1

2

3

4

5

6

7

NAME           READY   STATUS        ROLE

pg-cluster-0   1/1     Running       master

pg-cluster-1   1/1     Terminating   replica   # replica updated FIRST

pg-cluster-1   1/1     Running       (none)

pg-cluster-0   1/1     Terminating   (none)    # then a SWITCHOVER...

pg-cluster-1   1/1     Running       master    # ...pg-cluster-1 is now leader

pg-cluster-0   1/1     Running       replica   # old leader rejoins as replica

The order is replicas first, then a switchover onto the updated node, then the former leader — so there is never a moment when the entire cluster is down. In testing, the new limits were confirmed applied ("memory":"1Gi") and the cluster remained healthy for the duration.

PostgreSQL parameters follow the same model: set the value and Patroni applies it across the cluster.

1

2

3

4

5

kubectl -n cpo patch pg pg-cluster --type=merge \

  -p '{"spec":{"postgresql":{"parameters":{"max_connections":"200"}}}}'

# a few seconds later:

kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc 'show max_connections;'

# 200

Adding and removing replicas

Read capacity is controlled by numberOfInstances:

1

kubectl -n cpo patch pg pg-cluster --type=merge -p {"spec":{"numberOfInstances":3}}

A new node is cloned from the leader, then begins streaming — it has the data as soon as it joins:

1

2

3

| pg-cluster-0 | Leader  | running   |

| pg-cluster-1 | Replica | streaming |

| pg-cluster-2 | Replica | streaming |   # the new one, zero lag

1

2

kubectl -n cpo exec pg-cluster-2 -- psql -U postgres -tAc 'select count() from t;'

# 3

Reducing the count (for example numberOfInstances: 2) drains the highest-numbered node cleanly. If that node is the leader, Patroni fails over to a surviving node first, so neither the primary nor data is lost. A test cycle of 2 → 3 → 2 left the row count at 3 throughout.

Minor version updates

A minor update means changing the image field. With data loaded into the cluster, the image is replaced:

1

2

3

4

5

kubectl -n cpo exec pg-cluster-0 -- psql -U postgres \

  -c "create table before_update(note text); insert into before_update values ('hello from 17.5');"

kubectl -n cpo patch pg pg-cluster --type=merge \

  -p {"spec":{"dockerImage":"docker.io/cybertecpostgresql/cybertec-pg-container:rocky9-18.4-1"}}

The pod restarts onto the new image — roughly 25 seconds in this run — and the data is intact:

1

2

3

4

5

kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc select version();

# PostgreSQL 18.X on x86_64-pc-linux-gnu, ...

kubectl -n cpo exec pg-cluster-0 -- psql -U postgres -tAc select note from before_update;

# hello from 17.5

The switchover that follows uses the same zero-downtime pattern as the resize. Major upgrades, such as 17 → 18, are a larger operation: the operator runs pg_upgrade in place.

Sizing up the model

All three operations come down to one move: change the desired state in the manifest and let the operator reconcile it with a rolling update. Practically, that turns routine database maintenance into kubectl patch commands, or better, pull requests against the manifests kept in Git — while keeping the cluster up remains the operator's responsibility rather than the DBA's.