Skip to content

Manage Postgres with Postgres

The clearest demonstration of what Axiom is: one Postgres instance creating, inspecting and scaling another — through CloudNativePG, with no kubectl and no YAML.

First, install the operator, which is what creates the CRD the gateway can then discover:

kubectl apply --server-side -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml

kubectl wait --for=condition=Available deploy/cnpg-controller-manager \
  -n cnpg-system --timeout=240s

Then let the gateway write the kind. Reads may already be covered, but the shipped RBAC grants writes only per resource, and these examples create and scale clusters:

kubectl patch clusterrole axiom-gateway --type=json -p='[{"op":"add","path":"/rules/-","value":
  {"apiGroups":["postgresql.cnpg.io"],"resources":["clusters"],
   "verbs":["get","list","watch","create","update","delete"]}}]'

The gateway sees a new grant on the next import, with no restart. Foreign tables are catalog objects and do not follow an RBAC change, so import the kind now:

IMPORT FOREIGN SCHEMA k8s LIMIT TO (postgresql_cnpg_io_clusters) FROM SERVER prod INTO k8s;

That produces the usual shape — the universal columns, the kind's own top-level fields as jsonb, and raw:

       Column       | Type
--------------------+-------
 api_version        | text
 kind               | text
 name               | text
 namespace          | text
 ...
 spec               | jsonb
 status             | jsonb
 raw                | jsonb

FDW options: (resource 'clusters', "group" 'postgresql.cnpg.io', version 'v1', kind 'Cluster')

Now create one:

INSERT INTO k8s.postgresql_cnpg_io_clusters (namespace, name, spec) VALUES (
  'default', 'demo-db',
  '{"instances": 1, "storage": {"size": "256Mi"}}'::jsonb
);
INSERT 0 1

It is a real object, and the operator picks it up:

$ kubectl get cluster.postgresql.cnpg.io -n default
NAME      AGE   INSTANCES   READY   STATUS   PRIMARY
demo-db   0s

Watch it come up without leaving SQL:

SELECT name, status->>'phase' AS phase, status->>'readyInstances' AS ready
FROM k8s.postgresql_cnpg_io_clusters WHERE namespace = 'default';

Scale it with an UPDATE

spec is a jsonb column, so changing the cluster is a jsonb_set:

UPDATE k8s.postgresql_cnpg_io_clusters
   SET spec = jsonb_set(spec, '{instances}', '3')
 WHERE namespace = 'default' AND name = 'demo-db';
UPDATE 1

The operator reconciles, and the change is visible from either side:

$ kubectl get cluster.postgresql.cnpg.io demo-db -o jsonpath='{.spec.instances}'
3

A conflicting write does not silently win: Axiom sends the resourceVersion it read, so if something else changed the object first the API server rejects the update and you get a SQL error rather than a lost write.

Nothing here is CNPG-specific. Any CRD the gateway's RBAC permits becomes a table the same way, with spec as the field you write.