Running the stack on built output stopped the idle CPU load, but the remaining findings from the deployment report were still open. The Dockerfile is now multi-stage. The prod target runs on node:24-slim as the "node" user instead of root and carries only its runtime dependencies; "npm prune --omit=dev" drops tsx, drizzle-kit and typescript. The migration scripts need drizzle-orm and better-sqlite3, both regular dependencies, so they keep working. The dev target keeps devDependencies for compose.dev.yaml. Both compose files get a mem_limit. The server stack also sets memswap_limit to the same value, so a leak lets the OOM killer replace the container instead of pushing the host into swap. The Express API is no longer published on the host: it has no authentication and is reached through the Next.js rewrite. Development ports are bound to the loopback interface. Services drop all capabilities and run with no-new-privileges. SIGTERM and SIGINT now close the HTTP server and the SQLite handle and exit, instead of only logging the signal and waiting for the container's SIGKILL ten seconds later. server.closeIdleConnections() releases the keep-alive sockets the Next.js server holds, which would otherwise keep server.close() pending for the whole grace period. The compose start commands exec into node, because otherwise sh stays the foreground process and the signal never reaches it. Delete scripts/docker-start.sh rather than fixing its unsupervised background API: both compose stacks set their own command, so the single-container entrypoint has no caller left. Also remove drizzle.config.js (a stray tsc artifact drizzle-kit never reads) and the pre-Next index.html and styles.css. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
2.9 KiB
YAML
101 lines
2.9 KiB
YAML
# Entwicklungs-Stack: Quellcode-Mounts, laufende Dev-Server, Hot Reload.
|
|
# docker compose -f compose.dev.yaml up --build
|
|
#
|
|
# Die Polling-Watcher unten sind fuer Bind-Mounts unter Windows und macOS
|
|
# noetig, weil inotify-Events die VM-Grenze nicht passieren. Sie kosten
|
|
# dauerhaft CPU; genau deshalb laufen im Server-Stack (compose.yaml) gar
|
|
# keine Watcher.
|
|
#
|
|
# Der Projektname muss sich von dem des Server-Stacks unterscheiden, damit
|
|
# "docker compose down" in diesem Verzeichnis niemals einen laufenden
|
|
# Server-Stack abraeumen kann.
|
|
name: leistungsbilanz-dev
|
|
|
|
x-service-defaults: &service-defaults
|
|
init: true
|
|
restart: unless-stopped
|
|
stop_grace_period: 20s
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "20m"
|
|
max-file: "10"
|
|
|
|
services:
|
|
api:
|
|
<<: *service-defaults
|
|
build:
|
|
context: .
|
|
target: dev
|
|
command:
|
|
- sh
|
|
- -c
|
|
- npm run db:migrate && npm run db:verify:circuit-schema && exec npm run dev:api
|
|
environment:
|
|
PORT: "3000"
|
|
CHOKIDAR_USEPOLLING: "true"
|
|
LOG_LEVEL: "${LOG_LEVEL:-debug}"
|
|
# Nur auf dem Loopback: nichts davon ist authentifiziert, die API darf
|
|
# nicht aus dem LAN erreichbar sein.
|
|
ports:
|
|
- "127.0.0.1:3000:3000"
|
|
# Kein memswap_limit wie im Server-Stack: beim Entwickeln ist Swap als
|
|
# Puffer erwuenscht, die Grenze soll nur den Host vor Dauerlast schuetzen.
|
|
mem_limit: 1g
|
|
volumes:
|
|
- ./src:/app/src
|
|
- ./scripts:/app/scripts
|
|
- ./data:/app/data
|
|
- ./drizzle.config.ts:/app/drizzle.config.ts:ro
|
|
- ./tsconfig.json:/app/tsconfig.json:ro
|
|
healthcheck:
|
|
test:
|
|
- CMD
|
|
- node
|
|
- -e
|
|
- fetch('http://127.0.0.1:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1))
|
|
interval: 30s
|
|
timeout: 3s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
web:
|
|
<<: *service-defaults
|
|
build:
|
|
context: .
|
|
target: dev
|
|
command:
|
|
- npm
|
|
- run
|
|
- dev:web
|
|
- --
|
|
- --hostname
|
|
- 0.0.0.0
|
|
environment:
|
|
API_INTERNAL_URL: http://api:3000
|
|
WATCHPACK_POLLING: "true"
|
|
NEXT_TELEMETRY_DISABLED: "1"
|
|
LOG_LEVEL: "${LOG_LEVEL:-debug}"
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
ports:
|
|
- "127.0.0.1:3001:3001"
|
|
# "next dev" haelt Kompilierzustand und waechst im Dauerbetrieb stetig;
|
|
# die Grenze macht daraus einen Container-Neustart statt Host-Swapping.
|
|
mem_limit: 2g
|
|
volumes:
|
|
- ./src:/app/src
|
|
- ./next.config.mjs:/app/next.config.mjs:ro
|
|
- ./tsconfig.json:/app/tsconfig.json:ro
|
|
- ./tsconfig.next.json:/app/tsconfig.next.json:ro
|
|
healthcheck:
|
|
test:
|
|
- CMD
|
|
- node
|
|
- -e
|
|
- fetch('http://127.0.0.1:3001/web-health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1))
|
|
interval: 30s
|
|
timeout: 3s
|
|
retries: 5
|
|
start_period: 20s
|