feat(M5): docker-compose full stack (caddy + node backend + sqlite)

docker-compose.yml: two profiles.
  - backend: backend (node+ws+better-sqlite3, /data volume) + frontend
    (Caddy static build, STORAGE=ws, same-origin proxy)
  - firebase: existing Dockerfile + nginx (upstream path, untouched)
  Run: docker compose --profile backend up --build. OrbStack local now,
  remote docker context later.

server/Dockerfile: node:18-alpine, workspaces (shared dep), rebuild
better-sqlite3 for musl, DB at /data/tracker.sqlite.

Dockerfile.ws: CRA build STORAGE=ws → caddy:2-alpine serves /srv.
No backend URL baked (same-origin).

Caddyfile: handle /api/* + handle /ws → backend:4001 (path preserved,
mutually-exclusive handles so try_files SPA fallback never shadows proxy).
handle { static try_files } last. HTTP basic auth block optional.

src/storage/ws.js: same-origin defaults. Empty baseUrl = relative fetch
(Caddy proxy). wsUrl derives from window.location (http→ws/https→wss).
Fallback localhost for bare npm start dev.

.dockerignore: add data/ scratch/ tmp/ (never bake into image). Keep
Caddyfile in context (frontend build COPYs it).

Smoke verified via OrbStack:
  - GET / → 200 (static SPA)
  - PUT/GET /api/doc roundtrip → JSON persists
  - WS /ws subscribe + change push → both work through proxy

Firebase profile: pre-existing Dockerfile requires .env.local (hardcoded
COPY on main, not changed here). User must create file. Not a regression.
This commit is contained in:
david raistrick
2026-07-01 14:39:47 -04:00
parent 5521a2f6c6
commit 7467a8d30f
6 changed files with 171 additions and 5 deletions
+36
View File
@@ -0,0 +1,36 @@
# Caddyfile — serve static frontend, proxy /api + /ws to backend
# handle blocks are mutually exclusive + ordered: API/WS first, static last.
# (try_files at site level would rewrite /api/* → /index.html before proxy.)
{
# admin off for docker
admin off
}
:80 {
encode gzip
# REST API → backend service (path preserved: /api/doc etc.)
handle /api/* {
reverse_proxy backend:4001 {
header_up Host {host}
}
}
# WebSocket upgrade → backend
handle /ws {
reverse_proxy backend:4001
}
# Everything else: static SPA with client-side routing fallback
handle {
root * /srv
try_files {path} /index.html
file_server
}
# HTTP basic auth (in-house only). Uncomment + set CADDY_BASIC_AUTH env.
# basic_auth {
# {$CADDY_BASIC_AUTH}
# }
}