Two bugs that surfaced on first real deploy:
1. SPA was COPYed to /app/apps/api/public but server.ts looks at
/app/apps/web/dist (relative to apps/api/dist via fileURLToPath).
Static handler returned 404 for /. Move the COPY to match.
2. The aliased external network (`umbrel_main` -> name:
umbrel_main_network) wasn't attaching in Portainer Stacks → DNS
lookup of datum_datum_1 fails with ENOTFOUND. Use the network's
real name directly as the alias to avoid any Portainer indirection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two production-blocker bugs from the first deploy:
1. Static SPA never served — Dockerfile copied apps/web/dist into
apps/api/public, but server.ts default static dir resolves to
apps/web/dist. Mismatch meant every route fell through to Express'
bare 404 ("Cannot GET /"). Aligning Dockerfile to the default path.
2. DNS for the Datum container name failed (getaddrinfo ENOTFOUND
datum_datum_1) — gashboard's Docker DNS doesn't reliably alias
external-network container names across compose stacks. Switch the
default DATUM_URL to the container's known IP on umbrel_main_network
(10.21.0.11, captured during earlier diagnostics). If the IP changes
the user can override DATUM_URL in env. If gashboard isn't actually
joined to umbrel_main_network, the next failure will be a much more
diagnostic ECONNREFUSED instead of an opaque ENOTFOUND.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Node's fetch wraps the underlying network error in `.cause`; the bare
`err.message` is just "fetch failed" which tells us nothing about
DNS vs connection refused vs network unreachable. Add formatErr() that
walks .cause and includes its .code, plus the url being attempted, so
logs distinguish between the actual failure modes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-facing port unified at 1337 — vibes-aligned and easier to remember
than 8080/8420. Updates: api default PORT, .env.example, docker-compose
mapping (1337:1337), healthcheck target, Dockerfile EXPOSE, Vite dev
proxy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the original brief: 1337 vibe. Internal port stays 8080 (matches
PORT env default and Express trust-proxy assumptions); just remaps the
host port from the 8420 placeholder to 1337.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Defensive — keeps node_modules/dist/.vite/.env out of the build context
when running `docker build` locally. Portainer's Stacks→Repository flow
clones from git (which already excludes these via .gitignore), so this
doesn't change Portainer behavior, just hardens local-docker-build paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The api and web tsconfig.json files both extend ../../tsconfig.base.json,
but the previous Dockerfile didn't COPY that file into /app. `tsc` then
fails to resolve the extends and exits with code 2 during the
`pnpm --filter @gashboard/api build` stage. Adding the file to the
COPY in the deps stage — both build stages inherit FROM deps, so this
fixes both api and web builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>