Skip to content

Fix the full-Docker dev stack: no webapp source in the container, and no host.docker.internal on Linux #482

Closed

Description

@HMarzban

Summary

The README offers make up-dev as the full-Docker way to run docs.plus locally. Two compose problems break it. The webapp container has no webapp source. On Linux with Docker Engine, the REST and WebSocket containers cannot reach local Supabase. A third gap leaves push off on this path: no service gets a VAPID key.

  • Severity: Medium. The full-Docker alternative does not serve the app. The default make dev-local path works.
  • Area: docker-compose.dev.yml
  • Source: docs and README review of 2026-10-14. Five review agents each covered one docs area. A separate agent then checked each finding against the code and tried to refute it. Every item below survived that check. "Checked" lists the evidence. Item 3 was added on 2026-10-14 from the staff review of Fix the first-run docs: Supabase Cloud steps, full-Docker order, and local setup gaps #483.

Items

1. In the full-Docker alternative, the webapp container has no webapp source, so make up-dev cannot serve the app at http://localhost:3000.

  • Where: README.md:80
  • Severity: medium
  • Checked: Confirmed by reading the files. Nothing was run. README.md:80-117 offers make up-dev and lists webapp at http://localhost:3000. make -n up-dev runs docker compose -f docker-compose.dev.yml --env-file .env.development with no override file. The webapp service is in docker-compose.dev.yml:271-313. It builds target: build-extensions and mounts only ./packages:/app/packages plus the anonymous /app/node_modules. It runs bun run dev:ci (next dev --turbopack, apps/webapp/package.json:8) in /app/apps/webapp. In apps/webapp/docker/Dockerfile.bun, the build-extensions stage gets apps/ only from deps (line 94). Deps copies only apps/webapp/package.json (line 58). So the container has no webapp src/, pages, or next.config. Commit 006e7c9 shows the cause. Before the move, the webapp lived at packages/webapp, so the ./packages mount covered it. The commit changed working_dir to /app/apps/webapp but added no mount, and the old comment ('webapp with .next') is now stale. The admin-dashboard service mounts ./apps/admin-dashboard (line 361), so the gap is specific to the webapp. A reader who follows README §Alternative: full Docker gets no webapp on port 3000. The proposed fix needs one more line. A bare ./apps/webapp mount would also mount the host's apps/webapp/node_modules, which exists and links into the host's own Bun store. In the container, those links can point at missing or macOS-only packages. An anonymous volume for that path keeps the image's own copy.
  • Fix: In docker-compose.dev.yml, add these lines to the webapp volumes: list: - ./apps/webapp:/app/apps/webapp and - /app/apps/webapp/node_modules. The second line keeps the host's macOS node_modules out of the container. Fix the stale mount comment at the same time. Fix the first-run docs: Supabase Cloud steps, full-Docker order, and local setup gaps #483 owns the README §Alternative: full Docker text, so this issue changes no README line.

2. On Linux with Docker Engine, the REST and WebSocket containers cannot resolve host.docker.internal, so they cannot reach local Supabase.

  • Where: README.md:104
  • Severity: medium
  • Checked: README.md:104 says "The containers read SUPABASE_URL: http://host.docker.internal:54321 from the host". .env.example:70 sets SUPABASE_URL=http://host.docker.internal:54321. The Makefile up-dev target (Makefile:138) passes it in with --env-file .env.development. In docker-compose.dev.yml, rest-api (:92) and hocuspocus-server (:164) take SUPABASE_URL: ${SUPABASE_URL} and have no extra_hosts. Only webapp (:288-289) and admin-dashboard (:349-350) have 'host.docker.internal:host-gateway'. This shows the maintainers knew about the Linux gap but did not add the entry to the two backend services. Plain Docker Engine on Linux does not resolve host.docker.internal unless host-gateway is mapped. The backend creates its anon and service-role Supabase clients from this URL (apps/hocuspocus.server/src/lib/supabase.ts:50,63). It also uses the URL in api/middleware/adminAuth.ts, the health service and the MCP module. So on Linux, every backend call to Supabase fails while the containers still look healthy. The README says nothing about Linux limits. Its only platform note, README.md:35, advises OrbStack on macOS. This is not a settled decision. Medium is the right severity. The stack starts, but every backend path that needs Supabase fails, and the reader loses time finding out why. The best fix is the compose change, because it is two lines in each service and matches the existing webapp entry. A README caveat alone leaves the bug in place.
  • Fix: In docker-compose.dev.yml, add extra_hosts: ['host.docker.internal:host-gateway'] to the rest-api and hocuspocus-server services, the same as webapp (:288) and admin-dashboard (:349). This needs no README change.

3. The dev compose file passes no VAPID key to any service, so push cannot work on the make up-dev path.

  • Where: docker-compose.dev.yml, services rest-api, hocuspocus-worker and webapp
  • Severity: low
  • Checked: grep -n VAPID docker-compose.dev.yml prints nothing. The file has no env_file: (comment at docker-compose.dev.yml:94), so a key set in .env.development reaches a container only when its environment: list names it. Production names three: VAPID_PUBLIC_KEY on rest-api (docker-compose.prod.yml:212), all three VAPID_* keys on hocuspocus-worker (:435–:437), and NEXT_PUBLIC_VAPID_PUBLIC_KEY on webapp (:530). The webapp reads that key at apps/webapp/src/utils/push-notifications.ts:5. The worker sends the pushes (apps/hocuspocus.server/src/lib/push/sender.ts). So on this path the browser cannot subscribe and the worker cannot send, even when Fix the first-run docs: Supabase Cloud steps, full-Docker order, and local setup gaps #483 item 2 adds the key to the README.
  • Fix: In docker-compose.dev.yml, mirror production. Add VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} to rest-api. Add VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} and VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} to hocuspocus-worker. Copy production's VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:support@docs.plus} line exactly. An empty default would pass "", and the schema default (apps/hocuspocus.server/src/config/env.schema.ts:140) replaces only a missing value. Add NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${NEXT_PUBLIC_VAPID_PUBLIC_KEY:-} to the webapp environment: list. No build arg is needed, because the dev webapp runs next dev, which reads NEXT_PUBLIC_* values when it compiles. Push stays optional: with the keys empty, the stack starts as it does today.

Checked in the main session

  • apps/webapp/docker/Dockerfile.bun, stage build-extensions, copies /app/apps from deps. The deps stage holds only the three apps/*/package.json files (lines 58–60). The dev webapp service mounts only ./packages and runs bun run dev:ci (next dev --turbopack). So next dev has no apps/webapp/src to serve.
  • git log -S './apps/webapp' -- docker-compose.dev.yml finds no commit. The mount never existed, so adding it undoes no earlier decision.
  • target: build-extensions dates from cb1cad9, before the move. That stage never held webapp source; the old ./packages mount supplied it. Only the builder stage copies apps/webapp (Dockerfile.bun:194), and it runs a production build. So a target change is not a smaller fix.
  • hocuspocus-worker gets no SUPABASE_URL in this file, so it needs no extra_hosts line.

Acceptance criteria

  • Item 1 is fixed as its Fix says, or a comment here says why not.
  • Item 2 is fixed as its Fix says, or a comment here says why not.
  • Item 3 is fixed as its Fix says, or a comment here says why not.
  • On macOS, after make up-dev, http://localhost:3000 shows the home page.
  • On Linux with Docker Engine, rest-api and hocuspocus-server reach http://host.docker.internal:54321.

Verify

  1. docker compose -f docker-compose.dev.yml --env-file .env.development config shows the new mounts and extra_hosts.
  2. Run the README full-Docker steps on a fresh clone, then open http://localhost:3000. Until Fix the first-run docs: Supabase Cloud steps, full-Docker order, and local setup gaps #483 item 3 lands, run bun install first.
  3. On Linux: docker compose -f docker-compose.dev.yml exec rest-api getent hosts host.docker.internal prints an address.
  4. With the VAPID keys set in .env.development, docker compose -f docker-compose.dev.yml --env-file .env.development config shows them on rest-api, hocuspocus-worker and webapp. With them empty, make up-dev still starts.

Related


Generated by Claude Code

Activity

  1. HMarzban commented on Oct 15, 2026

    @HMarzban
    CollaboratorAuthor

    Related issues


    Cross-link review, 2026-10-15. Generated by Claude Code

  2. added this to the v2.2.0 milestone on Oct 18, 2026
  3. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    macOS config check, 2026-10-21

    On main at 0130027bf, docker compose -f docker-compose.dev.yml --env-file .env.development config renders with no error.

    • Verify 1 passes. webapp mounts apps/webapp and packages into /app, with its own node_modules and .next volumes. Three services carry host.docker.internal=host-gateway, and SUPABASE_URL points at http://host.docker.internal:54321.
    • Verify 4 passes for the config: the VAPID keys reach rest-api, hocuspocus-worker and webapp.

    Not run yet:

    • Verify 2, the full-Docker start on a fresh clone. make up-dev force-removes every container named docsy-, so it would remove a running make dev-local stack. Run it on a fresh clone, or with dev-local stopped.
    • Verify 3, the Linux check. It needs a Linux host with Docker Engine.
  4. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    Verify 2 passes on macOS, 2026-10-21

    A fresh clone of main at 0130027bf in an empty folder, following the README full-Docker steps. Local Supabase was already running on :54321, so the supabase start line was skipped.

    • bun install: 1741 packages in 8 s.
    • cp .env.example .env.development, then make up-dev: exit 0. All 7 containers are healthy: webapp, admin-dashboard, rest-api, hocuspocus server and worker, Postgres and Redis.
    • http://localhost:3000 shows the home page in a browser. Create New Document opens a pad with its editor and outline, so the webapp reaches rest-api and the WebSocket server.
    • rest-api resolves host.docker.internal, and a request from inside it to http://host.docker.internal:54321/auth/v1/health returns 200.
    • rest-api /health and worker /health return 200.

    Still open: Verify 3 and the Linux acceptance line, which need a Linux host with Docker Engine. Items 1 to 3 and the macOS line are done.

  5. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    Verify 3 passes on Linux Docker Engine, 2026-10-21

    Run on a Linux host with Docker Engine 29.1.3, in throwaway --rm containers on the default bridge network. No dev stack was started there, because that host serves production.

    Container getent hosts host.docker.internal TCP to a host port (sshd, 0.0.0.0:2269)
    with --add-host host.docker.internal:host-gateway 172.17.0.1 open
    without it no address —

    The second row is the original bug, and the first row is the fix. docker-compose.dev.yml puts the same host.docker.internal=host-gateway entry on rest-api and hocuspocus-server, and also on the worker, webapp and admin. Local Supabase binds 0.0.0.0:54321, so it is reachable through the gateway the same way.

    With Verify 2 on macOS (comment above), every acceptance line holds, so this issue closes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DevOpsbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions