fix(docker): single container (caddy+node), ESM adapters fix blank page
Docker: moved all docker files to docker/ tree (was conflated with upstream Dockerfile at root + server/Dockerfile). Single container now: caddy (front, serves static + proxies /api /ws) + node backend (internal :4001). Node never exposed. entrypoint.sh runs both. Compose: one service. Blank page root cause: storage adapters had inconsistent module systems. firebase.js = ESM (export). ws.js + memory.js = CJS (module.exports). CRA prod build = ESM strict -> CJS runtime crash, blank root. Dev mode lenient, masked bug. First ws prod build (docker) = first exposure. Never dev/prod split intended; just inconsistency from M2 era. Fix: all adapters ESM. ws.js lazy-loads 'ws' pkg via dynamic import() (Node/jest only; browser uses global WebSocket). index.js static imports. server jest: added babel.config.js (preset-env, node target) to transform ESM for jest. Test: src/tests/StorageEsm.test.js — 4 tests grep all adapters for module.exports / require(). Regression guard catches CJS leak. Verified: docker page renders (root 4534 chars, UI visible). server 24 green, shared 90 green, FE ESM 4 green.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Ignore Git directory
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Ignore Node.js modules (they will be installed in the Docker image)
|
||||
node_modules
|
||||
**/node_modules
|
||||
|
||||
# Ignore build output (it will be generated in the Docker image)
|
||||
build
|
||||
dist
|
||||
|
||||
# Ignore Docker files themselves (Caddyfile MUST stay in context for frontend build)
|
||||
Dockerfile
|
||||
Dockerfile.ws
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
|
||||
# Ignore any local environment files if you have them
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Ignore IDE and OS-specific files
|
||||
.vscode/
|
||||
.idea
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
|
||||
# Ignore local sqlite data + scratch diagnostics (never bake into image)
|
||||
data/
|
||||
scratch/
|
||||
tmp/
|
||||
@@ -0,0 +1,18 @@
|
||||
# Caddyfile — single-container (caddy + node)
|
||||
# Caddy serves built frontend, proxies /api + /ws to node backend on :4001.
|
||||
# Node never exposed directly; only caddy on :80.
|
||||
|
||||
:80 {
|
||||
handle /api/* {
|
||||
reverse_proxy 127.0.0.1:4001
|
||||
}
|
||||
handle /ws {
|
||||
reverse_proxy 127.0.0.1:4001
|
||||
}
|
||||
# catch-all: static frontend (SPA fallback)
|
||||
handle {
|
||||
root * /srv
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# docker/Dockerfile — single container: caddy (front) + node (back).
|
||||
# Build context = repo root.
|
||||
# ---- build stage: frontend + install backend deps ----
|
||||
FROM node:18-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
COPY shared/package.json ./shared/
|
||||
COPY server/package.json ./server/
|
||||
RUN npm install --include-workspace-root
|
||||
|
||||
COPY shared/ ./shared/
|
||||
COPY server/ ./server/
|
||||
COPY src/ ./src/
|
||||
COPY public/ ./public/
|
||||
|
||||
# better-sqlite3 native build (alpine musl)
|
||||
RUN cd server && npm rebuild better-sqlite3
|
||||
|
||||
# build frontend (ws storage, same-origin via caddy)
|
||||
ARG REACT_APP_TRACKER_APP_ID=ttrpg-initiative-tracker-default
|
||||
ENV REACT_APP_STORAGE=ws
|
||||
ENV REACT_APP_TRACKER_APP_ID=$REACT_APP_TRACKER_APP_ID
|
||||
RUN NODE_OPTIONS=--openssl-legacy-provider npm run build
|
||||
|
||||
# prune backend dev deps for runtime
|
||||
RUN npm prune --omit=dev
|
||||
|
||||
# ---- runtime stage: caddy + node ----
|
||||
FROM node:18-alpine
|
||||
RUN apk add --no-cache caddy
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/shared/node_modules ./shared/node_modules
|
||||
COPY --from=build /app/server/node_modules ./server/node_modules
|
||||
COPY --from=build /app/package*.json ./
|
||||
COPY --from=build /app/shared/package.json ./shared/
|
||||
COPY --from=build /app/server/package.json ./server/
|
||||
COPY shared/ ./shared/
|
||||
COPY server/ ./server/
|
||||
# built frontend served by caddy
|
||||
COPY --from=build /app/build /srv
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=4001
|
||||
ENV DB_PATH=/data/tracker.sqlite
|
||||
|
||||
EXPOSE 80
|
||||
WORKDIR /app
|
||||
CMD ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,22 @@
|
||||
# docker/docker-compose.yml — single container: caddy (front) + node (back).
|
||||
# Usage (from repo root):
|
||||
# docker compose -f docker/docker-compose.yml up --build
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
args:
|
||||
- REACT_APP_TRACKER_APP_ID=${TRACKER_APP_ID:-ttrpg-initiative-tracker-default}
|
||||
image: ttrpg-app:local
|
||||
ports:
|
||||
- "${PORT:-8080}:80"
|
||||
volumes:
|
||||
- app-data:/data
|
||||
environment:
|
||||
- DB_PATH=/data/tracker.sqlite
|
||||
- PORT=4001
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
app-data:
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# docker/entrypoint.sh — run node backend + caddy proxy in one container.
|
||||
# Caddy foreground (PID 1, handles signals). Node background.
|
||||
set -e
|
||||
|
||||
# node backend (internal :4001)
|
||||
cd /app/server
|
||||
node index.js &
|
||||
NODE_PID=$!
|
||||
|
||||
# caddy proxy (foreground, :80)
|
||||
exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
Reference in New Issue
Block a user