feat(cli): add SUPABASE_USE_SLIM_IMAGES flag for slim ghcr images - #6329
feat(cli): add SUPABASE_USE_SLIM_IMAGES flag for slim ghcr images#6329avallete wants to merge 26 commits into
Conversation
When SUPABASE_USE_SLIM_IMAGES is "true" or "1", the legacy shell resolves its local-stack Docker images from the slim ghcr.io/supabase/cli/<service> builds instead of the docker.io images pinned in the embedded Dockerfile manifest, keeping the Dockerfile version pins translated into the slim tag scheme via @supabase/stack's catalog (new "./versions" package export). Services with no slim build keep their docker.io reference: kong, the differ/migra/pgprove job images, the pg 13/14/15 major-version fallbacks, OrioleDB overrides, and the deno_version = 1 edge-runtime pin. Slim refs bypass the SUPABASE_INTERNAL_IMAGE_REGISTRY/ECR rewrite (they exist only at ghcr.io/supabase/cli), status/--exclude short names stay flag-invariant, services-command image overrides are never translated, and the declarative stale-container guard now rejects slim/non-slim family mismatches. With the flag unset, behavior is byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92576b1233
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@6c57060f76adaacd20590de6a68ace93277a5478Preview package for commit |
With SUPABASE_USE_SLIM_IMAGES on and a resolved ghcr.io/supabase/cli/postgres ref, the main-db and shadow-db container specs switch to the slim image's contract: the image entrypoint is kept (entry.sh self-initializes and passes container argv through to postgres), config.toml db settings travel as -c argv pairs, and the CLI's bootstrap schema plus pgsodium root key are staged via the existing secret-file docker cp — the image's bundled migrate.sh runs /etc/postgresql.schema.sql as its first-boot postinit, giving exactly-once semantics without any CLI-side marker. PGSODIUM_KEY_FILE keeps the root key deterministic for the non-root (uid 65532) image user. start --from-backup refuses a slim resolved ref with a typed error until the restore flow learns the slim contract. Flag off (or a registry override landing on docker.io) stays byte-identical. Verified against a real slim db start: healthy container, settings applied, all three schema templates present exactly once across restarts, root key loaded, shadow spec exercised. Shadow flows additionally need a slim-services image fix (empty supautils GUCs block "create extension pg_net"). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efba3aa535
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…slim-images-flag-c95c16 # Conflicts: # apps/cli/src/legacy/commands/start/SIDE_EFFECTS.md # apps/cli/src/legacy/shared/db-bootstrap/postgres.service.unit.test.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cbb49da949
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Reusing a named database volume initialized by the docker.io postgres image under SUPABASE_USE_SLIM_IMAGES crash-loops the slim image's non-root user against the 700-mode PGDATA until the health check times out. Before creating the container on an existing volume, a cheap docker-run probe now checks that the slim image's own user can read PG_VERSION and fails fast with remediation (stop --no-backup to reset, or unset the flag) when it cannot. Also documents the flag in the SIDE_EFFECTS of db pull, services, and the declarative schema commands, and records why the deno1 edge-runtime tag check intentionally also covers an explicit pin of that tag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 429bad2c79
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…slim contract The slim entrypoint runs a temporary server during first-boot init, so the slim specs' healthcheck now requires PID 1 to be the final postgres process before pg_isready counts (same gate the stack's docker path uses); docker.io keeps the plain pg_isready test byte-identically. The pg_dump one-shot runner (db dump, db pull, migration squash) now overrides the entrypoint to bash on slim refs — the slim entrypoint would otherwise initdb an empty cluster and hand the dump command to postgres. Also rewords a settings-renderer comment off Go-authority framing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 89b4e15a33
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…t coupling Postgres must write postmaster.pid and WAL under PGDATA, so the reused-volume guard now also requires write access (test -w on the mount) — a read-only- accessible volume previously slipped past the guard into the same crash-loop it exists to prevent. The error and probe naming follow (Inaccessible / AccessibleToImage). The new integration tests narrow errors with instanceof on the exported classes instead of asserting the _tag field through casts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 235924d28f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…y resolver The legacy resolver and the functions-path edgeRuntimeImage read the same .temp/edge-runtime-version pin but disagreed under the slim flag: a stale v1.68.4 pin (left by an earlier deno_version = 1 run) tag-swapped onto the slim base yields an unpullable ref, since no slim build of that tag exists. Both resolvers now apply the same docker.io exception from one shared constant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Docker CLI healthchecks are always CMD-SHELL, so distroless slim auth/storage/studio/pg-meta cannot probe via wget/node. Slim realtime one-shot jobs must override entry.sh, auth migrate argv drops the binary name, storage skips migrate-call.js, and vector/edge-runtime keep the image entrypoint instead of a shell wrapper. Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
Slim realtime already migrates and seeds from entry.sh (SEED_SELF_HOST=true). The docker.io eval health_check starts a second BEAM against Postgres and fails under a slow daemon. Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
Slim edge-runtime runs as uid 65532 and cannot read /root, so copy the main-service template to /tmp. Slim postgres leaves the postgres role as a non-superuser, so grant SUPERUSER and create the vector extension before storage-api's vector-bucket migrations run. Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
The postgres session cannot ALTER ROLE itself after the slim image demotes it. Append SUPERUSER plus CREATE EXTENSION vector to the migrate.sh postinit schema so supabase_admin applies it before the CLI connects. Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
Distroless storage runs as uid 65532. A named volume at /mnt is created root-owned because that path is missing from the image, so mkdir /mnt/stub fails with EACCES. /home/nonroot is owned by the image user and Docker copies that ownership into an empty volume. Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
Align the embedded pooler pin with the stack catalog and the oldest published slim ghcr.io/supabase/cli/pooler tag. Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01e32ba4c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… seeding Three slim-runtime gaps from PR review, each verified against the real ghcr images (both distroless: no sh/busybox anywhere in their layers): - The pg-delta legacy engine's edge-runtime script runner replaces the entrypoint with sh to heredoc its files, which cannot run on the slim edge-runtime image — it now resolves through a docker.io-pinned variant of the shared resolver (version pin still honored), the same locked-exception shape as the deno1 tag. - The slim storage image cannot carry a docker healthcheck, so bulk readiness accepted it at Running and bucket seeding raced its migrations — the health wait now probes HEAD /storage/v1/status through Kong on the slim path (including the --ignore-health-check storage recheck), skipped when kong is excluded; docker.io keeps the container healthcheck byte-identically. - The slim functions-serve multiline-secret rejection was a bare Error; it is now an exported tagged error classified invalidConfig, failing through the Effect channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a83e369fe8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The 17.6.1.166 slim build ships the full supautils policy (privileged extensions allowlist incl. pg_net and vector, privileged_extensions_superuser = supabase_admin), unblocking the shadow-database flows under SUPABASE_USE_SLIM_IMAGES — verified live: create extension pg_net succeeds as the demoted postgres role, and db diff runs its slim shadow end to end. The postgres demote is still present in the bundle migrations, so the CLI postinit re-promotion stays. docker.io publishes the same tag, so the flag-off pin moves in lockstep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both image families run the same bundled migrations, including the unconditional postgres demotion, so re-promoting postgres on slim made the two families diverge (rolsuper=t on slim vs f on docker.io). The vector pre-create it carried was never needed either: storage-api creates the extension itself in its own storage_vectors database through the supautils allowlist, which the shared config recipe now ships on slim. Verified by a full pg_settings/pg_roles/pg_extension parity diff between a slim and a docker.io stack of the same release: roles and extensions diff empty, postgres rolsuper=f on both, cron/vault custom-script grants intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes for `[db.pooler] enabled = true`: - The healthcheck exec'd curl, which the distroless slim supavisor image does not ship, so the container could never turn healthy and `start` failed outright under SUPABASE_USE_SLIM_IMAGES. Probe with the image's /bin/busybox wget instead, the same pattern realtime and analytics use. - Supavisor's internal metadata Ecto pool defaults to 25 connections, half of the slim local-dev max_connections=50. With every service enabled the database saturated at 50/50 and realtime failed with DatabaseLackOfConnections. Cap the meta pool at 5, which is plenty for the single local tenant and also stops wasting 25 of docker.io's 100. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ge to v1.71.0 storage-api prefers IMAGE_TRANSFORMATION_ENABLED over the legacy ENABLE_IMAGE_TRANSFORMATION key the CLI sets, and the slim storage image bakes IMAGE_TRANSFORMATION_ENABLED=false as an image ENV default, silently disabling transforms regardless of config.toml. Set the modern key explicitly so the config value wins on both image families. storage-api v1.71.0 also fixes the slim image's /home/nonroot volume-root mode (0700 -> 0711 65532), which blocked imgproxy (uid 999) from traversing the shared storage volume. Verified on a fresh stack: upload, public download, 32x32 render, vector buckets, and TUS create all green with no manual intervention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood report — slim images through the full CLI surfaceLocal end-to-end validation behind this PR's last five commits, run on macOS/OrbStack (arm64) from this branch's checkout. Documented here since PR descriptions stay validation-free by repo convention. Images under test
The postgres parity comparison (below) was run same-release — slim-from-#264 vs docker.io Round 1 — CLI flow matrix, flag on (fresh volumes per run)
Skipped: Round 2 — postinit removal (commit
|
| Service | Test | Result |
|---|---|---|
| PostgREST | insert + select /rest/v1 |
✅ |
| GraphQL | pg_graphql query (extension created as demoted postgres via allowlist; not pre-created on either family) |
✅ |
| Auth | signup + password recover | ✅ |
| Mailpit | recovery email via API | ✅ delivered |
| Storage | upload + public download | ✅ byte-identical |
| Imgproxy | /render/image/…?width=32 |
✅ after the two storage fixes below |
| Vector buckets | create + ListVectorBuckets | ✅ |
| Edge functions | functions new template served by slim edge-runtime v1.74.3 via Kong |
✅ {"message":"Hello slim!"} |
| Realtime | broadcast round-trip and postgres_changes INSERT delivery | ✅ (an initial "failure" was the test inserting before the async Subscribed to PostgreSQL ack — test race, not a bug) |
| Pooler | psql through supavisor transaction port 54329 | ✅ after the healthcheck fix |
| Studio | home + Advisors, Table Editor rendering live rows, Logs page | ✅ in-browser |
| Logs pipeline | vector → analytics → Studio postgres logs | ✅ 100 entries |
Findings from this round map to commits 94cd3a42e (pooler healthcheck + DB_POOL_SIZE=5; the db had saturated at 50/50 connections with supavisor_meta holding 25) and 691ff6b4f (IMAGE_TRANSFORMATION_ENABLED + storage bump).
Round 5 — storage v1.71.0 (published release, immutable digest)
Fresh project, fresh volume, zero manual intervention:
- volume root inherits
711 65532:65532from the image (the v1.70.x slim image baked0700, which blocked imgproxy uid 999 — the one finding that needed an image-side fix); - upload → public download (byte-identical) → 32×32 render all 200;
- vector bucket + TUS resumable create (201) pass; storage logs show zero warn/error lines;
- re-validated the pooler healthcheck and transformation-env fixes on a clean slate.
Known notes for reviewers
- Slim pooler/realtime/analytics ship
/bin/busybox; auth/pg-meta/studio remain probe-less on slim (pre-existing, unchanged here). packages/stack/src/ServiceCatalog.tspins drift from the Dockerfile template by design (postgres.165vs.166predates this PR); not touched.- The supavisor
DB_POOL_SIZE=5cap applies to both image families intentionally — docker.io was also spending 25 of its 100 connections on the meta pool.
Round 6 — shadow-baseline cache (SUPABASE_SHADOW_CACHE=1) on slim
The tar pool (~/.supabase/cache/shadow-baseline/) exports/restores initialized PGDATA, which is exactly the kind of path the slim image's non-root (uid 65532) contract could break. Verified on both families:
| Check | Result |
|---|---|
slim db diff cold → warm |
21.2s → 3.3s; tars exported on miss, restored on hit |
| correctness against a cached baseline | a live CREATE TABLE made after caching is correctly emitted by the next diff |
db reset / fresh-project start under the flag |
always first-boot (init line present, rolsuper=f after) — the pool is consumed by shadow flows only, main db never restores from tar |
| cross-family key isolation | flag-off db diff minted a distinct tar key (the cache key embeds postgres_image=<ref>); no slim/docker.io reuse in either direction |
docker.io db diff cold → warm |
22.9s → 3.4s — cache behavior identical across families |
No code changes needed — the existing cache seam works with the slim postgres image as-is.
🤖 Generated with Claude Code
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 691ff6b4fd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Slim-services #280 ships pg_dumpall/uniq, max_connections=100, /mnt owned by uid 65532, and exec-form HEALTHCHECKs. Drop the CLI workarounds those images replace and fix the remaining orchestration: omit the extra gen-types node argv, mount functions off /root, restore the realtime one-shot before user migrations, slim-translate only the current Dockerfile pin, remount storage at /mnt with a family write probe, and remove the unconditional DB_POOL_SIZE=5 cap.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0bede57286
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The slim edge-runtime image runs as uid 65532 (distroless nonroot), which breaks two docker.io-root assumptions in the functions Docker paths on native Linux / WSL2: - functions download --use-docker extracted straight into the host supabase/functions bind, which uid 65532 cannot write when the host directory belongs to a different uid (EACCES). The slim flow now keeps the unbundle output container-local (/tmp/unbundle/<slug>, eszip bound read-only under /tmp/eszips) via a create -> start --attach -> cp -> rm --force lifecycle, and the CLI writes the copied files host-side as the invoking user, with the same path-containment checks the multipart downloader applies. - The shared supabase_edge_runtime_<id> Deno-cache volume mounts at /root/.cache/deno, which the slim runtime neither reaches (0700 /root) nor uses (Deno caches under $HOME=/home/nonroot), and a docker.io-seeded volume is root-owned and can never be made writable by uid 65532 by remounting it. Slim runs (download/deploy/serve/start) now use their own supabase_edge_runtime_slim_<id> volume mounted over /home/nonroot, which Docker seeds from the image's 65532-owned home — same pattern as the slim Storage volume. Both volume families carry the project label that supabase stop --no-backup prunes by. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DrSmrBgbWfva1KbYWGMV4c
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c0b20cd2a3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Branch-local workflow_dispatch job that pulls the released slim edge-runtime image and validates it against this branch's slim functions Docker flows on a native Linux runner (CLI uid 1001 vs container uid 65532 — the condition the original EACCES reports came from): image layout smoke (0755 /root, busybox sh+cat, nonroot home), a negative control reproducing the pre-fix direct-bind EACCES, slim download via cp-out staging, slim deploy from a /root checkout, the slim-only Deno-cache volume, and the docker.io baseline. To be removed once the image + CLI pairing is validated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DrSmrBgbWfva1KbYWGMV4c
45dac45 to
9b2bcba
Compare
Validation run passed against the released slim edge-runtime v1.74.3 image (supabase/cli actions run 33154293277): image layout smoke, pre-fix EACCES negative control, slim download cp-out staging, slim deploy from a /root checkout, slim-only Deno-cache volume, and the docker.io baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DrSmrBgbWfva1KbYWGMV4c
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b2bcba950
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…-runtime tag A missing Postgres volume used to mark the run fresh, so leftover docker.io data was pruned on rollback. Probe those volumes first, skip freshness on slim --from-backup, and take the Dockerfile tag from the last colon so slim GHCR refs do not resolve to the registry host. Co-authored-by: Cursor <cursoragent@cursor.com>
…cker.io Rebuilt ghcr postgres/storage/auth/edge-runtime pins start as root, ship sh/wget, and restore after bundle migrate. The flag is only an image-name rewrite; leftover 65532/volume/boot/--from-backup/dump-bash/migrate forks are gone. Storage pin is v1.72.1 to match the rebuilt slim tag. Co-authored-by: Cursor <cursoragent@cursor.com>
Landed: slim images are docker.io-shaped
Watch for later
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be0af52024
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
/ai-review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c57060f76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const rawTag = image.slice(tagSeparator + 1); | ||
| const tag = alias === "vector" ? rawTag.replace(/-alpine$/, "") : rawTag; | ||
| return dockerImageForService(service, tag); |
There was a problem hiding this comment.
Route vector and pooler to the slim CLI repositories
When SUPABASE_USE_SLIM_IMAGES is enabled and start includes Vector or Supavisor, dockerImageForService honors the existing ServiceCatalog.ts artifact overrides, producing ghcr.io/supabase/vector:<tag> and ghcr.io/supabase/supavisor:<tag> rather than the intended ghcr.io/supabase/cli/vector:<tag> and ghcr.io/supabase/cli/pooler:v<tag>. These references then fail isSlimImageRef, so the newly added slim-specific Vector and Pooler container wiring is skipped; Vector additionally has -alpine stripped before the unrelated image is pulled. Construct these two CLI slim repository identities explicitly instead of delegating them to the general stack artifact resolver.
Useful? React with 👍 / 👎.
|
Split into a stacked review so code and docs no longer share one diff:
Closing this PR in favor of that stack. The original branch |
There was a problem hiding this comment.
Superseded by a newer AI review
🤖 AI Review
The 11 reported items deduplicate to 8 findings. Five major runtime/test/version issues, two minor readiness/documentation concerns, and one nit were confirmed against the checked-out code. The most consequential defects are the broken Pooler/Vector slim resolution, incompatible slim one-shot migration commands, unsynchronized service pins, and a guaranteed failing pg-delta test.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟠 MAJOR | apps/cli/src/shared/services/slim-images.ts:61 |
image-resolution |
claude+codex | Pooler and Vector resolve through catalog entries for upstream mirrors instead of the intended CLI slim repositories, so their slim runtime adaptations never activate and the new assertions fail. |
| 🟠 MAJOR | apps/cli-go/pkg/config/templates/Dockerfile:14 |
dependency-management |
claude | The manifest downgrades Realtime and Logflare and leaves the stack catalog unsynchronized with all five changed service pins. |
| 🟠 MAJOR | apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.integration.test.ts:309 |
test-correctness |
claude | The new pg-delta image-current tests hardcode the old Postgres tag, making the success case fail and preventing the family-mismatch case from isolating the behavior named by the test. |
| 🟠 MAJOR | apps/cli/src/legacy/shared/db-bootstrap/db-setup.ts:24 |
runtime-compatibility |
claude+codex | The slim one-shot migration implementation contradicts its declared image contracts: Storage still runs migrate-call.js in the slim image said not to contain it, and Auth retains the docker.io gotrue argv despite documenting a baked auth entrypoint. |
| 🟠 MAJOR | apps/cli/src/legacy/commands/start/services/gotrue.service.ts:657 |
readiness |
codex | Slim Auth, pg-meta, and Studio lose their application-level readiness checks and are treated as ready as soon as their processes are running. |
| 🟡 MINOR | apps/cli/src/legacy/shared/legacy-edge-runtime-image.ts:68 |
documentation |
claude+codex | The pg-delta shell runner is slim-aware even though its call-site comment and multiple SIDE_EFFECTS documents say it remains on docker.io. |
| 🟡 MINOR | apps/cli/src/legacy/commands/start/services/vector.service.ts:351 |
startup-ordering |
claude | The intended slim Vector branch removes the existing wait-for-Logflare gate before starting Vector. |
| ⚪ NIT | apps/cli/src/legacy/commands/start/services/storage.service.ts:210 |
dead-code |
claude | The change leaves unnecessary single-consumer abstractions and a no-op object spread from reverted slim variants. |
Stats
Claude findings: 7 · Codex findings: 4 · Confirmed: 8 · Refuted: 0 · Uncertain: 0
Models: claude-opus-5 + gpt-5.6-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
|
|
||
| const rawTag = image.slice(tagSeparator + 1); | ||
| const tag = alias === "vector" ? rawTag.replace(/-alpine$/, "") : rawTag; | ||
| return dockerImageForService(service, tag); |
There was a problem hiding this comment.
🟠 MAJOR · image-resolution · source: claude+codex
Pooler and Vector resolve through catalog entries for upstream mirrors instead of the intended CLI slim repositories, so their slim runtime adaptations never activate and the new assertions fail.
Evidence: slim-images.ts:21-22 maps the aliases to pooler/vector and :61 calls dockerImageForService. ServiceCatalog.ts:295 and :307 resolve those services as ghcr.io/supabase/vector and ghcr.io/supabase/supavisor, while slim-images.ts:120 recognizes only ghcr.io/supabase/cli/. slim-images.unit.test.ts:36-37 expects CLI pooler/vector repositories.
Suggested fix: Model the slim repository and tag scheme explicitly instead of reusing the upstream-mirror catalog entries, then retain Vector's intended tag normalization and update the tests accordingly.
| FROM supabase/realtime:v2.129.3 AS realtime | ||
| FROM supabase/storage-api:v1.72.1 AS storage | ||
| FROM supabase/logflare:1.50.4 AS logflare |
There was a problem hiding this comment.
🟠 MAJOR · dependency-management · source: claude
The manifest downgrades Realtime and Logflare and leaves the stack catalog unsynchronized with all five changed service pins.
Evidence: Dockerfile:14 and :16 contain realtime v2.129.3 and logflare 1.50.4, whereas the diff removes v2.129.9 and 1.50.6. Dockerfile:2, :12, and :15 also change Postgres, Pooler, and Storage, but ServiceCatalog.ts still declares 17.6.1.165, 2.9.7, and v1.71.0 along with the removed Realtime/Logflare versions.
Suggested fix: Resolve whether the two downgrades are intentional, restore the current pins if not, and run the repository's sync:versions script so ServiceCatalog.ts matches the Dockerfile manifest.
| const { layer } = setup(dir, { dbInspectImage: "supabase/postgres:17.6.1.165" }); | ||
| return Effect.gen(function* () { | ||
| const seam = yield* LegacyDeclarativeSeam; | ||
| const exit = yield* seam.ensureLocalPostgresImageCurrent().pipe(Effect.exit); | ||
| expect(Exit.isSuccess(exit)).toBe(true); | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| }).pipe(Effect.provide(layer)); |
There was a problem hiding this comment.
🟠 MAJOR · test-correctness · source: claude
The new pg-delta image-current tests hardcode the old Postgres tag, making the success case fail and preventing the family-mismatch case from isolating the behavior named by the test.
Evidence: legacy-pgdelta.seam.integration.test.ts:292 and :309 use 17.6.1.165, while Dockerfile:2 now pins 17.6.1.166. The default major version is 17, legacyResolveDbImage selects the Dockerfile image, and legacy-pgdelta.seam.layer.ts:270-273 requires matching tags.
Suggested fix: Build both fixtures from dockerfileServiceImageRaw("pg") and change only the image family in the family-mismatch case.
| * Slim Storage has no `migrate-call.js`, so that job uses the docker.io | ||
| * storage image. Slim Auth still runs a one-shot `migrate` under the baked | ||
| * `auth` ENTRYPOINT. |
There was a problem hiding this comment.
🟠 MAJOR · runtime-compatibility · source: claude+codex
The slim one-shot migration implementation contradicts its declared image contracts: Storage still runs migrate-call.js in the slim image said not to contain it, and Auth retains the docker.io gotrue argv despite documenting a baked auth entrypoint.
Evidence: db-setup.ts:24-26 declares the Storage docker.io fallback and Auth entrypoint behavior, but :865 uses input.images.storage with ["node","dist/scripts/migrate-call.js"] at :871, while :876 uses input.images.auth with ["gotrue","migrate"] at :889. db-setup.unit.test.ts:392-399 explicitly locks in those contradictory slim-image commands.
Suggested fix: Use the corresponding docker.io Storage image for that one-shot, and provide the slim Auth entrypoint only the migrate argument if that is its actual contract. Update the unit test to assert the compatible variants.
| // Distroless slim auth has no wget/curl/sh. Omitting the Docker healthcheck | ||
| // makes `legacyCheckContainerReady` treat `Running` as ready (same as PostgREST). | ||
| ...(slim | ||
| ? {} |
There was a problem hiding this comment.
🟠 MAJOR · readiness · source: codex
Slim Auth, pg-meta, and Studio lose their application-level readiness checks and are treated as ready as soon as their processes are running.
Evidence: gotrue.service.ts:657-660, pg-meta.service.ts:68-71, and studio.service.ts:190-193 omit healthchecks for slim references. health-check.ts:225-233 accepts any running container with no health state, while its HTTP gateway exceptions at :354-361 cover only PostgREST and Edge Runtime.
Suggested fix: Add host- or gateway-based HTTP readiness probes for the three distroless services instead of relying solely on container process state.
| * Same resolution as {@link legacyResolveEdgeRuntimeImage}. The slim image now | ||
| * ships `sh`, so shell-entrypoint callers no longer need a docker.io pin. | ||
| */ | ||
| export const legacyResolveEdgeRuntimeShellImage = legacyResolveEdgeRuntimeImage; |
There was a problem hiding this comment.
🟡 MINOR · documentation · source: claude+codex
The pg-delta shell runner is slim-aware even though its call-site comment and multiple SIDE_EFFECTS documents say it remains on docker.io.
Evidence: legacy-edge-runtime-image.ts:68 aliases the shell resolver to the slim-aware resolver, and legacy-edge-runtime-script.layer.ts:107 uses it. bootstrap/SIDE_EFFECTS.md:83, db/push/SIDE_EFFECTS.md:57, db/reset/SIDE_EFFECTS.md:143, and start/SIDE_EFFECTS.md:180 state that the runner stays on docker.io.
Suggested fix: Determine the actual slim Edge Runtime shell contract, then either keep the slim-aware resolver and correct the stale comments/docs or restore a docker.io-only shell resolver.
| return legacyUsesSlimRuntime(input.image) | ||
| ? { | ||
| image: input.image, | ||
| containerName: input.containerName, | ||
| env: input.dockerSocketPlan.env, | ||
| cmd: ["--config", "/etc/vector/vector.yaml"], | ||
| secretFiles: [{ containerPath: "/etc/vector/vector.yaml", content: vectorYaml }], | ||
| binds: input.dockerSocketPlan.binds, | ||
| healthcheck: legacySlimBusyboxWgetHealthcheck("http://127.0.0.1:9001/health"), | ||
| restartPolicy: "unless-stopped", | ||
| securityOpt: input.dockerSocketPlan.securityOpt, | ||
| networkId: input.networkId, | ||
| networkAliases: LEGACY_VECTOR_NETWORK_ALIASES, | ||
| labels: {}, | ||
| } |
There was a problem hiding this comment.
🟡 MINOR · startup-ordering · source: claude
The intended slim Vector branch removes the existing wait-for-Logflare gate before starting Vector.
Evidence: vector.service.ts:291-298 builds the docker.io entrypoint with an until-wget loop against Logflare. The slim branch at :351-365 starts Vector directly with its config and contains no equivalent gate; start.handler.ts creates Logflare immediately before Vector without an intervening readiness wait.
Suggested fix: Retain an equivalent Logflare readiness gate for the slim path, or establish and document that Vector's sink retry behavior makes it unnecessary.
| const env = { | ||
| ...legacyBuildStorageEnv({ | ||
| targetMigration: input.targetMigration, | ||
| anonKey: input.anonKey, | ||
| serviceRoleKey: input.serviceRoleKey, | ||
| jwtSecret: input.jwtSecret, | ||
| jwks: input.jwks, | ||
| dbHost: legacyServiceContainerName("db", input.projectId), | ||
| dbPassword: legacyStartInternalDbPassword(input.dbUrl), | ||
| fileSizeLimit: input.fileSizeLimit, | ||
| s3Region: input.s3Region, | ||
| s3AccessKeyId: input.s3AccessKeyId, | ||
| s3SecretAccessKey: input.s3SecretAccessKey, | ||
| imageTransformationEnabled: input.imageTransformationEnabled, | ||
| imgproxyHost: legacyServiceContainerName("imgproxy", input.projectId), | ||
| s3ProtocolEnabled: input.s3ProtocolEnabled, | ||
| vectorBucketsEnabled: input.vectorBucketsEnabled, | ||
| projectEnvValues: input.projectEnvValues, | ||
| }), |
There was a problem hiding this comment.
⚪ NIT · dead-code · source: claude
The change leaves unnecessary single-consumer abstractions and a no-op object spread from reverted slim variants.
Evidence: storage.service.ts:210-228 spreads the sole legacyBuildStorageEnv result into a new object without modifying it. postgres.service.ts:467-475 introduces an argv constant used only to join one exported string, and functions-docker.ts:149-156 delegates a fixed ["run","--rm"] prefix through a private helper with no other caller.
Suggested fix: Use legacyBuildStorageEnv directly and collapse the single-consumer wrappers unless a second variant is actually required.
Summary
Adds an opt-in env feature flag
SUPABASE_USE_SLIM_IMAGES("true"or"1", read from the ambient process env per call) that makes the legacy shell resolve its local-stack Docker images from the slimghcr.io/supabase/cli/<service>builds instead of the docker.io images pinned in the embedded Dockerfile manifest — so the slim images can be exercised through the stable CLI. With the flag unset, behavior is byte-identical.Commit 1 — flag plumbing
apps/cli/src/shared/services/slim-images.tsowns the gate and the pure translation: Dockerfile alias → stack-catalog service, keeping the Dockerfile's pinned version translated into the slim tag scheme viadockerImageForServicefrom a new lightweight@supabase/stack/versionsexport (supavisor:2.9.7→pooler:v2.9.7,logflare:1.50.4→analytics:v1.50.4, vector's docker.io-only-alpinesuffix stripped).dockerfileServiceImage(alias)is the single flag-translated choke point for default images;dockerfileServiceImageRawremains for identity contracts. Pinned-version paths (legacyResolvePinnedImage,serviceslistings,gen types) go through the catalog so tag-scheme differences normalize correctly.SUPABASE_INTERNAL_IMAGE_REGISTRY/ECR mirror rewrite entirely (single pull candidate) — they are published only atghcr.io/supabase/cli, so any rewrite would silently pull the non-slim mirror.Commit 2 — slim postgres container support (start + shadow)
The slim postgres image has a different runtime contract from docker.io (non-root uid 65532, its own
entry.sh, postgres under/opt/postgres, nodocker-entrypoint.sh). When the resolved ref is slim, the main-db and shadow-db specs switch to it:entry.shself-initializes on first boot (initdb, config, bundled supabase migrations) and passes container argv through to postgres, so[db.settings]travel as-c key=valueargv pairs.schema.sql+webhook.sql+_supabase.sql) and the deterministic pgsodium root key are staged via the existing secret-filedocker cp; the image's bundledmigrate.shexecutes/etc/postgresql.schema.sqlas its first-boot postinit — the same hook the docker.io image runs — giving exactly-once semantics across container restarts with no CLI-side marker.PGSODIUM_KEY_FILEpoints the image's getkey script at the CLI's root key.start --from-backuprefuses a slim resolved ref with a typed, classified error until the restore flow learns the slim contract (a registry override landing back on docker.io still restores).db start: healthy container, settings applied via argv, all three schema templates present exactly once across restarts, root key loaded, shadow spec exercised by hand.Deliberately unchanged even with the flag on
differ/migra/pgprovejob images (no slim builds)deno_version = 1edge-runtime image (supabase/edge-runtime:v1.68.4)status/--excludeshort names andservicesimage overridesReviewer notes
supabase/.envare not honored. SIDE_EFFECTS.md rows were added for every command whose image resolution the flag reaches.create extension pg_netin shadow flows) is fixed by the17.6.1.166slim build; the Dockerfile postgres pin is bumped to it (docker.io publishes the same tag), verified live with a slimdb diffend to end. The bundle demotespostgreson both image families, so the slim-only CLI postinit re-promotion was dropped (see the parity round below).Dogfooding round — full
supabase starton slim imagesmigrate./tmp(read-only rootfs) and slim storage mounts its volume at/home/nonroot.Dogfooding round — config-recipe parity and full service matrix
Ran the slim-services shared-config-recipe postgres build (slim-services#264) plus every other slim service through the full CLI flow matrix (
start/status/restart, bothdb diffengines,db dump, declarative sync,db reset,migration squash, seed buckets, negative guards), then functionally exercised each service: REST, GraphQL, auth + Mailpit SMTP, storage upload/download/TUS, imgproxy render, vector buckets, edge function invoke, realtime broadcast + postgres_changes, pooler connections, Studio (Advisors, Table Editor, Logs), and the vector → analytics logs pipeline. Fixes that came out of it:ALTER ROLE postgres WITH SUPERUSER; CREATE EXTENSION vector). Its premise was wrong: docker.io also demotespostgres(verifiedrolsuper = fon a fresh flag-off17.6.1.166stack), and storage-api createsvectoritself in its ownstorage_vectorsdatabase via the supautils allowlist. A same-release parity diff between slim and docker.io stacks (pg_settings/pg_roles/pg_extensionassupabase_admin) now shows empty roles and extensions diffs, and the settings diff reduces entirely to the slim bundle's documented local-dev profile, layout paths, andshared_buffers-derived values.curl, which the distroless slim supavisor image doesn't ship, so[db.pooler] enabled = truefailedstartoutright. It now probes with the image's/bin/busybox wget(the realtime/analytics pattern).max_connections=50— which saturated the database and starved realtime. The CLI now setsDB_POOL_SIZE=5.IMAGE_TRANSFORMATION_ENABLED=falseas an image ENV default and storage-api prefers that key over theENABLE_IMAGE_TRANSFORMATIONthe CLI set. The CLI now sets both keys from config./home/nonroot0700 → 0711) that blocked imgproxy (uid 999) from reading uploads. Verified on a fresh stack: upload, public download, render, vector buckets, and TUS create all pass with no manual intervention. Full methodology and results: feat(cli): add SUPABASE_USE_SLIM_IMAGES flag for slim ghcr images #6329 (comment)Linked issue
Closes #
open-for-contributionlabel (or I'm a Supabase maintainer).Checklist
fix(cli): …).pnpm check:allandpnpm testpass for the workspace(s) I touched.🤖 Generated with Claude Code