Skip to content

stack: mode: "native" silently falls back to Docker when binary resolution fails #5787

Description

@martijnwalraven

Environment

  • supabase/cli develop @ 31d6fb97, consuming @supabase/stack from source (Node 24.18.0)
  • macOS arm64 (Darwin 25.5)

Expected

Per the package README ("Native binaries with Docker fallback — uses native services when available and falls back to Docker images automatically" describes auto; "native" requires native binaries) and the next-shell CLI's own help text (--mode native to require native-compatible services, apps/cli/src/next/commands/start/start.command.ts), a failed native binary resolution in mode: "native" should be an error.

Actual

The binary→Docker fallback applies in every non-docker mode, including "native". resolveService (packages/stack/src/resolve.ts) is mode-blind — it maps BinaryNotFoundError and DownloadError (any network failure) to a Docker image resolution — and mode is only consulted one level up in StackPreparation.prepare, which special-cases mode === "docker" and the docker-only service set, then routes everything else through the same fallback path.

Observed live: with the default postgres version, createStack({ mode: "native", ... }) on macOS arm64 quietly started postgres as docker run --rm --name supabase-postgres-… public.ecr.aws/supabase/postgres:17.6.1.142 while postgrest/auth resolved native. The trigger: DEFAULT_VERSIONS pins postgres 17.6.1.142 (synced from the Go CLI's Dockerfile), but the newest native release line on supabase/postgres is v17.6.1.141-cli — so the out-of-box native resolution 404s (DownloadError) and silently flips substrate. The Dockerfile-synced default outrunning the -cli release line is a second, related issue: the out-of-box native path can 404 whenever the Docker tag advances first.

As far as we can tell this fallback-in-native isn't intentional: no test asserts the binary→Docker fallback (the fallback tests in prefetch.unit.test.ts cover the ECR→Docker Hub→GHCR registry chain), and it contradicts both documented contracts above.

Why this matters more than a wrong substrate

A silent substrate flip masks real defects for every consumer. Two we hit in one session:

  1. supabase/auth's darwin-arm64 release assets have contained Linux ELF binaries since rc2.189.0-rc.1 (fix: fix: build darwin-arm64 release binaries with GOOS=darwin auth#2618). Because the asset resolves fine and only fails at spawn (ENOEXEC), even auto mode's fallback never fires — the service crash-loops under restart: "unless-stopped", and since the ready path has no timeout, stack.start() hangs indefinitely with no diagnostic. Native auth on macOS has never worked against any auth release ≥ v2.189.0, and nothing in the pipeline could tell anyone.
  2. Heads-up for when native auth works again: the -cli postgres tarball's bundled platform migrations define auth.uid() against the PostgREST-v9 per-claim GUC (request.jwt.claim.sub), while the PostgREST the stack runs (v14) sets only the JSON request.jwt.claims — so auth.uid() returns NULL and owner-scoped RLS silently matches nothing on the native path. The Docker images carry the modern coalescing definition, so docker mode is unaffected. (Likely owned by supabase/postgres's -cli packaging; mentioned here because the native path is what surfaces it.)

Suggested fix

Thread the mode (or an allowDockerFallback flag) into resolution so that in mode: "native", BinaryNotFoundError/DownloadError propagate as errors instead of resolving to Docker — matching the documented contract. Happy to send a PR if that direction sounds right.

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions