17fb981f99
ROOT CAUSE: the Dockerfile copied src/poller/scripts/supabase but NOT content/. mediaEngine.js read content/stark-lines.json with an unguarded module-load readFileSync; ENOENT in the image threw at require time, and via app.js → routes/desk → deskService → mediaEngine that crashed the ENTIRE API at boot. The Coolify healthcheck rolled back to the last healthy image (4d2b27d), so every deploy since219167esilently served a 14-hour-old build — S11 live tracking, S6 API code, the settlement boot line, and SNAPSHOT_EXPECTED_INTERVAL were all merged but NOT running. FIX (one train): 1. Dockerfile COPYs content/ into the runner image. 2. mediaEngine: stark-lines.json is OPTIONAL (garnish, never load-bearing) — loadStark() try/catch → {} → posts render without the Stark kicker, never a crash. Belt AND suspenders with #1. 3. src/preflight.js (§A4): boot prints '[preflight] OK' or 'DEGRADED' naming exactly what content/env is missing — before the healthcheck can fail silently. Run first in server.js. 4. Full fragility sweep: mediaEngine was the ONLY unguarded module-load file read; coachSignals (config/coaches.json) was already lazy + try/catch + copied. No others. Verified: requiring app.js + deskService + mediaEngine with stark-lines.json ABSENT now boots clean (reproduced the exact prod failure). 2757 -> 2763 tests (tests/unit/bootResilience.test.js). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
76 lines
2.5 KiB
Docker
76 lines
2.5 KiB
Docker
# syntax=docker/dockerfile:1.6
|
|
#
|
|
# VYNDR Express backend (port 3001).
|
|
#
|
|
# Multi-stage build:
|
|
# 1. deps — install production deps with a clean lockfile
|
|
# 2. runner — copy src/, poller/, scripts/, node_modules and start
|
|
#
|
|
# The Next.js frontend ships in a separate image (web/Dockerfile). PM2
|
|
# pollers run INSIDE this image and are auto-started by
|
|
# scripts/docker-entrypoint.sh so a Coolify redeploy reseeds them on
|
|
# every container start.
|
|
#
|
|
# Build: docker build -t vyndr-api .
|
|
# Run: docker run -p 3001:3001 --env-file .env vyndr-api
|
|
|
|
# --- deps stage ---
|
|
FROM node:20-alpine AS deps
|
|
WORKDIR /app
|
|
|
|
# package-lock.json is the source of truth — npm ci reproduces it exactly.
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci --omit=dev --no-audit --no-fund
|
|
|
|
# --- runner stage ---
|
|
FROM node:20-alpine AS runner
|
|
WORKDIR /app
|
|
|
|
# curl is used by the /api/health smoke check (Coolify HEALTHCHECK).
|
|
RUN apk add --no-cache curl tini
|
|
|
|
# PM2 is installed globally so the entrypoint can call `pm2 start` to
|
|
# boot all three pollers (NBA / WNBA / MLB) alongside the Express API.
|
|
RUN npm install -g pm2@latest --no-audit --no-fund
|
|
|
|
ENV NODE_ENV=production \
|
|
PORT=3001 \
|
|
PM2_HOME=/app/.pm2
|
|
|
|
# Non-root user — the container should never run as uid 0 even if the
|
|
# host accidentally maps a privileged port.
|
|
RUN addgroup -S vyndr && adduser -S vyndr -G vyndr
|
|
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
COPY package.json package-lock.json ./
|
|
COPY src ./src
|
|
COPY poller ./poller
|
|
COPY scripts ./scripts
|
|
COPY supabase ./supabase
|
|
# Session 65 (P0) — content/ holds the Stark-line library + seed articles.
|
|
# It was NOT copied, so mediaEngine's module-load read threw ENOENT in the
|
|
# image and crashed the API at boot (the deploy silently rolled back for 14
|
|
# hours). The read is now hardened to optional too (belt AND suspenders),
|
|
# but the file belongs in the image.
|
|
COPY content ./content
|
|
|
|
# Persistent volume for JSONL training data (resolutions survive
|
|
# redeploys via the Coolify mount). PM2_HOME lives outside it so
|
|
# supervisor state is local to the container.
|
|
RUN mkdir -p /app/data/training /app/.pm2 \
|
|
&& chown -R vyndr:vyndr /app/data /app/.pm2 \
|
|
&& chmod +x /app/scripts/docker-entrypoint.sh
|
|
|
|
USER vyndr
|
|
|
|
EXPOSE 3001
|
|
|
|
# tini reaps zombies — important now that we spawn pm2 as a child of
|
|
# this entrypoint.
|
|
ENTRYPOINT ["/sbin/tini", "--"]
|
|
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
|
CMD curl -fsS http://127.0.0.1:3001/api/health || exit 1
|
|
|
|
CMD ["sh", "scripts/docker-entrypoint.sh"]
|