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:
9
supabase/app.yaml
Normal file
9
supabase/app.yaml
Normal 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
126
supabase/notes.md
Normal 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 (~2–3 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.
|
||||
159
supabase/versions/1/configmap-db-init.yaml
Normal file
159
supabase/versions/1/configmap-db-init.yaml
Normal 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;
|
||||
291
supabase/versions/1/configmap-kong.yaml
Normal file
291
supabase/versions/1/configmap-kong.yaml
Normal 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
|
||||
105
supabase/versions/1/deployment-auth.yaml
Normal file
105
supabase/versions/1/deployment-auth.yaml
Normal 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
|
||||
90
supabase/versions/1/deployment-kong.yaml
Normal file
90
supabase/versions/1/deployment-kong.yaml
Normal 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: {}
|
||||
60
supabase/versions/1/deployment-meta.yaml
Normal file
60
supabase/versions/1/deployment-meta.yaml
Normal 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
|
||||
78
supabase/versions/1/deployment-realtime.yaml
Normal file
78
supabase/versions/1/deployment-realtime.yaml
Normal 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
|
||||
70
supabase/versions/1/deployment-rest.yaml
Normal file
70
supabase/versions/1/deployment-rest.yaml
Normal 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
|
||||
122
supabase/versions/1/deployment-storage.yaml
Normal file
122
supabase/versions/1/deployment-storage.yaml
Normal 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
|
||||
95
supabase/versions/1/deployment-studio.yaml
Normal file
95
supabase/versions/1/deployment-studio.yaml
Normal 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
|
||||
25
supabase/versions/1/ingress.yaml
Normal file
25
supabase/versions/1/ingress.yaml
Normal 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 }}"
|
||||
24
supabase/versions/1/kustomization.yaml
Normal file
24
supabase/versions/1/kustomization.yaml
Normal 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
|
||||
56
supabase/versions/1/manifest.yaml
Normal file
56
supabase/versions/1/manifest.yaml
Normal 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."
|
||||
4
supabase/versions/1/namespace.yaml
Normal file
4
supabase/versions/1/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
21
supabase/versions/1/pvc.yaml
Normal file
21
supabase/versions/1/pvc.yaml
Normal 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 }}"
|
||||
80
supabase/versions/1/scripts/generate-keys.sh
Normal file
80
supabase/versions/1/scripts/generate-keys.sh
Normal 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."
|
||||
87
supabase/versions/1/services.yaml
Normal file
87
supabase/versions/1/services.yaml
Normal 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
|
||||
89
supabase/versions/1/statefulset-db.yaml
Normal file
89
supabase/versions/1/statefulset-db.yaml
Normal 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
|
||||
Reference in New Issue
Block a user