feat(postgrest)!: strip chart down to a stateless service - #161
Open
emmanuel wants to merge 1 commit into
Open
Conversation
emmanuel
force-pushed
the
refactor/postgrest-stateless
branch
from
August 6, 2026 19:05
3ed2512 to
8210a96
Compare
Drop the CloudNativePG cluster dependency, the Database CR and goose. The chart no longer provisions a database; database.connection.* has to point at one that already exists, and migrations are somebody else's job. The keyserver goes with them, along with the images/ build contexts and the workflow that built them. Its remaining job, putting a JWKS on disk for PGRST_JWT_SECRET, is now files/fetch-jwks.sh on a digest-pinned Minimus curl image. It validates each response per key and renames it into place, so a bad fetch cannot replace working keys, and it runs as an init container so PostgREST does not start without a usable key set. jwks.refresh optionally keeps it resident to pick up rotations, sending SIGUSR2 so PostgREST re-reads the file. Off by default. Add probes against PostgREST's admin server, which the chart never had, and checksum annotations, without which a config or credential change never reached a running pod. Short-lived peek tokens can no longer be minted: the keyserver generated its own key pair and served them, and a fetch-only script has no equivalent. od360's compose stack runs the same script; keep the two in sync.
emmanuel
force-pushed
the
refactor/postgrest-stateless
branch
from
August 8, 2026 06:29
0ed4a35 to
f84fa5f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The chart provisioned a CloudNativePG cluster, ran goose migrations, and shipped a keyserver that minted its own JWTs. None of that belongs in a chart described as "a PostgREST data api". This cuts it back to that, plus the Service and Ingress around it.
A default install is now a Deployment with one init container and one container, a Service, two ConfigMaps and a Secret.
Removed
clustersub-chart dependency,Chart.lock, and thecluster:values block: rolesgoose/anon/peek/view/edit, databases, storagetemplates/database.yaml, the CNPGDatabaseCRtemplates/migrations.yaml, thegooseConfigMap, themigrationsSecret,database.migrations.*, thedatabase.migrationshelper, and the migration init containerwaitinit container, which installed postgresql-client at runtime and polled with the migrations secretkeyserver:values, and theauth.app.localhostingress ruleimages/goose/,images/keyserver/, and.github/workflows/docker.yaml, which had nothing left to buildpostgrest.jwt.claims, unreferenced and already broken: it treatedapplication.jwt.claimas a scalar when it is a mapIngress is unchanged apart from dropping the keyserver rule.
JWKS
files/fetch-jwks.shcovers the one thing the keyserver did that PostgREST needs: getting a key set onto disk forPGRST_JWT_SECRET. It runs as an init container onreg.mini.dev/curl-fips(Minimus hardened FIPS curl, digest pinned, no runtime package installs), fetches once and exits, so PostgREST does not start without a usable key set and a bad URL blocks the rollout instead of producing a pod that never goes ready.Responses are validated before being renamed into place, so a failed or malformed fetch leaves working keys alone. The check runs per key, which matters because a document can carry the required
kty,useandkidspread across different keys and still satisfy a substring check without holding a usable key.jwks.require.kty/use/kidtighten it; they default to empty, sinceuseandkidare optional in RFC 7517.jwks.refreshoptionally keeps the same script resident to pick up rotations, sending SIGUSR2 so PostgREST re-reads the file. Off by default: it only helps where signing keys actually rotate, and a resident container that crashes drops the pod from its Service endpoints, which a one-shot init container cannot do.Probes
The chart had none, so the Service routed traffic regardless of whether PostgREST could reach the database, and a rollout reported success as soon as the container was running.
startupProbeandlivenessProbenow target PostgREST's admin server (/readyand/live).There is no readiness probe. It would drop the pod from its Service during a database outage, and callers would get a connection refusal rather than PostgREST's own 503, which names the failure. The startup probe covers the case that matters, since it gates the rollout and is not re-evaluated afterwards. Worth revisiting above one replica, where readiness would let you shed a single bad pod.
checksum/configandchecksum/secretwere added alongside: without them, changingdatabase.connection.*or anyPGRST_*setting rewrote the ConfigMap or Secret but left the pod template untouched, so nothing rolled and the change did not take effect.Breaking
database.connection.*must point at one that exists, with theanonrole and the roles the claim mapping resolves to already created.keyserver.*is replaced byjwks.*. Service port 8000 and theauth.app.localhostingress rule are gone.peektokens can no longer be minted. The keyserver generated its own key pair, merged it into the served document and issued them from/peek. A fetch-only script has no equivalent, and/jwksgoes with it. Thepeekrole itself is untouched.database.migrations.*and goose are gone. Run migrations out of band.appVersionset, rather than floating onlatest.Upgrading an existing release will drop the CNPG
Clusterfrom the release manifest, which Helm will delete. That path has not been worked out here and needs handling before anyone upgrades in place.Testing
On kind (v1.35.0) against a real Postgres: the gating fetch writes and exits cleanly; a changed
kidproduces one write and one SIGUSR2; a 200 carrying a non-JWKS is rejected and the previous key set survives; an unreachable JWKS leaves the new pod inInit:Errorwith the old one still serving; killing the database leaves the pod Ready and answering 503 withPGRST000; and a rollout against an unreachable database leaves the new pod at0/1while the old one keeps serving.Against the real image, the failure matrix (HTTP 500, a 200 carrying HTML, an empty body, a truncated body, 404, unreachable host) leaves existing keys intact in every case.
uki-code/od360#4363 runs the same script for docker-compose; this copy is the authoritative one.