Skip to content

stack: realtime service cannot run (or be consumed) in docker mode #5788

Description

@martijnwalraven

Pinned at: develop @ 31d6fb97 · macOS arm64 (Darwin 25.5), Docker Desktop (server 29.5.3), Node 24.18.0, realtime image v2.112.3 (DEFAULT_VERSIONS), consuming @supabase/stack via createStack().

Enabling realtime in docker mode (createStack({ mode: "docker", realtime: { port } })) never reaches ready, and even a healthy realtime container can't be consumed by supabase-js through the stack. Three findings, plus one observation:

1. The realtime container can't reach postgres — start() hangs indefinitely.
The realtime ServiceDef's env (packages/stack/src/services/realtime.ts) doesn't set DB_IP_VERSION. On Docker Desktop for Mac, every connection attempt fails with:

Postgrex.Protocol failed to connect: ** (DBConnection.ConnectionError)
tcp connect (host.docker.internal:53976): network is unreachable - :enetunreach

— while getent ahosts host.docker.internal inside the same image resolves IPv4-only (192.168.65.254), and GoTrue/PostgREST connect fine over the identical wiring. The migration runner gives up after ~15s, the container exits, unless-stopped restart-loops it forever, and stack.start() waits with no diagnostic (the no-timeout behavior noted in #5787 makes this a silent hang).

Verified fix: re-running the identical container with only DB_IP_VERSION=ipv4 added makes migrations run instantly. This looks like the known realtime self-host IPv6-preference gotcha; the ServiceDef probably wants DB_IP_VERSION=ipv4 for the host.docker.internal path.

Worth noting the docker-mode e2e suite doesn't enable realtime, so this path appears never to have been exercised.

2. The ApiProxy can't carry realtime websockets.
The proxy routes /realtime/v1/* as plain HTTP (ApiProxy.ts) and neither it nor the Node platform layer (node.ts — bare createServer()) registers any upgrade handling, so supabase-js — which derives wss://…/realtime/v1/websocket from the project URL — can never connect through stack.url.

3. The direct service endpoint can't serve supabase-js either.
Bypassing the proxy fails independently three ways: the bare realtime server 404s /realtime/v1/websocket (it serves /socket/websocket; the /realtime/v1 prefix is proxy vocabulary), tenant resolution is Host-based (bare-IP → 401 {"message":"Tenant not found in database"}; Host: realtime-dev → 200), and the apikey must be a real JWT since the proxy's publishable-key→JWT transform is HTTP-only. A raw handshake at /socket/websocket with a tenant-bearing hostname and a signed anon JWT does return 101 — so websocket upgrade support in the proxy (path rewrite + tenant Host + the existing auth transform) would make supabase-js work end-to-end with no client changes.

4. Observation, not root-caused: with finding 1 worked around and a 101 handshake established, channel joins still fail with UnableToConnectToTenantDatabase / UnableToConnectToProject — the SEED_SELF_HOST tenant's own (encrypted-settings) connection pool appears not to honor the env-level IPv4 fix. Happy to provide the full repro.

Suggested resolutions: DB_IP_VERSION=ipv4 (or equivalent) in the realtime ServiceDef; websocket upgrade support in the ApiProxy; realtime coverage in the docker-mode e2e.

Metadata

Metadata

Assignees

No one assigned

    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