A Kubernetes operator for deploying and managing self-hosted Supabase instances.
The Supabase Operator enables you to deploy complete Supabase instances on Kubernetes using a single Custom Resource Definition (CRD). It manages all Supabase components including Kong API Gateway, GoTrue authentication, PostgREST, Realtime, Storage API, and Meta.
Features:
- 🚀 Deploy full Supabase stack with a single manifest
- 🔐 Automatic JWT secret generation
- 📊 Granular status reporting with per-component tracking
- 🔄 Rolling updates with health checks
- 🏗️ Multi-tenant: Multiple Supabase projects per namespace
- ☸️ Kubernetes-native with standard types and patterns
The operator manages:
- Kong: API Gateway (v2.8.1)
- Auth: GoTrue authentication service (v2.177.0)
- PostgREST: Automatic REST API (v12.2.12)
- Realtime: WebSocket server (v2.34.47)
- Storage API: File storage service (v1.25.7)
- Meta: PostgreSQL metadata service (v0.91.0)
- Studio: Supabase management UI (2025.10.01-sha-8460121)
External dependencies (user-provided):
- PostgreSQL database
- S3-compatible storage
- Kubernetes 1.33+
- kubectl configured
- External PostgreSQL database
- S3-compatible storage (MinIO, AWS S3, etc.)
kubectl apply -f https://raw.githubusercontent.com/strrl/supabase-operator/main/config/install.yamlOr install from source:
git clone https://github.com/strrl/supabase-operator
cd supabase-operator
make install
make deploykubectl create secret generic postgres-config \
  --from-literal=host=postgres.example.com \
  --from-literal=port=5432 \
  --from-literal=database=supabase \
  --from-literal=username=postgres \
  --from-literal=password=your-secure-passwordkubectl create secret generic s3-config \
  --from-literal=endpoint=https://s3.example.com \
  --from-literal=region=us-east-1 \
  --from-literal=bucket=supabase-storage \
  --from-literal=accessKeyId=your-access-key \
  --from-literal=secretAccessKey=your-secret-keyNote: Use camelCase for secret keys:
accessKeyIdandsecretAccessKey(not kebab-case)
kubectl create secret generic studio-dashboard-creds \
  --from-literal=username=supabase \
  --from-literal=password='choose-a-strong-password'Change these credentials before exposing Kong publicly.
apiVersion: supabase.strrl.dev/v1alpha1
kind: SupabaseProject
metadata:
  name: my-supabase
  namespace: default
spec:
  projectId: my-supabase-project
  database:
    secretRef:
      name: postgres-config
    sslMode: require
    maxConnections: 50
  storage:
    secretRef:
      name: s3-config
    forcePathStyle: true
  studio:
    dashboardBasicAuthSecretRef:
      name: studio-dashboard-credsApply the manifest:
kubectl apply -f my-supabase.yamlkubectl get supabaseproject my-supabase -o yamlCheck component status:
kubectl get supabaseproject my-supabase -o jsonpath='{.status.components}'The operator creates services for each component:
kubectl get services -l app.kubernetes.io/part-of=supabaseAccess Kong API Gateway:
kubectl port-forward svc/my-supabase-kong 8000:8000Requests to http://localhost:8000/ will answer 401 Unauthorized until you supply the username/password stored in studio-dashboard-creds.
The operator generates API keys and stores them in a secret named <project>-jwt within the same namespace as your SupabaseProject.
# Get the public ANON key
ANON_KEY=$(kubectl get secret my-supabase-jwt \
  -o jsonpath='{.data.anon-key}' | base64 -d)
# Get the Service Role key
SERVICE_ROLE_KEY=$(kubectl get secret my-supabase-jwt \
  -o jsonpath='{.data.service-role-key}' | base64 -d)
# Optional: discover the API endpoint
API_URL=$(kubectl get supabaseproject my-supabase \
  -o jsonpath='{.status.endpoints.api}')Use $ANON_KEY for client-side requests and $SERVICE_ROLE_KEY for trusted backend workflows.
Supabase components use the external PostgreSQL database you referenced via postgres-config. You can reuse the same credentials to connect with tools like psql.
POSTGRES_HOST=$(kubectl get secret postgres-config -o jsonpath='{.data.host}' | base64 -d)
POSTGRES_PORT=$(kubectl get secret postgres-config -o jsonpath='{.data.port}' | base64 -d)
POSTGRES_DB=$(kubectl get secret postgres-config -o jsonpath='{.data.database}' | base64 -d)
POSTGRES_USER=$(kubectl get secret postgres-config -o jsonpath='{.data.username}' | base64 -d)
POSTGRES_PASSWORD=$(kubectl get secret postgres-config -o jsonpath='{.data.password}' | base64 -d)
psql "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"If the database is only reachable inside the cluster (for example, over a private network), run kubectl run with a temporary pod or establish a VPN/tunnel that matches your deployment topology.
Override default images and resources:
spec:
  kong:
    image: kong:3.0.0
    replicas: 2
    resources:
      limits:
        memory: "4Gi"
        cpu: "1000m"
      requests:
        memory: "2Gi"
        cpu: "500m"
    extraEnv:
      - name: KONG_LOG_LEVEL
        value: "debug"| Component | Memory Limit | CPU Limit | Memory Request | CPU Request | 
|---|---|---|---|---|
| Kong | 2.5Gi | 500m | 1Gi | 250m | 
| Auth | 128Mi | 100m | 64Mi | 50m | 
| PostgREST | 256Mi | 200m | 128Mi | 100m | 
| Realtime | 256Mi | 200m | 128Mi | 100m | 
| Storage | 128Mi | 100m | 64Mi | 50m | 
| Meta | 128Mi | 100m | 64Mi | 50m | 
On first deployment, the operator automatically initializes your PostgreSQL database with:
Extensions:
- pgcrypto- Cryptographic functions
- uuid-ossp- UUID generation
- pg_stat_statements- Query statistics
Schemas:
- auth- Authentication data
- storage- File metadata
- realtime- Real-time subscriptions
Roles:
- authenticator- API request authenticator role
- anon- Anonymous access role
- service_role- Service-level access role with RLS bypass
All initialization operations are idempotent and safe to re-run.
The operator provides granular status reporting:
status:
  phase: Running
  message: "All components running"
  conditions:
    - type: Ready
      status: "True"
      reason: AllComponentsReady
    - type: Progressing
      status: "False"
      reason: ReconciliationComplete
  components:
    kong:
      phase: Running
      ready: true
      version: kong:2.8.1
      replicas: 1
      readyReplicas: 1
    auth:
      phase: Running
      ready: true
      version: supabase/gotrue:v2.177.0Phases:
- Pending: Initial state
- ValidatingDependencies: Checking PostgreSQL and S3
- DeployingSecrets: Generating JWT secrets
- DeployingComponents: Creating deployments
- Running: All components healthy
- Failed: Reconciliation error
The operator exposes Prometheus metrics at :8443/metrics:
kubectl port-forward -n supabase-operator-system \
  svc/supabase-operator-controller-manager-metrics-service 8443:8443Key metrics:
- controller_runtime_reconcile_total- Total reconciliations
- controller_runtime_reconcile_errors_total- Reconciliation errors
- controller_runtime_reconcile_time_seconds- Reconciliation duration
- workqueue_depth- Controller work queue depth
- workqueue_adds_total- Items added to work queue
If using Prometheus Operator, the operator includes a ServiceMonitor:
kubectl apply -k config/prometheusCheck secrets exist:
kubectl get secret postgres-config s3-configVerify secret keys:
kubectl get secret postgres-config -o jsonpath='{.data}' | jqRequired database secret keys: host, port, database, username, password
Required storage secret keys: endpoint, region, bucket, accessKeyId, secretAccessKey
Check status message:
kubectl get supabaseproject my-supabase -o jsonpath='{.status.message}'Check conditions:
kubectl get supabaseproject my-supabase -o jsonpath='{.status.conditions}' | jqView controller logs:
kubectl logs -n supabase-operator-system \
  -l control-plane=controller-manager \
  --tail=100Check database connectivity:
kubectl run -it --rm debug --image=postgres:16 --restart=Never -- \
  psql -h postgres.example.com -U postgres -d supabaseVerify database permissions:
The database user must have CREATEDB or superuser privileges to create extensions and schemas.
Check pod status:
kubectl get pods -l app.kubernetes.io/part-of=supabaseCheck pod logs:
kubectl logs my-supabase-kong-xxxVerify resource limits: Ensure your cluster has sufficient resources for all components.
- Go 1.22+
- Kubebuilder 4.0+
- Docker
make buildmake testmake install  # Install CRDs
make run      # Run controller locallymake manifests  # Generate CRD and RBAC
make generate   # Generate deepcopy codeSee future-considerations.md for deferred features and architectural flexibility.
Contributions welcome! Please read the design documents for context.
MIT
- v1alpha1: Core operator with basic deployment
- v1beta1: Advanced features (HA, backup, monitoring)
- v1: Production-ready with stability guarantees
See future-considerations.md for planned features.
The e2e suite spins up a temporary Minikube profile, deploys the operator, and exercises a SupabaseProject end-to-end (including capturing a Kong Studio screenshot via headless Chrome).
- 
Install Minikube ( minikube versionshould succeed) and ensure Docker is available.
- 
Install Google Chrome or Chromium locally, or set E2E_CHROME_PATHto a compatible executable. If Chrome is missing, the screenshot spec is skipped.
- 
Run: MINIKUBE_START_ARGS="--driver=docker --cpus=4 --memory=8192 --wait=all" make test-e2eThis command creates the supabase-operator-test-e2eMinikube profile, runsgo test -tags=e2e, saves screenshots to.artifacts/screenshots/, and tears the profile down afterwards.