Wire schema v1 into a manual Supabase migration and vendor the real stack
- Move db/schema.sql to supabase/migrations/ as the first supabase CLI migration (manual `db push` only, no automated runner); re-add the gebos_ingest role + grants there since init.sql never re-runs - Add gebos-postgres-passwords oneshot on db-host: syncs role passwords from sops (LoadCredential, journal-safe), makes supabase_admin SUPERUSER and hands the auth schema to supabase_auth_admin to match the upstream supabase/postgres image; add pg_hba rule for 10.0.0.0/8 - Vendor the official docker-compose (studio/kong/auth/rest/meta only, external Postgres, loopback Studio with no Kong dashboard route) plus kong.yml (trimmed) and kong-entrypoint.sh (verbatim); tested: compose config, Kong config parse, migration applied on TimescaleDB pg17 - Document decisions as ADRs 0001-0004 (migrations, passwords, vendored stack, JWT API keys incl. verify/mint procedure) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+179
-35
@@ -1,65 +1,209 @@
|
||||
# Gebos — vendored Supabase compose stack.
|
||||
#
|
||||
# TODO: copy the full official compose from
|
||||
# Vendored from the official self-hosting bundle
|
||||
# https://github.com/supabase/supabase/blob/master/docker/docker-compose.yml
|
||||
# and apply the three modifications described in ./README.md:
|
||||
# - drop the `db` service
|
||||
# - replace `db:5432` / `postgres:5432` with ${POSTGRES_HOST}:${POSTGRES_PORT}
|
||||
# - bind Studio to ${STUDIO_BIND}:3000 (default 127.0.0.1)
|
||||
# with the deliberate deviations documented in ./README.md:
|
||||
#
|
||||
# The skeleton below names the services we'll keep and the env vars each one
|
||||
# reads, so reviewers can sanity-check the topology before the real compose
|
||||
# lands.
|
||||
# * no `db` service — Postgres runs on db-host; every service connects to
|
||||
# ${POSTGRES_HOST}:${POSTGRES_PORT} and the `depends_on: db` blocks are
|
||||
# dropped
|
||||
# * no realtime / storage / imgproxy / functions / supavisor / analytics —
|
||||
# the pilot needs auth + rest + studio only; re-vendor from upstream when
|
||||
# one of them becomes needed
|
||||
# * Studio binds to ${STUDIO_BIND}:3000 (loopback; reach it via SSH tunnel)
|
||||
# * meta connects as supabase_admin, not `postgres`: upstream's `postgres`
|
||||
# is its cluster superuser, but on db-host that role carries a different
|
||||
# password. gebos-postgres-passwords makes supabase_admin SUPERUSER to
|
||||
# match the upstream image's semantics.
|
||||
#
|
||||
# Where variables come from (see nix/modules/gebos-supabase.nix):
|
||||
# * POSTGRES_HOST / POSTGRES_PORT / POSTGRES_DB / STUDIO_BIND — systemd
|
||||
# unit `environment` (non-secret deployment config, nix-managed)
|
||||
# * POSTGRES_PASSWORD / JWT_SECRET / ANON_KEY / SERVICE_ROLE_KEY —
|
||||
# sops-rendered EnvironmentFile (secrets)
|
||||
# * gebos constants (public URLs, org name) — hardcoded right here
|
||||
|
||||
name: gebos-supabase
|
||||
|
||||
services:
|
||||
kong:
|
||||
image: kong:2.8.1
|
||||
|
||||
studio:
|
||||
container_name: supabase-studio
|
||||
image: supabase/studio:2026.07.07-sha-a6a04f2
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8000:8000/tcp" # Caddy proxies api.gebos.online here
|
||||
# Never 0.0.0.0 — Studio performs no authentication of its own in this
|
||||
# topology (the upstream basic-auth lives on Kong's dashboard route,
|
||||
# which we do not expose). Access = SSH tunnel to app-host.
|
||||
- ${STUDIO_BIND}:3000:3000/tcp
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"node -e \"fetch('http://localhost:3000/api/platform/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})\""
|
||||
]
|
||||
timeout: 10s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
environment:
|
||||
HOSTNAME: "0.0.0.0"
|
||||
|
||||
STUDIO_PG_META_URL: http://meta:8080
|
||||
POSTGRES_HOST: ${POSTGRES_HOST}
|
||||
POSTGRES_PORT: ${POSTGRES_PORT}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_USER_READ_WRITE: supabase_admin
|
||||
|
||||
PGRST_DB_SCHEMAS: public
|
||||
|
||||
DEFAULT_ORGANIZATION_NAME: Gebos
|
||||
DEFAULT_PROJECT_NAME: Gebos
|
||||
|
||||
SUPABASE_URL: http://kong:8000
|
||||
SUPABASE_PUBLIC_URL: https://api.gebos.online
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
AUTH_JWT_SECRET: ${JWT_SECRET}
|
||||
|
||||
# No Logflare/analytics stack in this deployment.
|
||||
ENABLED_FEATURES_LOGS_ALL: "false"
|
||||
|
||||
kong:
|
||||
container_name: supabase-kong
|
||||
image: kong/kong:3.9.1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 127.0.0.1:8000:8000/tcp # Caddy proxies api.gebos.online here
|
||||
healthcheck:
|
||||
test: ["CMD", "kong", "health"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
# The entrypoint substitutes $ENV_VARS in temp.yml and writes the result
|
||||
# to KONG_DECLARATIVE_CONFIG (Kong has no native env interpolation).
|
||||
- ./kong.yml:/home/kong/temp.yml:ro
|
||||
- ./kong-entrypoint.sh:/home/kong/kong-entrypoint.sh:ro
|
||||
environment:
|
||||
KONG_DATABASE: "off"
|
||||
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
|
||||
volumes:
|
||||
- ./kong.yml:/home/kong/kong.yml:ro
|
||||
KONG_DECLARATIVE_CONFIG: /usr/local/kong/kong.yml
|
||||
KONG_ROUTER_FLAVOR: expressions
|
||||
# https://github.com/supabase/cli/issues/14
|
||||
KONG_DNS_ORDER: LAST,A,CNAME
|
||||
KONG_DNS_NOT_FOUND_TTL: 1
|
||||
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth,request-termination,ip-restriction,post-function
|
||||
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
|
||||
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
|
||||
KONG_PROXY_ACCESS_LOG: /dev/stdout combined
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
# Opaque sb_* API keys — not used yet. Empty values make the entrypoint
|
||||
# fall back to legacy apikey pass-through and strip the empty
|
||||
# credentials from the declarative config.
|
||||
SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_PUBLISHABLE_KEY:-}
|
||||
SUPABASE_SECRET_KEY: ${SUPABASE_SECRET_KEY:-}
|
||||
ANON_KEY_ASYMMETRIC: ${ANON_KEY_ASYMMETRIC:-}
|
||||
SERVICE_ROLE_KEY_ASYMMETRIC: ${SERVICE_ROLE_KEY_ASYMMETRIC:-}
|
||||
entrypoint: ["/bin/sh", "/home/kong/kong-entrypoint.sh"]
|
||||
|
||||
auth:
|
||||
image: supabase/gotrue:v2.158.1
|
||||
container_name: supabase-auth
|
||||
image: supabase/gotrue:v2.189.0
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:9999/health"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
environment:
|
||||
GOTRUE_DB_DRIVER: postgres
|
||||
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
GOTRUE_SITE_URL: https://app.gebos.online
|
||||
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
||||
GOTRUE_JWT_EXP: "3600"
|
||||
GOTRUE_API_HOST: 0.0.0.0
|
||||
GOTRUE_API_PORT: 9999
|
||||
API_EXTERNAL_URL: https://api.gebos.online
|
||||
|
||||
GOTRUE_DB_DRIVER: postgres
|
||||
# GoTrue owns the auth schema and runs its own migrations in it on
|
||||
# startup — supabase_auth_admin's ownership of that schema is enforced
|
||||
# by the gebos-postgres-passwords oneshot on db-host.
|
||||
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
|
||||
GOTRUE_SITE_URL: https://app.gebos.online
|
||||
GOTRUE_URI_ALLOW_LIST: ""
|
||||
# Tenants are invited by an admin (magic link), never self-signed-up.
|
||||
GOTRUE_DISABLE_SIGNUP: "true"
|
||||
|
||||
GOTRUE_JWT_ADMIN_ROLES: service_role
|
||||
GOTRUE_JWT_AUD: authenticated
|
||||
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
||||
GOTRUE_JWT_EXP: "3600"
|
||||
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
||||
GOTRUE_JWT_ISSUER: https://api.gebos.online
|
||||
|
||||
GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: "false"
|
||||
GOTRUE_MAILER_AUTOCONFIRM: "false"
|
||||
|
||||
# TODO: no SMTP provider configured yet. Magic links (UVI-NFR-08,
|
||||
# passwordless-only) cannot send until these are filled in — add the
|
||||
# SMTP secrets to nix/secrets/secrets.yaml and wire them through
|
||||
# gebos-secrets.nix like the other supabase keys.
|
||||
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL:-}
|
||||
GOTRUE_SMTP_HOST: ${SMTP_HOST:-}
|
||||
GOTRUE_SMTP_PORT: ${SMTP_PORT:-587}
|
||||
GOTRUE_SMTP_USER: ${SMTP_USER:-}
|
||||
GOTRUE_SMTP_PASS: ${SMTP_PASS:-}
|
||||
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME:-Gebos}
|
||||
GOTRUE_MAILER_URLPATHS_INVITE: /auth/v1/verify
|
||||
GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/v1/verify
|
||||
GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify
|
||||
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
||||
|
||||
GOTRUE_EXTERNAL_PHONE_ENABLED: "false"
|
||||
GOTRUE_SMS_AUTOCONFIRM: "false"
|
||||
|
||||
rest:
|
||||
image: postgrest/postgrest:v12.0.2
|
||||
container_name: supabase-rest
|
||||
image: postgrest/postgrest:v14.12
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: [ "CMD", "postgrest", "--ready" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
environment:
|
||||
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
PGRST_DB_SCHEMAS: public
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_DB_MAX_ROWS: 1000
|
||||
PGRST_DB_EXTRA_SEARCH_PATH: public
|
||||
PGRST_DB_ANON_ROLE: anon
|
||||
|
||||
studio:
|
||||
image: supabase/studio:20240326-5e5586d
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${STUDIO_BIND}:3000:3000/tcp" # never bind to 0.0.0.0
|
||||
environment:
|
||||
STUDIO_PG_META_URL: http://meta:8080
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DEFAULT_ORGANIZATION_NAME: Gebos
|
||||
SUPABASE_URL: https://api.gebos.online
|
||||
DASHBOARD_USERNAME: gebos
|
||||
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
|
||||
PGRST_ADMIN_SERVER_PORT: 3001
|
||||
PGRST_ADMIN_SERVER_HOST: localhost
|
||||
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_DB_USE_LEGACY_GUCS: "false"
|
||||
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_APP_SETTINGS_JWT_EXP: "3600"
|
||||
command:
|
||||
[
|
||||
"postgrest"
|
||||
]
|
||||
|
||||
meta:
|
||||
image: supabase/postgres-meta:v0.83.2
|
||||
container_name: supabase-meta
|
||||
image: supabase/postgres-meta:v0.96.6
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PG_META_PORT: 8080
|
||||
PG_META_DB_HOST: ${POSTGRES_HOST}
|
||||
PG_META_DB_PORT: ${POSTGRES_PORT}
|
||||
PG_META_DB_NAME: ${POSTGRES_DB}
|
||||
|
||||
Reference in New Issue
Block a user