feat: add supabase deployer
This commit is contained in:
483
roles/supabase/tasks/main.yml
Normal file
483
roles/supabase/tasks/main.yml
Normal file
@@ -0,0 +1,483 @@
|
||||
---
|
||||
# Deploy Supabase on OpenShift via the supabase-community Helm chart.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - KUBECONFIG set in environment (or ocp_kubeconfig host var)
|
||||
# - cert-manager installed with a ClusterIssuer matching supabase_tls_cluster_issuer
|
||||
# - A ReadWriteOnce-capable StorageClass set via supabase_storage_class
|
||||
# - Vault variables defined (see meta/argument_specs.yml for the full list)
|
||||
#
|
||||
# Architecture:
|
||||
# - Kong API gateway is the single ingress point (Route: supabase_studio_host)
|
||||
# - Studio dashboard is served through Kong
|
||||
# - DB runs as root — anyuid SCC granted to the db ServiceAccount
|
||||
# - Vector reads /var/log/pods — privileged SCC granted to vector SA
|
||||
# - All secrets are created as K8s Secrets and referenced via secretRef
|
||||
#
|
||||
# OpenShift SCC notes:
|
||||
# - supabase-db ServiceAccount → anyuid SCC (postgres runs as root)
|
||||
# - supabase-vector ServiceAccount → privileged SCC (hostPath to /var/log/pods)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 0: Resolve secrets from Vault (generate and store if missing)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Resolve Supabase secrets from Vault
|
||||
ansible.builtin.include_tasks: vault_secrets.yml
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 1: Namespace
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create Supabase namespace
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ supabase_namespace }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 2: SCC grants (ClusterRoleBindings before helm so pods start clean)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create ClusterRole granting anyuid SCC (supabase db)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-db-anyuid-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [anyuid]
|
||||
|
||||
- name: Bind anyuid SCC to supabase db ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-db-anyuid-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_db_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-db-anyuid-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
- name: Create ClusterRole granting anyuid SCC (supabase kong)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-kong-anyuid-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [anyuid]
|
||||
|
||||
- name: Bind anyuid SCC to supabase kong ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-kong-anyuid-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_kong_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-kong-anyuid-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
- name: Create ClusterRole granting anyuid SCC (supabase functions)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-functions-anyuid-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [anyuid]
|
||||
|
||||
- name: Bind anyuid SCC to supabase functions ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-functions-anyuid-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_functions_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-functions-anyuid-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
- name: Create ClusterRole granting privileged SCC (supabase vector)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-vector-privileged-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [privileged]
|
||||
|
||||
- name: Bind privileged SCC to supabase vector ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-vector-privileged-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_vector_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-vector-privileged-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 3: Kubernetes Secrets (no_log — referenced by helm via secretRef)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create JWT secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_jwt }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
anonKey: "{{ __supabase_anon_key }}"
|
||||
serviceKey: "{{ __supabase_service_key }}"
|
||||
secret: "{{ __supabase_jwt_secret }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create DB secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_db }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
password: "{{ __supabase_db_password }}"
|
||||
database: "{{ supabase_db_name }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create dashboard secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_dashboard }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: "{{ __supabase_dashboard_username }}"
|
||||
password: "{{ __supabase_dashboard_password }}"
|
||||
openAiApiKey: "{{ __supabase_openai_api_key }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create analytics secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_analytics }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
publicAccessToken: "{{ __supabase_analytics_public_token }}"
|
||||
privateAccessToken: "{{ __supabase_analytics_private_token }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create realtime secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_realtime }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
secretKeyBase: "{{ __supabase_realtime_secret_key_base }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create meta secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_meta }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
cryptoKey: "{{ __supabase_meta_crypto_key }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create SMTP secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_smtp }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: "{{ vault_supabase_smtp_username | default('') }}" # set in host_vars
|
||||
password: "{{ vault_supabase_smtp_password | default('') }}" # set in host_vars
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 4: Optional Docker Hub pull secret
|
||||
# Avoids anonymous rate limiting for docker.io images (kong, vector, etc.)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create Docker Hub pull secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supabase-dockerhub
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: >-
|
||||
{{ {'auths': {'https://index.docker.io/v1/': {
|
||||
'username': supabase_dockerhub_username,
|
||||
'password': vault_supabase_dockerhub_token,
|
||||
'auth': (supabase_dockerhub_username + ':' + vault_supabase_dockerhub_token) | b64encode
|
||||
}}} | to_json | b64encode }}
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: supabase_dockerhub_enabled | bool
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 5: Helm install / upgrade
|
||||
# ------------------------------------------------------------------
|
||||
- name: Deploy Supabase via Helm
|
||||
kubernetes.core.helm:
|
||||
release_name: "{{ supabase_release_name }}"
|
||||
chart_ref: supabase
|
||||
chart_repo_url: "{{ supabase_chart_repo_url }}"
|
||||
chart_version: "{{ supabase_chart_version | default(omit, true) }}"
|
||||
release_namespace: "{{ supabase_namespace }}"
|
||||
create_namespace: false
|
||||
wait: false
|
||||
values:
|
||||
# Use predictable names so SCC bindings and Route target can reference them
|
||||
deployment:
|
||||
db:
|
||||
fullnameOverride: "{{ supabase_db_fullname }}"
|
||||
kong:
|
||||
fullnameOverride: "{{ supabase_kong_fullname }}"
|
||||
functions:
|
||||
fullnameOverride: "{{ supabase_functions_fullname }}"
|
||||
vector:
|
||||
fullnameOverride: "{{ supabase_vector_fullname }}"
|
||||
|
||||
# Secrets via secretRef — keep secret material out of Helm values
|
||||
secret:
|
||||
jwt:
|
||||
secretRef: "{{ __supabase_secret_jwt }}"
|
||||
db:
|
||||
secretRef: "{{ __supabase_secret_db }}"
|
||||
dashboard:
|
||||
secretRef: "{{ __supabase_secret_dashboard }}"
|
||||
analytics:
|
||||
secretRef: "{{ __supabase_secret_analytics }}"
|
||||
realtime:
|
||||
secretRef: "{{ __supabase_secret_realtime }}"
|
||||
meta:
|
||||
secretRef: "{{ __supabase_secret_meta }}"
|
||||
smtp:
|
||||
secretRef: "{{ __supabase_secret_smtp }}"
|
||||
|
||||
# Storage — all PVCs use the configured StorageClass
|
||||
persistence:
|
||||
db:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_db_storage_size }}"
|
||||
functions:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_functions_storage_size }}"
|
||||
imgproxy:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_imgproxy_storage_size }}"
|
||||
minio:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_minio_storage_size }}"
|
||||
|
||||
# Explicit docker.io registry prefix on all docker.io images.
|
||||
# OpenShift ImageContentSourcePolicy rewrites bare image refs (e.g. kong/kong)
|
||||
# to the configured mirror; supplying the full docker.io/ hostname forces the
|
||||
# pull against the real registry and lets the pull secret take effect.
|
||||
image:
|
||||
analytics:
|
||||
repository: docker.io/supabase/logflare
|
||||
auth:
|
||||
repository: docker.io/supabase/gotrue
|
||||
db:
|
||||
repository: docker.io/supabase/postgres
|
||||
functions:
|
||||
repository: docker.io/supabase/edge-runtime
|
||||
imgproxy:
|
||||
repository: docker.io/darthsim/imgproxy
|
||||
kong:
|
||||
repository: docker.io/kong/kong
|
||||
meta:
|
||||
repository: docker.io/supabase/postgres-meta
|
||||
realtime:
|
||||
repository: docker.io/supabase/realtime
|
||||
rest:
|
||||
repository: docker.io/postgrest/postgrest
|
||||
storage:
|
||||
repository: docker.io/supabase/storage-api
|
||||
studio:
|
||||
repository: docker.io/supabase/studio
|
||||
vector:
|
||||
repository: docker.io/timberio/vector
|
||||
|
||||
# Docker Hub pull secret (only wired in when enabled)
|
||||
imagePullSecrets: "{{ [{'name': 'supabase-dockerhub'}] if supabase_dockerhub_enabled | bool else [] }}"
|
||||
|
||||
# Disable chart's nginx ingress — we create an OCP Route below
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# Studio public URL (used by auth callbacks and the browser)
|
||||
environment:
|
||||
studio:
|
||||
SUPABASE_PUBLIC_URL: "https://{{ supabase_studio_host }}"
|
||||
STUDIO_DEFAULT_ORGANIZATION: "{{ supabase_org_name }}"
|
||||
STUDIO_DEFAULT_PROJECT: "{{ supabase_project_name }}"
|
||||
NEXT_PUBLIC_ENABLE_LOGS: "true"
|
||||
|
||||
# SMTP
|
||||
auth:
|
||||
environment:
|
||||
GOTRUE_SMTP_HOST: "{{ supabase_smtp_host }}"
|
||||
GOTRUE_SMTP_PORT: "{{ supabase_smtp_port | string }}"
|
||||
GOTRUE_SMTP_ADMIN_EMAIL: "{{ supabase_smtp_admin_email }}"
|
||||
GOTRUE_SMTP_SENDER_NAME: "{{ supabase_smtp_sender_name }}"
|
||||
GOTRUE_MAILER_AUTOCONFIRM: "{{ 'false' if supabase_smtp_enabled else 'true' }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 6: OpenShift Route (edge TLS, cert-manager certificate)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create TLS Certificate for Supabase
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: supabase-tls
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
spec:
|
||||
secretName: supabase-tls
|
||||
issuerRef:
|
||||
name: "{{ supabase_tls_cluster_issuer }}"
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- "{{ supabase_studio_host }}"
|
||||
|
||||
- name: Create OpenShift Route for Supabase (Kong gateway)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: route.openshift.io/v1
|
||||
kind: Route
|
||||
metadata:
|
||||
name: supabase
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "{{ supabase_tls_cluster_issuer }}"
|
||||
spec:
|
||||
host: "{{ supabase_studio_host }}"
|
||||
to:
|
||||
kind: Service
|
||||
name: "{{ supabase_kong_fullname }}"
|
||||
weight: 100
|
||||
port:
|
||||
targetPort: http
|
||||
tls:
|
||||
termination: edge
|
||||
insecureEdgeTerminationPolicy: Redirect
|
||||
certificate: ""
|
||||
key: ""
|
||||
caCertificate: ""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 7: Wait for Kong deployment to be healthy
|
||||
# ------------------------------------------------------------------
|
||||
- name: Wait for Kong deployment to be ready
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
name: "{{ supabase_kong_fullname }}"
|
||||
register: __supabase_kong_deploy
|
||||
until: >-
|
||||
__supabase_kong_deploy.resources | length > 0 and
|
||||
(__supabase_kong_deploy.resources[0].status.readyReplicas | default(0)) >= 1
|
||||
retries: "{{ __supabase_wait_retries }}"
|
||||
delay: 10
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 8: Summary
|
||||
# ------------------------------------------------------------------
|
||||
- name: Display Supabase deployment summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Supabase deployment complete!"
|
||||
- " Namespace : {{ supabase_namespace }}"
|
||||
- " Studio URL : https://{{ supabase_studio_host }}"
|
||||
- " Kong service : {{ supabase_kong_fullname }}:8000"
|
||||
- " DB PVC : {{ supabase_db_storage_size }} ({{ supabase_storage_class }})"
|
||||
- " SMTP enabled : {{ supabase_smtp_enabled }}"
|
||||
- ""
|
||||
- " Login with vault_supabase_dashboard_username / vault_supabase_dashboard_password"
|
||||
Reference in New Issue
Block a user