Skip to content

Make self-host docker compose up -d actually work (production build, Kong keys, API-role grants)#160

Merged
pratikbodkhe merged 1 commit into
mainfrom
fix/self-host-prod-default
Jul 6, 2026
Merged

Make self-host docker compose up -d actually work (production build, Kong keys, API-role grants)#160
pratikbodkhe merged 1 commit into
mainfrom
fix/self-host-prod-default

Conversation

@pratikbodkhe

Copy link
Copy Markdown
Contributor

Summary

Following the schema-on-first-boot fix (#159), I ran the documented self-host flow one command at a time on a fresh clone and booted the real stack. docker compose up -d produced a non-functional instance. Three defects, each reproduced and fixed against a live boot:

1. The default command ran the dev server (and exposed Postgres)

docker-compose.override.yml was git-tracked, so Docker Compose auto-merged it on every docker compose up. It swapped the web service to target: deps + pnpm dev + NODE_ENV=development (a crashing Next.js dev server, restarted 3x in my boot) and bound Postgres to 0.0.0.0:5432. The Dockerfile's production runner stage (node server.js) was never used on the default path.

Fix: renamed the overlay to docker-compose.dev.yml (opt-in: docker compose -f docker-compose.yml -f docker-compose.dev.yml up). The default docker compose up -d now builds and serves the production image, and no longer exposes the DB port.

2. Kong rejected the real API keys

docker/kong.yml shipped literal ${ANON_KEY} / ${SERVICE_ROLE_KEY} placeholders; nothing substituted them, so key-auth registered the placeholder strings and every API request (auth, REST, storage) failed with Invalid authentication credentials. The app could not sign in or read data.

Fix: the supabase-kong service renders the key template at startup (canonical Supabase pattern), substituting the generated keys. docker/kong.yml double quotes converted to single so the shell substitution is safe.

3. Most tables were unreachable by the API roles

The self-host psql migrate path created objects as POSTGRES_USER, and docker/init's default privileges are keyed to a different role, so only a handful of the 26 tables were granted to anon/authenticated/service_role. create-admin failed with 42501 permission denied for table profiles, blocking setup.

Fix: run-self-host-migrations.sh sets ALTER DEFAULT PRIVILEGES for POSTGRES_USER before the migration loop (mirroring the platform), so every migration-created object is granted at creation and each migration's own hardening REVOKE stays the final word. The internal ledger gets RLS enabled and is intentionally excluded from grants.

A first attempt at #3 used a blanket GRANT ... ON ALL after the loop. Code review caught that it clobbered two hardening migrations (profile-role self-escalation, retro facilitator_token leak). The before-loop approach here preserves both, verified on a live boot.

Docs

  • README: dropped the inaccurate "nothing else to install" line (step 2 needs Node/pnpm) and added a Docker-only env-gen command; documented the opt-in dev overlay and the production build.

Guards (run in CI via test:self-host)

verify-self-host-migrations.test.mjs now also asserts: no auto-loaded dev override; the default web service is production; Kong substitutes keys; kong.yml is substitution-safe; and default privileges run before the loop with no blanket GRANT ... ON ALL (the exact regression that caused the security holes). 8/8 pass.

Verification

A fresh docker compose up -d over wiped volumes: migrations apply, Kong serves real keys, and the full flow passes — create-admin -> seed-demo -> complete -> password sign-in -> authenticated PostgREST read of the seeded series -> GET / 200. Hardening intact: authenticated has no table-level UPDATE on profiles (role/has_full_access not grantable) and anon cannot EXECUTE any internal retro helper.

CI (supabase start) is unaffected: docker/kong.yml and run-self-host-migrations.sh are only used by the compose self-host path.

…n stack

The documented one-command self-host was non-functional on a fresh box. Three
defects, each reproduced and fixed against a live boot:

- The git-tracked docker-compose.override.yml auto-loaded on `docker compose up`
  and swapped the web app to the Next.js dev server (target: deps, pnpm dev,
  NODE_ENV=development) while exposing Postgres on the host. Renamed to
  docker-compose.dev.yml so the dev overlay is opt-in; the default command now
  builds and serves the production image (node server.js).

- supabase-kong mounted docker/kong.yml with literal ${ANON_KEY}/${SERVICE_ROLE_KEY}
  placeholders that nothing substituted, so key-auth registered the placeholders
  and rejected the real generated keys; every API call (auth/REST/storage) failed
  with "Invalid authentication credentials". Kong now renders the key template at
  startup; kong.yml quotes converted to single so the substitution is safe.

- run-self-host-migrations.sh did not establish the API-role grants for objects
  created as POSTGRES_USER (docker/init's defaults are keyed to a different role),
  so only a handful of tables were reachable and setup failed with 42501. Sets
  ALTER DEFAULT PRIVILEGES before the migration loop so each migration's own
  hardening REVOKE stays final, and enables RLS on the internal ledger.

README: correct the "nothing else to install" claim (add a Docker-only env-gen
path), document the opt-in dev overlay, note the production build.
verify-self-host-migrations.test.mjs guards all three against regression (CI).

Verified on a fresh `docker compose up -d` over wiped volumes: setup, sign-in,
and an authed data read all succeed; profile role-escalation and retro
facilitator-token hardening remain intact.
@pratikbodkhe pratikbodkhe merged commit e42adb9 into main Jul 6, 2026
18 checks passed
@pratikbodkhe pratikbodkhe deleted the fix/self-host-prod-default branch July 6, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant