Skip to content

Supabase on Kubernetes

Architecture authority: ADR 0018.

Reviewed 2026-07-14. Supabase officially recommends Docker for self-hosting and lists Kubernetes options as community-driven. FractalOps therefore consumes the community Helm chart directly; it does not fork the chart or add a Supabase operator. assembly.manifest.json is the current chart, source-commit, archive-digest, and component-image SSOT. Every Git revision is immutable, while the supabase-stable-channel.yml workflow checks the upstream non-prerelease channel every six hours, resolves every enabled image digest, and advances the SSOT only after tests and a live Kubernetes API dry-run. The sole Argo CD Application is supabase; its environment overlay renders one Helm release with Studio and every enabled API component. The former in-repository Supabase, Storage, and Realtime charts are deleted.

The repository’s operator is explicitly early-stage and has no published image. Adopting it would add an unstable CRD and a locally built controller without removing a required capability, so the standard Helm path is the smaller and more portable choice.

  • The chart owns Supabase Deployments, Services, component environment wiring, native secret references, Studio, Auth, PostgREST, Realtime, Storage, postgres-meta, and the internal Kong API gateway.
  • CloudNativePG owns PostgreSQL lifecycle and login roles. Backend Git migrations own schemas, grants, RLS, and data repair. External Secrets projects OpenBao values into standard Kubernetes Secrets.
  • SeaweedFS provides the S3-compatible durable Storage backend. The chart’s embedded PostgreSQL, MinIO, and application PVCs remain disabled.
  • The object-storage namespace is the durable SeaweedFS authority. Supabase uses its bucket-scoped SUPABASE_STORAGE_* identity; the legacy seaweedfs namespace and evidence-writer credentials are not valid runtime targets.
  • Cluster ingress is composed in the same Application. Kong remains internal because the official self-host topology uses it for API routing and authentication plugins; cluster routing must not reimplement that policy.

platform/k8s/argocd/runtime/resources/supabase.application.yaml keeps the live Application identity and points it at platform/k8s/environments/lxc-pve-lab/supabase. It has no automated sync policy. platform/k8s/bootstrap_supabase.sh seeds runtime secrets, waits for the native ExternalSecret, and publishes this one Application; it does not synchronize it.

  1. Prove the CNPG backup, migration ledger, worker capacity, SeaweedFS bucket, immutable images, Realtime tenant prerequisites, and rollback pin.
  2. Preserve the previous Storage data volume as a read-only migration source and remove its Argo ownership once. First compare storage.objects with the volume. When metadata owns objects, migrate through the Storage API. When metadata is empty, archive the orphan bytes with archive-storage-orphans.job.yaml; it mounts the source read-only and requires count, byte-total, and authenticated SeaweedFS readback equality. Keep the source PVC until restore proof passes.
  3. Manually synchronize supabase. Normal server-side apply creates the upstream release and foreground pruning removes resources absent from the new source.
  4. Prove Auth, REST, Realtime, and Storage through supabase.yamon.io; prove Studio through studio.yamon.io. Confirm / and /pg/ are not public API routes.

The API Ingress exposes only enabled routes declared by upstream Kong: /auth/v1, /rest/v1, /realtime/v1, /storage/v1, and OAuth discovery. Studio’s Kong / fallback and postgres-meta /pg/ remain private. The Storage orphan operation is a one-time live handoff, not desired state. Never add permanent Argo Force=true or Replace=true annotations to the Application or its resources.

  • 0.7.1 changes component environment values to Kubernetes-native arrays. Values written for 0.6.x do not render correctly and must not be reused.
  • The chart supplies one database password to component roles such as supabase_admin, supabase_auth_admin, authenticator, and supabase_storage_admin; CNPG must reconcile those roles to the same referenced password before rollout.
  • Realtime requires _realtime before its release migration and a realtime tenant schema. Its generic /healthcheck can return success while tenant migrations failed; readiness proof must inspect /api/tenants/fractalops-live/health and require data.healthy=true. Preserve SELF_HOST_TENANT_NAME=fractalops-live; upstream seeds use that value as external_id, so changing it creates a different tenant instead of adopting the existing native control row.
  • The chart’s embedded database and MinIO defaults are not production authority in this deployment. A values reset must not silently re-enable them.
  • Storage v1.48.10 and v1.60.4 use the same TenantLocation key shape: <tenant>/<bucket>/<versioned-object>. Keep TENANT_ID=storage-single-tenant across the file-to-S3 cutover; changing it silently relocates every object.
  • Standard CNPG PostgreSQL lacks pg_graphql. PostgREST therefore exposes only migration-owned public,storage; /graphql/v1 remains closed. Enable GraphQL only after a CNPG-compatible immutable image provides the extension and a Backend migration owns graphql_public.
Terminal window
uv run --frozen --group test python scripts/update-supabase-stable.py --dry-run
kustomize build --enable-helm \
platform/k8s/environments/lxc-pve-lab/supabase >/tmp/supabase-rendered.yaml
uv run pytest -q \
platform/k8s/composition/supabase/supabase_upstream_contract_test.py \
platform/k8s/cue_generate_native_values_test.py \
platform/k8s/openbao_bootstrap_test.py
platform/k8s/bootstrap_supabase.sh

The bootstrap command mutates OpenBao projections and publishes the Application, so run it only from an authorized operator environment. It intentionally leaves sync manual.

Authoritative references: Supabase self-hosting, official Docker topology, community Kubernetes repository, chart releases, Storage v1.48.10 locator, Storage v1.60.4 locator, and Realtime v2.102.3 release entrypoint.