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.yaml
Or install from source:
git clone https://github.com/strrl/supabase-operator
cd supabase-operator
make install
make deploy
kubectl 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-password
kubectl 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-key
Note: Use camelCase for secret keys:
accessKeyId
andsecretAccessKey
(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-creds
Apply the manifest:
kubectl apply -f my-supabase.yaml
kubectl get supabaseproject my-supabase -o yaml
Check 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=supabase
Access Kong API Gateway:
kubectl port-forward svc/my-supabase-kong 8000:8000
Requests 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 functionsuuid-ossp
- UUID generationpg_stat_statements
- Query statistics
Schemas:
auth
- Authentication datastorage
- File metadatarealtime
- Real-time subscriptions
Roles:
authenticator
- API request authenticator roleanon
- Anonymous access roleservice_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.0
Phases:
Pending
: Initial stateValidatingDependencies
: Checking PostgreSQL and S3DeployingSecrets
: Generating JWT secretsDeployingComponents
: Creating deploymentsRunning
: All components healthyFailed
: 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:8443
Key metrics:
controller_runtime_reconcile_total
- Total reconciliationscontroller_runtime_reconcile_errors_total
- Reconciliation errorscontroller_runtime_reconcile_time_seconds
- Reconciliation durationworkqueue_depth
- Controller work queue depthworkqueue_adds_total
- Items added to work queue
If using Prometheus Operator, the operator includes a ServiceMonitor:
kubectl apply -k config/prometheus
Check secrets exist:
kubectl get secret postgres-config s3-config
Verify secret keys:
kubectl get secret postgres-config -o jsonpath='{.data}' | jq
Required 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}' | jq
View controller logs:
kubectl logs -n supabase-operator-system \
-l control-plane=controller-manager \
--tail=100
Check database connectivity:
kubectl run -it --rm debug --image=postgres:16 --restart=Never -- \
psql -h postgres.example.com -U postgres -d supabase
Verify 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=supabase
Check pod logs:
kubectl logs my-supabase-kong-xxx
Verify resource limits: Ensure your cluster has sufficient resources for all components.
- Go 1.22+
- Kubebuilder 4.0+
- Docker
make build
make test
make install # Install CRDs
make run # Run controller locally
make manifests # Generate CRD and RBAC
make generate # Generate deepcopy code
See 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 version
should succeed) and ensure Docker is available. -
Install Google Chrome or Chromium locally, or set
E2E_CHROME_PATH
to 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-e2e
This command creates the
supabase-operator-test-e2e
Minikube profile, runsgo test -tags=e2e
, saves screenshots to.artifacts/screenshots/
, and tears the profile down afterwards.