feat(supabase): add services and statefulset for database management

feat(synapse): update ingress to use traefik ingress class and bump version

feat(syncthing-discovery): introduce syncthing discovery service with deployment and ingress

feat(syncthing-relay): add syncthing relay server with deployment and ingress configuration

fix(taiga): update liveness and readiness probes to use tcpSocket for health checks

fix(taiga): change PVC access mode to ReadWriteMany for media and static storage

feat(traefik): add icon and ignore rules for traefik service

docs(ushahidi): add notes for Redis configuration and Laravel startup probe adjustments

feat(ushahidi): implement dedicated Redis deployment for Ushahidi

fix(vllm): update deployment strategy and readiness/liveness probes for improved stability

fix(writefreely): pin writefreely image version to v0.15.1 for consistency

docs(zulip): add notes for TLS-terminating reverse proxy configuration and expected behavior
This commit is contained in:
2026-07-02 21:34:27 +00:00
parent 9f5057dff8
commit 4d983819c9
151 changed files with 3403 additions and 1303 deletions

9
supabase/app.yaml Normal file
View File

@@ -0,0 +1,9 @@
name: supabase
is: supabase
description: Supabase is an open-source Firebase alternative providing a Postgres database, authentication, instant REST and realtime APIs, edge functions, and a web dashboard.
category: developer
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/supabase.svg
latest: "1"
ignoreRules:
- WC-SC-POD # Most Supabase images run as root; only postgres enforces non-root
- WC-SC-CTR # See above

126
supabase/notes.md Normal file
View File

@@ -0,0 +1,126 @@
# Supabase Notes
## Overview
Supabase is a multi-component application. All components run in the `supabase` namespace:
| Component | Image | Role |
|-----------|-------|------|
| `db` | supabase/postgres | PostgreSQL with Supabase extensions |
| `kong` | kong/kong | API gateway, entry point for all traffic |
| `auth` | supabase/gotrue | JWT-based authentication |
| `rest` | postgrest/postgrest | Auto-generated REST API from Postgres |
| `realtime` | supabase/realtime | WebSocket subscriptions |
| `storage` + `imgproxy` | supabase/storage-api + darthsim/imgproxy | File storage (imgproxy as sidecar) |
| `meta` | supabase/postgres-meta | DB introspection for Studio |
| `studio` | supabase/studio | Web dashboard (protected by Kong basic-auth) |
Traffic flows: browser → Traefik → Kong (port 8000) → individual services.
## First-Deploy Checklist
1. Add and deploy with: `wild app add supabase && wild app deploy supabase`
2. Wait for postgres to initialize (~23 min on first boot — it runs SQL init scripts)
3. Access the Studio at `https://supabase.<your-domain>` using the dashboard credentials
4. **Replace example API keys before going to production** (see below)
## JWT Keys — MUST Replace for Production
The default `jwtSecret`, `anonKey`, and `serviceRoleKey` in `secrets.yaml` are Supabase's well-known **example keys** (publicly known, expire Jan 2027). They are sufficient for testing but **must not be used in production**.
Generate new keys using the built-in script:
```bash
wild app run supabase generate-keys
```
Then copy the output values into `secrets.yaml` and redeploy:
```bash
wild app deploy supabase
```
The postgres database stores the JWT secret in its settings (`app.settings.jwt_secret`). After replacing keys, the postgres pod must restart to pick up the new secret — a redeploy handles this.
## Postgres — Internal Database
Supabase uses its own dedicated postgres instance (`supabase/postgres` image) rather than the shared Wild Cloud postgres app. This image includes:
- ~390 extensions pre-compiled (pgjwt, wal2json, vector, pg_net, etc.)
- Supabase service roles (supabase_admin, authenticator, supabase_auth_admin, supabase_storage_admin, etc.)
- Initialization scripts for realtime, webhooks, JWT settings
All Supabase service roles (auth, storage, realtime, etc.) use the same password as the postgres admin (`dbPassword` secret). This is the Supabase default behavior.
### Bootstrap User: supabase_admin
`POSTGRES_USER` is set to `supabase_admin` (not `postgres`). This is required because the image's `migrate.sh` script connects immediately as `supabase_admin` before running any init scripts. Using `POSTGRES_USER=postgres` would make postgres the bootstrap superuser, which prevents `migrate.sh` from demoting it (PostgreSQL prohibits removing SUPERUSER from the bootstrap user).
With `POSTGRES_USER=supabase_admin`:
1. `initdb` creates `supabase_admin` as the bootstrap superuser
2. `migrate.sh` connects as `supabase_admin` and creates the `postgres` role
3. After init-scripts run, `migrate.sh` demotes `postgres` to non-superuser (this is Supabase's intended model)
The `postgres` user remains available with database-level privileges. All Supabase service roles use specific functional accounts (supabase_auth_admin, authenticator, etc.), not the postgres superuser.
### Volume Mount: /var/lib/postgresql (parent dir)
The PVC is mounted at `/var/lib/postgresql` (the parent), NOT at `/var/lib/postgresql/data` (PGDATA). This is required because Longhorn's ext4 filesystem creates a `lost+found` directory, which blocks postgres `initdb` when the PVC is mounted directly at PGDATA. With the parent-dir mount, `initdb` creates `/var/lib/postgresql/data` as a subdirectory, leaving `lost+found` at the parent level where it doesn't interfere.
## Realtime Tenant ID
The Realtime service must identify itself with the tenant ID `realtime-dev`. The Deployment sets `spec.template.spec.hostname: realtime-dev` so the Elixir application seeds itself with this tenant ID. The Kubernetes service for Realtime is also named `realtime-dev` so Kong can route to it correctly.
## Storage
File storage uses the local filesystem backend by default. Objects are stored in the `supabase-storage` PVC. The `imgproxy` container is a sidecar in the same pod, sharing the storage PVC for image transformation.
For S3-backed storage: edit `deployment-storage.yaml` to change `STORAGE_BACKEND` to `s3` and add S3 credentials as secrets.
## SMTP
SMTP is required for auth email flows (email confirmation, password reset). Configure the `smtp` app dependency and it will be wired automatically. If you want to disable email confirmation during initial testing, you can temporarily set `GOTRUE_MAILER_AUTOCONFIRM=true` in `deployment-auth.yaml`.
## Studio Dashboard Credentials
The Studio dashboard is protected by HTTP Basic Auth via Kong. Credentials:
- Username: configured via `dashboardUsername` config key (default: `supabase`)
- Password: auto-generated `dashboardPassword` secret
To view the generated password:
```bash
kubectl -n supabase get secret supabase-secrets -o jsonpath='{.data.dashboardPassword}' | base64 -d
```
## Connecting Applications
Applications connect to Supabase via the Kong API gateway:
- **REST API**: `https://supabase.<domain>/rest/v1/`
- **Auth**: `https://supabase.<domain>/auth/v1/`
- **Realtime**: `wss://supabase.<domain>/realtime/v1/`
- **Storage**: `https://supabase.<domain>/storage/v1/`
Use the `anonKey` for client-side (browser) access and `serviceRoleKey` for server-side (admin) access.
## Supabase JavaScript Client
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://supabase.<your-domain>',
'<anonKey>' // from secrets.yaml
)
```
## Troubleshooting
**Postgres won't start**: Check that the supabase/postgres image can be pulled and that the PVC is bound.
**Auth returns 500**: Postgres is not ready or the init scripts haven't completed. Check DB pod logs and wait for all init SQL to finish.
**Kong returns 401 for all requests**: The `anonKey` in secrets doesn't match the `jwtSecret`. Regenerate keys with `wild app run supabase generate-keys`.
**Realtime health check fails**: The pod hostname may not be `realtime-dev`. Check the Deployment `spec.template.spec.hostname` field.
**Studio shows "Project not found"**: Studio needs Kong to be healthy. Check Kong pod logs for declarative config errors.

View File

@@ -0,0 +1,159 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: supabase-db-init
data:
jwt.sql: |
\set jwt_secret `echo "$JWT_SECRET"`
\set jwt_exp `echo "$JWT_EXP"`
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
roles.sql: |
\set pgpass `echo "$POSTGRES_PASSWORD"`
ALTER USER authenticator WITH PASSWORD :'pgpass';
ALTER USER pgbouncer WITH PASSWORD :'pgpass';
ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
realtime.sql: |
\set pguser `echo "$POSTGRES_USER"`
create schema if not exists _realtime;
alter schema _realtime owner to :pguser;
webhooks.sql: |
BEGIN;
CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
CREATE TABLE supabase_functions.migrations (
version text PRIMARY KEY,
inserted_at timestamptz NOT NULL DEFAULT NOW()
);
INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
CREATE TABLE supabase_functions.hooks (
id bigserial PRIMARY KEY,
hook_table_id integer NOT NULL,
hook_name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
request_id bigint
);
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
CREATE FUNCTION supabase_functions.http_request()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
request_id bigint;
payload jsonb;
url text := TG_ARGV[0]::text;
method text := TG_ARGV[1]::text;
headers jsonb DEFAULT '{}'::jsonb;
params jsonb DEFAULT '{}'::jsonb;
timeout_ms integer DEFAULT 1000;
BEGIN
IF url IS NULL OR url = 'null' THEN
RAISE EXCEPTION 'url argument is missing';
END IF;
IF method IS NULL OR method = 'null' THEN
RAISE EXCEPTION 'method argument is missing';
END IF;
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
headers = '{"Content-Type": "application/json"}'::jsonb;
ELSE
headers = TG_ARGV[2]::jsonb;
END IF;
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
params = '{}'::jsonb;
ELSE
params = TG_ARGV[3]::jsonb;
END IF;
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
timeout_ms = 1000;
ELSE
timeout_ms = TG_ARGV[4]::integer;
END IF;
CASE
WHEN method = 'GET' THEN
SELECT http_get INTO request_id FROM net.http_get(url, params, headers, timeout_ms);
WHEN method = 'POST' THEN
payload = jsonb_build_object('old_record', OLD, 'record', NEW, 'type', TG_OP, 'table', TG_TABLE_NAME, 'schema', TG_TABLE_SCHEMA);
SELECT http_post INTO request_id FROM net.http_post(url, payload, params, headers, timeout_ms);
ELSE
RAISE EXCEPTION 'method argument % is invalid', method;
END CASE;
INSERT INTO supabase_functions.hooks (hook_table_id, hook_name, request_id) VALUES (TG_RELID, TG_NAME, request_id);
RETURN NEW;
END
$function$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_functions_admin') THEN
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
END IF;
END $$;
GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
GRANT supabase_functions_admin TO postgres;
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_pg_net_admin') THEN
REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
DROP OWNED BY supabase_pg_net_admin;
DROP ROLE supabase_pg_net_admin;
END IF;
END $$;
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net') THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END $$;
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_event_trigger_ddl_commands() AS ev JOIN pg_extension AS ext ON ev.objid = ext.oid WHERE ext.extname = 'pg_net') THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END;
$$;
COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname = 'issue_pg_net_access') THEN
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
EXECUTE PROCEDURE extensions.grant_pg_net_access();
END IF;
END $$;
INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
ALTER function supabase_functions.http_request() SECURITY DEFINER;
ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
COMMIT;

View File

@@ -0,0 +1,291 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: supabase-kong-config
data:
kong-entrypoint.sh: |
#!/bin/sh
# Build Lua expressions for request-transformer (legacy HS256 key path only).
# $SUPABASE_ANON_KEY and $SUPABASE_SERVICE_KEY are the HS256 JWTs.
export LUA_AUTH_EXPR="\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or headers.apikey)"
export LUA_RT_WS_EXPR="\$(query_params.apikey)"
# Substitute environment variables in the Kong declarative config.
awk '{
result = ""
rest = $0
while (match(rest, /\$[A-Za-z_][A-Za-z_0-9]*/)) {
varname = substr(rest, RSTART + 1, RLENGTH - 1)
if (varname in ENVIRON) {
result = result substr(rest, 1, RSTART - 1) ENVIRON[varname]
} else {
result = result substr(rest, 1, RSTART + RLENGTH - 1)
}
rest = substr(rest, RSTART + RLENGTH)
}
print result rest
}' /home/kong/temp.yml > "$KONG_DECLARATIVE_CONFIG"
# Remove empty key-auth credentials (unconfigured opaque keys)
sed -i '/^[[:space:]]*- key:[[:space:]]*$/d' "$KONG_DECLARATIVE_CONFIG"
exec /entrypoint.sh kong docker-start
kong.yml: |
_format_version: '2.1'
_transform: true
consumers:
- username: DASHBOARD
- username: anon
keyauth_credentials:
- key: $SUPABASE_ANON_KEY
- username: service_role
keyauth_credentials:
- key: $SUPABASE_SERVICE_KEY
acls:
- consumer: anon
group: anon
- consumer: service_role
group: admin
basicauth_credentials:
- consumer: DASHBOARD
username: '$DASHBOARD_USERNAME'
password: '$DASHBOARD_PASSWORD'
services:
- name: auth-v1-open
url: http://auth:9999/verify
routes:
- name: auth-v1-open
strip_path: true
paths:
- /auth/v1/verify
plugins:
- name: cors
- name: auth-v1-open-callback
url: http://auth:9999/callback
routes:
- name: auth-v1-open-callback
strip_path: true
paths:
- /auth/v1/callback
plugins:
- name: cors
- name: auth-v1-open-authorize
url: http://auth:9999/authorize
routes:
- name: auth-v1-open-authorize
strip_path: true
paths:
- /auth/v1/authorize
plugins:
- name: cors
- name: auth-v1-open-jwks
url: http://auth:9999/.well-known/jwks.json
routes:
- name: auth-v1-open-jwks
strip_path: true
paths:
- /auth/v1/.well-known/jwks.json
plugins:
- name: cors
- name: auth-v1
url: http://auth:9999/
routes:
- name: auth-v1-all
strip_path: true
paths:
- /auth/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: rest-v1
url: http://rest:3000/
routes:
- name: rest-v1-all
strip_path: true
paths:
- /rest/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: graphql-v1
url: http://rest:3000/rpc/graphql
routes:
- name: graphql-v1-all
strip_path: true
paths:
- /graphql/v1
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Content-Profile: graphql_public"
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: realtime-v1-ws
url: http://realtime-dev:4000/socket
protocol: ws
routes:
- name: realtime-v1-ws
strip_path: true
paths:
- /realtime/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "x-api-key:$LUA_RT_WS_EXPR"
replace:
querystring:
- "apikey:$LUA_RT_WS_EXPR"
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: realtime-v1-rest
url: http://realtime-dev:4000/api
protocol: http
routes:
- name: realtime-v1-rest
strip_path: true
paths:
- /realtime/v1/api
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: storage-v1
url: http://storage:5000/
routes:
- name: storage-v1-all
strip_path: true
paths:
- /storage/v1/
plugins:
- name: cors
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: post-function
config:
access:
- |
local auth = kong.request.get_header("authorization")
if auth == nil or auth == "" or auth:find("^%s*$") then
kong.service.request.clear_header("authorization")
end
- name: meta
url: http://meta:8080/
routes:
- name: meta-all
strip_path: true
paths:
- /pg/
plugins:
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- name: dashboard
url: http://studio:3000/
routes:
- name: dashboard-all
strip_path: true
paths:
- /
plugins:
- name: cors
- name: basic-auth
config:
hide_credentials: true

View File

@@ -0,0 +1,105 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-auth
spec:
replicas: 1
selector:
matchLabels:
component: auth
template:
metadata:
labels:
component: auth
spec:
containers:
- name: auth
image: supabase/gotrue:v2.189.0
ports:
- containerPort: 9999
env:
- name: GOTRUE_API_HOST
value: "0.0.0.0"
- name: GOTRUE_API_PORT
value: "9999"
- name: API_EXTERNAL_URL
value: "https://{{ .domain }}"
- name: GOTRUE_DB_DRIVER
value: postgres
- name: GOTRUE_DB_DATABASE_URL
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbUrlAuth
- name: GOTRUE_SITE_URL
value: "{{ .siteUrl }}"
- name: GOTRUE_URI_ALLOW_LIST
value: ""
- name: GOTRUE_DISABLE_SIGNUP
value: "false"
- name: GOTRUE_JWT_ADMIN_ROLES
value: service_role
- name: GOTRUE_JWT_AUD
value: authenticated
- name: GOTRUE_JWT_DEFAULT_GROUP_NAME
value: authenticated
- name: GOTRUE_JWT_EXP
value: "{{ .jwtExpiry }}"
- name: GOTRUE_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: GOTRUE_JWT_ISSUER
value: "https://{{ .domain }}/auth/v1"
- name: GOTRUE_EXTERNAL_EMAIL_ENABLED
value: "true"
- name: GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED
value: "false"
- name: GOTRUE_MAILER_AUTOCONFIRM
value: "false"
- name: GOTRUE_SMTP_ADMIN_EMAIL
value: "{{ .smtp.from }}"
- name: GOTRUE_SMTP_HOST
value: "{{ .smtp.host }}"
- name: GOTRUE_SMTP_PORT
value: "{{ .smtp.port }}"
- name: GOTRUE_SMTP_USER
value: "{{ .smtp.user }}"
- name: GOTRUE_SMTP_PASS
valueFrom:
secretKeyRef:
name: supabase-secrets
key: smtp.password
- name: GOTRUE_SMTP_SENDER_NAME
value: "{{ .smtp.from }}"
- name: GOTRUE_MAILER_URLPATHS_INVITE
value: "/auth/v1/verify"
- name: GOTRUE_MAILER_URLPATHS_CONFIRMATION
value: "/auth/v1/verify"
- name: GOTRUE_MAILER_URLPATHS_RECOVERY
value: "/auth/v1/verify"
- name: GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE
value: "/auth/v1/verify"
- name: GOTRUE_EXTERNAL_PHONE_ENABLED
value: "false"
livenessProbe:
httpGet:
path: /health
port: 9999
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 9999
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 50m

View File

@@ -0,0 +1,90 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-kong
spec:
replicas: 1
selector:
matchLabels:
component: kong
template:
metadata:
labels:
component: kong
spec:
containers:
- name: kong
image: kong/kong:3.4.2
command: ["/bin/sh", "/home/kong/kong-entrypoint.sh"]
ports:
- containerPort: 8000
- containerPort: 8443
env:
- name: LD_LIBRARY_PATH
value: "/usr/local/kong/lib"
- name: KONG_DATABASE
value: "off"
- name: KONG_DECLARATIVE_CONFIG
value: /usr/local/kong/kong.yml
- name: KONG_DNS_ORDER
value: LAST,A,CNAME
- name: KONG_DNS_NOT_FOUND_TTL
value: "1"
- name: KONG_PLUGINS
value: request-transformer,cors,key-auth,acl,basic-auth,request-termination,ip-restriction,post-function
- name: KONG_NGINX_PROXY_PROXY_BUFFER_SIZE
value: 160k
- name: KONG_NGINX_PROXY_PROXY_BUFFERS
value: 64 160k
- name: KONG_PROXY_ACCESS_LOG
value: /dev/stdout combined
- name: SUPABASE_ANON_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: anonKey
- name: SUPABASE_SERVICE_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: serviceRoleKey
- name: DASHBOARD_USERNAME
value: "{{ .dashboardUsername }}"
- name: DASHBOARD_PASSWORD
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dashboardPassword
volumeMounts:
- name: kong-config
mountPath: /home/kong/temp.yml
subPath: kong.yml
- name: kong-config
mountPath: /home/kong/kong-entrypoint.sh
subPath: kong-entrypoint.sh
- name: kong-output
mountPath: /usr/local/kong
livenessProbe:
exec:
command: ["kong", "health"]
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command: ["kong", "health"]
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: 256Mi
cpu: 100m
volumes:
- name: kong-config
configMap:
name: supabase-kong-config
- name: kong-output
emptyDir: {}

View File

@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-meta
spec:
replicas: 1
selector:
matchLabels:
component: meta
template:
metadata:
labels:
component: meta
spec:
containers:
- name: meta
image: supabase/postgres-meta:v0.96.6
ports:
- containerPort: 8080
env:
- name: PG_META_PORT
value: "8080"
- name: PG_META_DB_HOST
value: "{{ .db.host }}"
- name: PG_META_DB_PORT
value: "5432"
- name: PG_META_DB_NAME
value: "{{ .db.name }}"
- name: PG_META_DB_USER
value: "{{ .db.user }}"
- name: PG_META_DB_PASSWORD
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbPassword
- name: CRYPTO_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: pgMetaCryptoKey
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 50m

View File

@@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-realtime
spec:
replicas: 1
selector:
matchLabels:
component: realtime
template:
metadata:
labels:
component: realtime
spec:
# hostname must be realtime-dev so Realtime seeds tenant as "realtime-dev"
# (Realtime parses the subdomain part of its hostname for the tenant id)
hostname: realtime-dev
containers:
- name: realtime
image: supabase/realtime:v2.102.3
ports:
- containerPort: 4000
env:
- name: PORT
value: "4000"
- name: DB_HOST
value: "{{ .db.host }}"
- name: DB_PORT
value: "5432"
- name: DB_USER
value: supabase_admin
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbPassword
- name: DB_NAME
value: "{{ .db.name }}"
- name: DB_AFTER_CONNECT_QUERY
value: "SET search_path TO _realtime"
- name: DB_ENC_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: realtimeEncKey
- name: API_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: supabase-secrets
key: secretKeyBase
- name: METRICS_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: ERL_AFLAGS
value: "-proto_dist inet_tcp"
- name: DNS_NODES
value: "''"
- name: RLIMIT_NOFILE
value: "10000"
- name: APP_NAME
value: realtime
- name: SEED_SELF_HOST
value: "true"
- name: RUN_JANITOR
value: "true"
- name: DISABLE_HEALTHCHECK_LOGGING
value: "true"
resources:
requests:
memory: 256Mi
cpu: 100m

View File

@@ -0,0 +1,70 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-rest
spec:
replicas: 1
selector:
matchLabels:
component: rest
template:
metadata:
labels:
component: rest
spec:
containers:
- name: rest
image: postgrest/postgrest:v14.12
command: ["postgrest"]
ports:
- containerPort: 3000
env:
- name: PGRST_DB_URI
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbUrlRest
- name: PGRST_DB_SCHEMAS
value: "public,storage,graphql_public"
- name: PGRST_DB_MAX_ROWS
value: "1000"
- name: PGRST_DB_EXTRA_SEARCH_PATH
value: "public"
- name: PGRST_DB_ANON_ROLE
value: anon
- name: PGRST_ADMIN_SERVER_PORT
value: "3001"
- name: PGRST_ADMIN_SERVER_HOST
value: localhost
- name: PGRST_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: PGRST_DB_USE_LEGACY_GUCS
value: "false"
- name: PGRST_APP_SETTINGS_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: PGRST_APP_SETTINGS_JWT_EXP
value: "{{ .jwtExpiry }}"
livenessProbe:
exec:
command: ["postgrest", "--ready"]
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command: ["postgrest", "--ready"]
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 50m

View File

@@ -0,0 +1,122 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-storage
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: storage
template:
metadata:
labels:
component: storage
spec:
containers:
- name: storage
image: supabase/storage-api:v1.60.4
ports:
- containerPort: 5000
env:
- name: ANON_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: anonKey
- name: SERVICE_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: serviceRoleKey
- name: POSTGREST_URL
value: "http://rest:3000"
- name: AUTH_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbUrlStorage
- name: STORAGE_PUBLIC_URL
value: "https://{{ .domain }}"
- name: REQUEST_ALLOW_X_FORWARDED_PATH
value: "true"
- name: FILE_SIZE_LIMIT
value: "52428800"
- name: STORAGE_BACKEND
value: file
- name: GLOBAL_S3_BUCKET
value: stub
- name: REGION
value: stub
- name: FILE_STORAGE_BACKEND_PATH
value: /var/lib/storage
- name: TENANT_ID
value: stub
- name: ENABLE_IMAGE_TRANSFORMATION
value: "true"
- name: IMGPROXY_URL
value: "http://localhost:5001"
livenessProbe:
httpGet:
path: /status
port: 5000
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /status
port: 5000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
volumeMounts:
- name: supabase-storage
mountPath: /var/lib/storage
resources:
requests:
memory: 256Mi
cpu: 100m
- name: imgproxy
image: darthsim/imgproxy:v3.30.1
ports:
- containerPort: 5001
env:
- name: IMGPROXY_BIND
value: ":5001"
- name: IMGPROXY_LOCAL_FILESYSTEM_ROOT
value: /
- name: IMGPROXY_USE_ETAG
value: "true"
- name: IMGPROXY_AUTO_WEBP
value: "true"
- name: IMGPROXY_MAX_SRC_RESOLUTION
value: "16.8"
volumeMounts:
- name: supabase-storage
mountPath: /var/lib/storage
livenessProbe:
exec:
command: ["imgproxy", "health"]
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 50m
volumes:
- name: supabase-storage
persistentVolumeClaim:
claimName: supabase-storage

View File

@@ -0,0 +1,95 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-studio
spec:
replicas: 1
selector:
matchLabels:
component: studio
template:
metadata:
labels:
component: studio
spec:
containers:
- name: studio
image: supabase/studio:2026.06.03-sha-0bca601
ports:
- containerPort: 3000
env:
- name: HOSTNAME
value: "0.0.0.0"
- name: STUDIO_PG_META_URL
value: "http://meta:8080"
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_HOST
value: "{{ .db.host }}"
- name: POSTGRES_DB
value: "{{ .db.name }}"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbPassword
- name: POSTGRES_USER_READ_WRITE
value: "{{ .db.user }}"
- name: PG_META_CRYPTO_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: pgMetaCryptoKey
- name: PGRST_DB_SCHEMAS
value: "public,storage,graphql_public"
- name: PGRST_DB_MAX_ROWS
value: "1000"
- name: PGRST_DB_EXTRA_SEARCH_PATH
value: "public"
- name: DEFAULT_ORGANIZATION_NAME
value: "{{ .defaultOrg }}"
- name: DEFAULT_PROJECT_NAME
value: "{{ .defaultProject }}"
- name: SUPABASE_URL
value: "http://kong:8000"
- name: SUPABASE_PUBLIC_URL
value: "https://{{ .domain }}"
- name: SUPABASE_ANON_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: anonKey
- name: SUPABASE_SERVICE_KEY
valueFrom:
secretKeyRef:
name: supabase-secrets
key: serviceRoleKey
- name: AUTH_JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: ENABLED_FEATURES_LOGS_ALL
value: "false"
livenessProbe:
exec:
command:
- node
- -e
- "fetch('http://localhost:3000/api/platform/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})"
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /api/platform/profile
port: 3000
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 3
resources:
requests:
memory: 512Mi
cpu: 200m

View File

@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: supabase
annotations:
external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/ttl: "60"
spec:
ingressClassName: traefik
rules:
- host: "{{ .domain }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kong
port:
number: 8000
tls:
- hosts:
- "{{ .domain }}"
secretName: "{{ .tlsSecretName }}"

View File

@@ -0,0 +1,24 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: supabase
labels:
- includeSelectors: true
pairs:
app: supabase
managedBy: kustomize
partOf: wild-cloud
resources:
- namespace.yaml
- pvc.yaml
- services.yaml
- ingress.yaml
- configmap-db-init.yaml
- configmap-kong.yaml
- statefulset-db.yaml
- deployment-kong.yaml
- deployment-auth.yaml
- deployment-rest.yaml
- deployment-realtime.yaml
- deployment-storage.yaml
- deployment-meta.yaml
- deployment-studio.yaml

View File

@@ -0,0 +1,56 @@
version: 2026.07.02
requires:
- name: smtp
defaultConfig:
namespace: supabase
domain: supabase.{{ .cloud.domain }}
externalDnsDomain: "{{ .cloud.domain }}"
tlsSecretName: wildcard-wild-cloud-tls
dbStorage: 10Gi
fileStorage: 5Gi
db:
host: db
name: postgres
user: supabase_admin
jwtExpiry: "3600"
siteUrl: "https://supabase.{{ .cloud.domain }}"
defaultOrg: "Default Organization"
defaultProject: "Default Project"
dashboardUsername: supabase
smtp:
host: "{{ .apps.smtp.host }}"
port: "{{ .apps.smtp.port }}"
user: "{{ .apps.smtp.user }}"
from: "{{ .apps.smtp.from }}"
defaultSecrets:
- key: dbPassword
- key: dashboardPassword
- key: jwtSecret
default: "your-super-secret-jwt-token-with-at-least-32-characters-long"
- key: anonKey
default: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
- key: serviceRoleKey
default: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
- key: secretKeyBase
default: "UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq"
- key: realtimeEncKey
default: "supabaserealtime"
- key: vaultEncKey
default: '{{ crypto.SHA256 .secrets.jwtSecret }}'
- key: pgMetaCryptoKey
default: '{{ crypto.SHA256 .secrets.dbPassword }}'
- key: dbUrlAuth
default: "postgres://supabase_auth_admin:{{ .secrets.dbPassword }}@{{ .app.db.host }}:5432/{{ .app.db.name }}?sslmode=disable"
- key: dbUrlStorage
default: "postgres://supabase_storage_admin:{{ .secrets.dbPassword }}@{{ .app.db.host }}:5432/{{ .app.db.name }}?sslmode=disable"
- key: dbUrlRest
default: "postgres://authenticator:{{ .secrets.dbPassword }}@{{ .app.db.host }}:5432/{{ .app.db.name }}?sslmode=disable"
requiredSecrets:
- smtp.password
scripts:
- name: generate-keys
path: scripts/generate-keys.sh
description: "Generate new JWT secret and API keys. Run this before going to production to replace the default example keys."
params:
- name: JWT_SECRET
description: "Optional: provide your own JWT secret (min 32 chars). Leave blank to auto-generate."

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: supabase-db
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "{{ .dbStorage }}"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: supabase-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "{{ .fileStorage }}"

View File

@@ -0,0 +1,80 @@
#!/bin/bash
# generate-keys.sh — Generate a new JWT secret and matching Supabase API keys.
#
# This script generates a cryptographically secure JWT_SECRET and derives
# ANON_KEY and SERVICE_ROLE_KEY JWTs from it. Run this script and update
# the supabase secrets in your Wild Cloud instance before going to production.
#
# Usage:
# wild app run supabase generate-keys [JWT_SECRET=<your-secret>]
#
# The script uses kubectl exec to run inside the cluster. Requires:
# - python3 with hmac, hashlib, base64, json modules (standard library)
# - The supabase namespace to exist
#
# After running, update secrets.yaml in your instance with the output values.
set -e
NAMESPACE="supabase"
INSTANCE="${WILD_INSTANCE:-$(wild instance current 2>/dev/null || echo 'test-cloud')}"
# Generate or use provided JWT secret
if [ -n "$JWT_SECRET" ]; then
SECRET="$JWT_SECRET"
else
SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(48))")
fi
# Generate HS256 JWT tokens using python3 (no external deps needed)
GENERATE_JWT=$(cat <<'PYEOF'
import sys, hmac, hashlib, base64, json, time
def b64url_encode(data):
if isinstance(data, str):
data = data.encode()
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
def make_jwt(secret, role):
header = b64url_encode(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(',', ':')))
# Tokens valid for 10 years from now
now = int(time.time())
exp = now + (10 * 365 * 24 * 3600)
payload = b64url_encode(json.dumps({
"role": role,
"iss": "supabase",
"iat": now,
"exp": exp
}, separators=(',', ':')))
signing_input = f"{header}.{payload}"
sig = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest()
signature = b64url_encode(sig)
return f"{signing_input}.{signature}"
secret = sys.argv[1]
role = sys.argv[2]
print(make_jwt(secret, role))
PYEOF
)
ANON_KEY=$(python3 -c "$GENERATE_JWT" "$SECRET" "anon")
SERVICE_ROLE_KEY=$(python3 -c "$GENERATE_JWT" "$SECRET" "service_role")
echo ""
echo "=== Supabase JWT Keys ==="
echo ""
echo "Add these to your secrets.yaml (or update via 'wild secrets set'):"
echo ""
echo " jwtSecret: |"
echo " $SECRET"
echo ""
echo " anonKey: |"
echo " $ANON_KEY"
echo ""
echo " serviceRoleKey: |"
echo " $SERVICE_ROLE_KEY"
echo ""
echo "IMPORTANT: After updating secrets.yaml, redeploy Supabase:"
echo " wild app deploy supabase"
echo ""
echo "The existing database will have its JWT secret updated on next pod restart."

View File

@@ -0,0 +1,87 @@
apiVersion: v1
kind: Service
metadata:
name: db
spec:
selector:
component: db
ports:
- port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: kong
spec:
selector:
component: kong
ports:
- port: 8000
targetPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: auth
spec:
selector:
component: auth
ports:
- port: 9999
targetPort: 9999
---
apiVersion: v1
kind: Service
metadata:
name: rest
spec:
selector:
component: rest
ports:
- port: 3000
targetPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: realtime-dev
spec:
selector:
component: realtime
ports:
- port: 4000
targetPort: 4000
---
apiVersion: v1
kind: Service
metadata:
name: storage
spec:
selector:
component: storage
ports:
- port: 5000
targetPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: meta
spec:
selector:
component: meta
ports:
- port: 8080
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: studio
spec:
selector:
component: studio
ports:
- port: 3000
targetPort: 3000

View File

@@ -0,0 +1,89 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: supabase-db
spec:
serviceName: db
replicas: 1
selector:
matchLabels:
component: db
template:
metadata:
labels:
component: db
spec:
securityContext:
fsGroup: 999
containers:
- name: db
image: supabase/postgres:17.6.1.136
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: supabase-secrets
key: dbPassword
- name: POSTGRES_DB
value: "{{ .db.name }}"
- name: POSTGRES_USER
value: "{{ .db.user }}"
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: supabase-secrets
key: jwtSecret
- name: JWT_EXP
value: "{{ .jwtExpiry }}"
volumeMounts:
- name: supabase-db
mountPath: /var/lib/postgresql
- name: db-init
mountPath: /docker-entrypoint-initdb.d/init-scripts/99-jwt.sql
subPath: jwt.sql
- name: db-init
mountPath: /docker-entrypoint-initdb.d/init-scripts/99-roles.sql
subPath: roles.sql
- name: db-init
mountPath: /docker-entrypoint-initdb.d/migrations/99-realtime.sql
subPath: realtime.sql
- name: db-init
mountPath: /docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql
subPath: webhooks.sql
livenessProbe:
exec:
command:
- pg_isready
- -U
- "{{ .db.user }}"
- -d
- "{{ .db.name }}"
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
exec:
command:
- pg_isready
- -U
- "{{ .db.user }}"
- -d
- "{{ .db.name }}"
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: 256Mi
cpu: 100m
volumes:
- name: supabase-db
persistentVolumeClaim:
claimName: supabase-db
- name: db-init
configMap:
name: supabase-db-init