feat(compose): deployment-mode toggles for Supabase + storage#3
Merged
Conversation
Extract buildS3Config() with env-driven region (R2_REGION, default 'auto') and forcePathStyle (R2_FORCE_PATH_STYLE=true|*, default false). Defaults preserve current Garage behavior; new envs unlock AWS S3 (real region) and MinIO (path-style).
Reorganize .env.example around MIKE_SUPABASE_MODE and MIKE_STORAGE_MODE. Each section labels which mode(s) need it, and external-mode secrets get their own variables (EXTERNAL_SUPABASE_*, EXTERNAL_POSTGRES_URL) so they don't collide with the bundled-mode generated ones.
Add 'profiles:' to postgres, gotrue, postgrest, garage, init-garage. Use defaulted EXTERNAL_*:- lookups in the backend env block and frontend build args so the same compose file covers all six MIKE_SUPABASE_MODE x MIKE_STORAGE_MODE combinations. Remove depends_on edges that would auto-deselect always-on services when their profile-gated targets are inactive (init-db, mike-backend, caddy); service scripts already handle the waits.
Move &search_path=auth inside the default expression so operators providing EXTERNAL_POSTGRES_URL without a query string don't get a broken URL with '&' instead of '?' as the first separator. Introduce a dedicated EXTERNAL_SUPABASE_GOTRUE_PG_URL var (matches the existing EXTERNAL_POSTGRES_AUTHENTICATOR_URL pattern) so operators can override GoTrue's connection string without affecting PostgREST/init-db. Also declare EXTERNAL_POSTGRES_AUTHENTICATOR_URL in .env.example — it was referenced in compose but missing from the operator-facing template.
init-db.sh: accept PG_URL connection string (preferred), fall back to PGHOST/PGUSER/PGPASSWORD/PGDATABASE. Drops the GoTrue-healthcheck wait in favor of the auth.users-table poll, which works for hosted Supabase where GoTrue isn't ours. backend-entrypoint.sh: skip /run/secrets/garage.env source when R2_ACCESS_KEY_ID is already in env (external storage). Wait up to 60s in bundled mode for init-garage to write the file.
Bundled-fallback branch now passes -h/-U/-d flags to psql/pg_isready and lets psql read PGPASSWORD silently from env, so a libpq error message can't echo the password into docker logs. PG_URL branch unchanged (operator chose to put creds in URL themselves). Replace 'ls /migrations/... | sort | while' with a 'for' loop over a literal glob, with case-based handling for the no-match scenario. Eliminates SC2012, surfaces empty/missing migrations dir explicitly via the 'applied N incremental migrations' summary line.
Reads MIKE_SUPABASE_MODE and MIKE_STORAGE_MODE from .env, validates them, maps to docker compose --profile flags, dispatches. Adds shell tests for all six valid combos plus invalid-mode rejection.
1. Use ${PROFILES[@]+"${PROFILES[@]}"} so the all-external mode
(empty PROFILES array) doesn't trip 'unbound variable' on Bash 3.2
(stock macOS /bin/bash) under set -u.
2. read_var regex tightened from [[:space:]]*# to [[:space:]]+# so a
value containing '#' (e.g. hunter2#withhash) isn't silently truncated
at the first '#'. Standard .env convention: # is a comment only when
preceded by whitespace.
generate-secrets.sh now reads MIKE_SUPABASE_MODE and MIKE_STORAGE_MODE and only generates the secrets the chosen modes use. Missing operator- supplied values (EXTERNAL_SUPABASE_*, R2_* for external storage, etc.) emit WARN lines so the operator knows what to fill in. current_value regex tightened ([[:space:]]+# instead of *) to match the .env convention: '#' is only a comment when preceded by whitespace. Matches the same fix applied to mike CLI in 5fb9f95.
scripts/smoke-test.sh boots the default stack, waits for Caddy on $MIKE_PORT, hits the backend, and tears down. README gains a 'Deployment modes' section with the six-combo matrix and the frontend-rebuild caveat for hosted-Supabase mode.
backend/src/index.ts exposes app.get('/health'); Caddy's handle_path
/backend/* strips the prefix, so the smoke test URL must be
/backend/health, not /backend/healthz.
- init-db.sh: sanitize PG_URL when logging so password doesn't leak to 'docker compose logs init-db'. Only affects the PG_URL branch; PGHOST branch already used a non-secret CONN_DESC. - generate-secrets.sh: warn about EXTERNAL_POSTGRES_AUTHENTICATOR_URL and EXTERNAL_SUPABASE_GOTRUE_PG_URL in bundled-byo-db mode. Without them PostgREST/GoTrue default to the deselected bundled postgres host and silently fail to start. - README.md: use ./mike build mike-frontend consistently (the Configuration section still had a stale docker compose form).
storage.test.ts: wrap each test body in try/finally so a failing assertion doesn't leak env vars to the next test. test/mike.test.sh: two new cases proving the read_var regex requires whitespace before '#' (locks in the fix from 5fb9f95). Brings test count from 8 to 10. scripts/smoke-test.sh: header comment now states explicitly that the script only covers the default (bundled-full + bundled) mode.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds two env vars in
.envthat select, per axis, whether each backplane is bundled (containers we ship) or external (cloud / BYO):MIKE_SUPABASE_MODE∈ {bundled-full,bundled-byo-db,external}MIKE_STORAGE_MODE∈ {bundled,external}3 × 2 = 6 combinations. Defaults (
bundled-full+bundled) match the current stack exactly, so existing installs upgrade with no config changes.New
./mikeCLI wrapper reads the modes, validates them, and dispatches todocker composewith the right--profileflags../mike up,./mike down,./mike build,./mike logs,./mike migrate, plus./mike --print-profilesfor debugging.Why
The original compose stack required running every component locally. Many self-hosters want to combine self-host compute with managed Postgres (Neon, RDS), Hosted Supabase, or managed S3 (R2, AWS, MinIO). The single-toggle model handles all six combos from one compose file.
The toggle names +
EXTERNAL_*env conventions are designed to translate 1:1 to a futurevalues.yaml, so the same mental model carries to Helm/k3s when that lands (separate PR — see "Not in this PR" below).How
docker-compose.yml): five services gainprofiles:gates —postgres(db-bundled),gotrue+postgrest(supabase-shim),garage+init-garage(storage-bundled). Backend env and frontend build args use${EXTERNAL_*:-fallback}defaulted lookups so one file covers every combo.depends_onedges that would have auto-deselected always-on services through profile gating were removed; their script-level waits cover ordering../mikeCLI wrapper (mike): bash, repo-root, executable. POSIX-shell-styleawkparses.envwithout sourcing (no arbitrary shell execution). Validates modes, computes--profileflags, dispatches. Bash 3.2-safe empty-array splice for the all-external case on stock macOS.docker/init-db.sh): acceptsPG_URL(hosted Supabase / BYO Postgres) with fallback toPGHOST/PGUSER/PGPASSWORD/PGDATABASE. Bundled-fallback branch passes-h/-U/-das args and letspsqlreadPGPASSWORDfrom env, so the password never lands in URL form and never leaks todocker compose logs.for-loop over[0-9][0-9][0-9]_*.sqlglob with no-match guard.docker/backend-entrypoint.sh): skips sourcing/run/secrets/garage.envwhenR2_ACCESS_KEY_IDis already in env (external storage mode); waits up to 60s in bundled mode forinit-garageto write the file.scripts/generate-secrets.sh): generates only the secrets the chosen modes need (no JWT/SUPABASE keys in external Supabase mode; no GARAGE_* tokens in external storage mode). EmitsWARN: …lines on stderr for missing operator-suppliedEXTERNAL_*/R2_*values, including thebundled-byo-dbrequirements forEXTERNAL_POSTGRES_AUTHENTICATOR_URLandEXTERNAL_SUPABASE_GOTRUE_PG_URL.backend/src/lib/storage.ts): extractedbuildS3Config()fromgetClient(). AddsR2_REGION(defaultauto) andR2_FORCE_PATH_STYLE(defaultfalse) reads, unlocking AWS S3 (real region for SigV4) and MinIO (path-style). 4-line behavioral change, defaults preserve Garage exactly..env.examplerewritten around the toggle model — each section labels which mode(s) need it, EXTERNAL_* groups are visible and documented.### Deployment modessubsection with the six-combo matrix, switching procedure (including the./mike build mike-frontendrebuild when crossing into/out ofexternalSupabase mode), and a hosted-Supabase migration note.Test plan / verification
backend/test/storage.test.ts+ 17 existing). Each test wrapped intry { … } finally { restoreEnv(); }so an assertion throw doesn't leak env to the next test.test/mike.test.sh): 10/10 — 6 valid mode combos + 2 invalid-mode rejections + 2 cases locking theread_varregex (#is only a comment when preceded by whitespace)./run/secrets/garage.env).docker compose config --servicesvalidated for all 6 profile combinations — each produces exactly the expected service set.scripts/smoke-test.sh) against full Docker stack on a laptop: stack came up, Caddy responded on:80,/backend/healthreturned{"ok":true}, clean teardown via./mike down --remove-orphans. Default mode (bundled-full+bundled) only; other modes need manual verification with real external services.Not in this PR
EXTERNAL_*env conventions, andinit-db.sh's mode-awareness are all designed so the same shapes translate tovalues.yamland templated manifests without changing this PR's surface.Commits
14 commits, conventional commit style. Several
fix(...)commits land mid-chain — they're review-loop corrections caught by spec-compliance + code-quality review during development, kept as separate commits so the PR history is auditable instead of squashed into a single mega-feat commit.