diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..cd1615c --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,25 @@ +# Context: Supabase Operator + +Glossary of domain terms for this repo. Behavior language only, no implementation detail. + +## Terms + +### SupabaseProject +The single CRD of this operator. Namespace scoped. Describes one Supabase instance: which external database and object storage to use, and per component settings. The operator deploys and manages all Supabase components (Kong, Auth, PostgREST, Realtime, Storage API, Meta, Studio) from it. + +### External dependency +A service the operator needs but never creates or manages: PostgreSQL and S3 compatible object storage. The user provides both and hands the operator connection credentials through Secrets. The operator only validates and consumes them. + +### Secret contract +The exact key names a credentials Secret must contain so the operator can consume it. +- Database Secret keys: `host`, `port`, `database`, `username`, `password` +- Storage Secret keys: `endpoint`, `region`, `bucket`, `accessKeyId`, `secretAccessKey` + +### Quickstart +The documented golden path for a first time user: install the operator, install external dependencies with well known Helm charts (CloudNativePG for PostgreSQL, MinIO for object storage), create the credential Secrets, apply one SupabaseProject, reach a working Studio via port-forward. Target is a working Studio on a fresh cluster in minutes. + +### Database initialization +The one time setup the operator runs against the external PostgreSQL after a project is created: creating the `_supabase` database, Supabase schemas, service roles, and extensions. Requires a superuser grade connection and the `supabase/postgres` image, because Supabase specific extensions must be present. + +### dev scaffolding +The manifests under `dev/` used by maintainers for local development. Not user facing, not part of the quickstart. diff --git a/docs/adr/0001-quickstart-via-charts-not-managed-mode.md b/docs/adr/0001-quickstart-via-charts-not-managed-mode.md new file mode 100644 index 0000000..ee5f345 --- /dev/null +++ b/docs/adr/0001-quickstart-via-charts-not-managed-mode.md @@ -0,0 +1,31 @@ +# ADR 0001: Quickstart uses well known Helm charts, not an operator managed mode + +Date: 2026-07-25 + +Status: accepted + +## Context + +Issue #18 planned a "managed mode": `spec.database.managed: true` would make the operator provision an in-cluster PostgreSQL StatefulSet, and a similar flag would provision MinIO. The goal was a first working project in under 5 minutes on a bare cluster. + +Building that inside the operator means the operator takes on database lifecycle work: storage, upgrades, credentials, failure recovery. That is a large and permanent maintenance load, and mature tools already do it better. + +A second constraint: the operator only supports the `supabase/postgres` image, because database initialization needs Supabase specific extensions (pgjwt, pg_net and others) and superuser access. Any install path must produce a PostgreSQL running that image. + +## Decision + +Do not build managed mode. Keep PostgreSQL and S3 strictly user provided. + +Instead, ship a documented quickstart that installs the dependencies with well known Helm charts: + +- PostgreSQL via the CloudNativePG operator, with the cluster image set to `supabase/postgres`. Known required settings: `postgresUID: 101`, `postgresGID: 102` (the image does not use the CNPG default uid 26), and `enableSuperuserAccess: true` (database initialization needs superuser). +- Object storage via the official MinIO chart (`charts.min.io`), standalone mode, bucket declared through chart values. + +The quickstart manifests live in the repo so users can apply them directly and docs cannot drift from them. + +## Consequences + +- The operator stays thin: it never owns database or storage lifecycle. +- The 5 minute goal is met by documentation quality, not new operator code paths. +- The CNPG plus `supabase/postgres` combination is community proven but not officially supported by either project; the quickstart must be verified end to end whenever images are bumped. +- Issue #18 Phase A tasks about managed mode are superseded by this decision. diff --git a/docs/quickstart/VALIDATION.md b/docs/quickstart/VALIDATION.md new file mode 100644 index 0000000..268611b --- /dev/null +++ b/docs/quickstart/VALIDATION.md @@ -0,0 +1,239 @@ +# Quickstart validation + +This file records a successful run on a fresh kind cluster. + +## Tested versions + +```text +kind v0.32.0 +Kubernetes v1.36.1 +CloudNativePG chart 0.29.0 +CloudNativePG 1.30.0 +MinIO chart 5.4.0 +MinIO RELEASE.2024-12-18T13-15-44Z +cert-manager v1.21.0 +Supabase PostgreSQL 15.14.1.021 +Supabase Operator image ghcr.io/strrl/supabase-operator:358f551-dirty +Supabase Operator chart 0.1.0 +``` + +## Commands + +Create the cluster and add the chart sources. + +```sh +kind create cluster --name quickstart-claude --wait 120s + +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm repo add minio https://charts.min.io/ +helm repo add jetstack https://charts.jetstack.io +helm repo update cnpg minio jetstack +``` + +Install CloudNativePG and create PostgreSQL. + +```sh +helm upgrade --install cnpg cnpg/cloudnative-pg \ + --kube-context kind-quickstart-claude \ + --namespace cnpg-system \ + --create-namespace \ + --version 0.29.0 \ + --wait \ + --timeout 5m + +kubectl --context kind-quickstart-claude \ + apply -f docs/quickstart/postgres-cluster.yaml +kubectl --context kind-quickstart-claude \ + wait --namespace supabase-quickstart \ + --for=condition=Ready \ + cluster/supabase-postgres \ + --timeout 10m +kubectl --context kind-quickstart-claude \ + get cluster supabase-postgres \ + --namespace supabase-quickstart \ + -o custom-columns='NAME:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,IMAGE:.spec.imageName,UID:.spec.postgresUID,GID:.spec.postgresGID' +``` + +Confirm that PostgreSQL accepts a TLS connection. + +```sh +kubectl --context kind-quickstart-claude \ + run postgres-tls-check \ + --namespace supabase-quickstart \ + --image=postgres:15-alpine \ + --restart=Never \ + --command \ + -- sh -c 'PGPASSWORD=quickstart-postgres-password psql "host=supabase-postgres-rw port=5432 dbname=postgres user=postgres sslmode=require" -Atc "select ssl from pg_stat_ssl where pid = pg_backend_pid()"' +kubectl --context kind-quickstart-claude \ + wait --namespace supabase-quickstart \ + --for=jsonpath='{.status.phase}'=Succeeded \ + pod/postgres-tls-check \ + --timeout 2m +kubectl --context kind-quickstart-claude \ + logs postgres-tls-check \ + --namespace supabase-quickstart +``` + +Install MinIO and confirm the declared bucket. + +```sh +helm upgrade --install minio minio/minio \ + --kube-context kind-quickstart-claude \ + --namespace supabase-quickstart \ + --create-namespace \ + --version 5.4.0 \ + --values docs/quickstart/minio/values.yaml \ + --wait \ + --timeout 5m +kubectl --context kind-quickstart-claude \ + rollout status deployment/minio \ + --namespace supabase-quickstart \ + --timeout 5m +kubectl --context kind-quickstart-claude \ + run minio-check \ + --namespace supabase-quickstart \ + --image=quay.io/minio/mc:RELEASE.2024-11-21T17-21-54Z \ + --restart=Never \ + --command \ + -- sh -c 'mc alias set quickstart http://minio:9000 storage-admin quickstart-minio-password >/dev/null && mc stat quickstart/supabase-storage' +kubectl --context kind-quickstart-claude \ + wait --namespace supabase-quickstart \ + --for=jsonpath='{.status.phase}'=Succeeded \ + pod/minio-check \ + --timeout 2m +kubectl --context kind-quickstart-claude \ + logs minio-check \ + --namespace supabase-quickstart +``` + +Install cert-manager, apply the Secrets, build the operator, and install its +chart. + +```sh +helm upgrade --install cert-manager jetstack/cert-manager \ + --kube-context kind-quickstart-claude \ + --namespace cert-manager \ + --create-namespace \ + --version v1.21.0 \ + --set crds.enabled=true \ + --wait \ + --timeout 5m + +kubectl --context kind-quickstart-claude \ + apply -f docs/quickstart/database-secret.yaml +kubectl --context kind-quickstart-claude \ + apply -f docs/quickstart/storage-secret.yaml +kubectl --context kind-quickstart-claude \ + apply -f docs/quickstart/studio-basic-auth-secret.yaml + +OPERATOR_IMAGE_TAG="$(bash hack/commit-hash.sh)" +make image +kind load docker-image \ + "ghcr.io/strrl/supabase-operator:${OPERATOR_IMAGE_TAG}" \ + --name quickstart-claude + +helm upgrade --install supabase-operator ./helm/supabase-operator \ + --kube-context kind-quickstart-claude \ + --namespace supabase-operator-system \ + --create-namespace \ + --set image.repository=ghcr.io/strrl/supabase-operator \ + --set "image.tag=${OPERATOR_IMAGE_TAG}" \ + --set image.pullPolicy=IfNotPresent \ + --wait \ + --timeout 5m +``` + +Apply the project and check every component. + +```sh +kubectl --context kind-quickstart-claude \ + apply -f docs/quickstart/supabase-project.yaml +kubectl --context kind-quickstart-claude \ + wait --namespace supabase-quickstart \ + --for=jsonpath='{.status.phase}'=Running \ + supabaseproject/quickstart \ + --timeout 10m +kubectl --context kind-quickstart-claude \ + get supabaseproject quickstart \ + --namespace supabase-quickstart \ + -o custom-columns='NAME:.metadata.name,PHASE:.status.phase' +kubectl --context kind-quickstart-claude \ + get deployment \ + --namespace supabase-quickstart \ + -o custom-columns='NAME:.metadata.name,READY:.status.readyReplicas,DESIRED:.spec.replicas' +``` + +In one terminal, forward Kong. + +```sh +kubectl --context kind-quickstart-claude \ + port-forward service/quickstart-kong 18000:8000 \ + --namespace supabase-quickstart +``` + +In another terminal, check the unauthenticated and authenticated responses. + +```sh +curl \ + --output /dev/null \ + --silent \ + --write-out 'HTTP %{http_code}\n' \ + http://127.0.0.1:18000/ +curl \ + --user supabase:quickstart-studio-password \ + --location \ + --output /dev/null \ + --silent \ + --show-error \ + --write-out 'HTTP %{http_code}\n' \ + http://127.0.0.1:18000/ +``` + +## Selected outputs + +```text +CloudNativePG: +NAME READY IMAGE UID GID +supabase-postgres True supabase/postgres:15.14.1.021 101 102 + +Database TLS check: +t + +MinIO: +Name : supabase-storage +Type : folder +Location : us-east-1 +Objects count: 0 + +SupabaseProject: +NAME PHASE +quickstart Running + +Deployments: +minio 1 1 +quickstart-auth 1 1 +quickstart-kong 1 1 +quickstart-meta 1 1 +quickstart-postgrest 1 1 +quickstart-realtime 1 1 +quickstart-storage 1 1 +quickstart-studio 1 1 + +Studio through Kong: +HTTP 401 +HTTP 200 +``` + +## Static checks + +```sh +kubectl --context kind-quickstart-claude \ + apply --dry-run=client -f docs/quickstart/ +helm template minio minio/minio \ + --kube-context kind-quickstart-claude \ + --namespace supabase-quickstart \ + --version 5.4.0 \ + --values docs/quickstart/minio/values.yaml +helm lint ./helm/supabase-operator +git diff --check +``` diff --git a/docs/quickstart/database-secret.yaml b/docs/quickstart/database-secret.yaml new file mode 100644 index 0000000..fc848c9 --- /dev/null +++ b/docs/quickstart/database-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: supabase-db-credentials + namespace: supabase-quickstart +type: Opaque +stringData: + host: supabase-postgres-rw.supabase-quickstart.svc.cluster.local + port: "5432" + database: postgres + username: postgres + password: quickstart-postgres-password diff --git a/docs/quickstart/minio/values.yaml b/docs/quickstart/minio/values.yaml new file mode 100644 index 0000000..a667455 --- /dev/null +++ b/docs/quickstart/minio/values.yaml @@ -0,0 +1,23 @@ +mode: standalone + +rootUser: storage-admin +rootPassword: quickstart-minio-password + +replicas: 1 + +persistence: + enabled: true + size: 1Gi + +resources: + requests: + memory: 256Mi + cpu: 250m + limits: + memory: 512Mi + cpu: 500m + +buckets: + - name: supabase-storage + policy: none + purge: false diff --git a/docs/quickstart/postgres-cluster.yaml b/docs/quickstart/postgres-cluster.yaml new file mode 100644 index 0000000..d293431 --- /dev/null +++ b/docs/quickstart/postgres-cluster.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: supabase-quickstart +--- +apiVersion: v1 +kind: Secret +metadata: + name: postgres-superuser + namespace: supabase-quickstart +type: kubernetes.io/basic-auth +stringData: + username: postgres + password: quickstart-postgres-password +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: supabase-postgres + namespace: supabase-quickstart +spec: + instances: 1 + imageName: supabase/postgres:15.14.1.021 + postgresUID: 101 + postgresGID: 102 + enableSuperuserAccess: true + superuserSecret: + name: postgres-superuser + storage: + size: 1Gi diff --git a/docs/quickstart/storage-secret.yaml b/docs/quickstart/storage-secret.yaml new file mode 100644 index 0000000..465b971 --- /dev/null +++ b/docs/quickstart/storage-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: supabase-storage-credentials + namespace: supabase-quickstart +type: Opaque +stringData: + endpoint: http://minio.supabase-quickstart.svc.cluster.local:9000 + region: us-east-1 + bucket: supabase-storage + accessKeyId: storage-admin + secretAccessKey: quickstart-minio-password diff --git a/docs/quickstart/studio-basic-auth-secret.yaml b/docs/quickstart/studio-basic-auth-secret.yaml new file mode 100644 index 0000000..18bbb01 --- /dev/null +++ b/docs/quickstart/studio-basic-auth-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: supabase-studio-basic-auth + namespace: supabase-quickstart +type: Opaque +stringData: + username: supabase + password: quickstart-studio-password diff --git a/docs/quickstart/supabase-project.yaml b/docs/quickstart/supabase-project.yaml new file mode 100644 index 0000000..e8eb638 --- /dev/null +++ b/docs/quickstart/supabase-project.yaml @@ -0,0 +1,18 @@ +apiVersion: supabase.strrl.dev/v1alpha1 +kind: SupabaseProject +metadata: + name: quickstart + namespace: supabase-quickstart +spec: + projectId: quickstart + database: + secretRef: + name: supabase-db-credentials + sslMode: require + storage: + secretRef: + name: supabase-storage-credentials + forcePathStyle: true + studio: + dashboardBasicAuthSecretRef: + name: supabase-studio-basic-auth