forked from jappel/leistungsbilanz-ts
Add production compose stack and stop idle load in containers
The development stack was running permanently on a server: polling file watchers, a healthcheck that rendered a full page every five seconds and no memory limit grew next dev to 10 GB and pushed the host into swap. - add compose.prod.yaml running compiled output in separate api/web services - make the Dockerfile multi-stage with dev and prod targets, prune devDependencies and run the runtime image as node instead of root - bake API_INTERNAL_URL at build time; next start ignores it at runtime because rewrite destinations are resolved into routes-manifest.json - drop CHOKIDAR_USEPOLLING and WATCHPACK_POLLING - probe /health instead of /, which redirects to /projects and made every healthcheck render the project list - give every service a memory limit and forbid swap in production - rename the development compose project to leistungsbilanz-dev so its down command cannot target the production stack - bind development ports to localhost - close the http server and the SQLite handle on SIGTERM/SIGINT - match probe user agents in the navigation log filter; Node's fetch sends one, so the previous check never matched - exit docker-start.sh when either supervised process dies - remove drizzle.config.js, a compiled copy drizzle-kit never reads, and the pre-Next index.html/styles.css leftovers Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a17e2e3f4b
commit
01fa527b9c
14 changed files with 424 additions and 118 deletions
70
compose.yaml
Normal file → Executable file
70
compose.yaml
Normal file → Executable file
|
|
@ -1,26 +1,39 @@
|
|||
name: leistungsbilanz
|
||||
# Local development only. Runs watch-mode servers against bind-mounted sources.
|
||||
# For a deployment use compose.prod.yaml.
|
||||
#
|
||||
# The project name must stay distinct from the production project so that
|
||||
# `docker compose down` in this directory can never tear down a running
|
||||
# production stack.
|
||||
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 && npm run dev:api
|
||||
environment:
|
||||
PORT: "3000"
|
||||
CHOKIDAR_USEPOLLING: "true"
|
||||
LOG_LEVEL: "${LOG_LEVEL:-info}"
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "20m"
|
||||
max-file: "10"
|
||||
# Development ports stay on the loopback interface; nothing here is
|
||||
# authenticated and the API must not be reachable from the LAN.
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "127.0.0.1:3000:3000"
|
||||
mem_limit: 1g
|
||||
volumes:
|
||||
- ./src:/app/src
|
||||
- ./scripts:/app/scripts
|
||||
|
|
@ -32,15 +45,17 @@ services:
|
|||
- CMD
|
||||
- node
|
||||
- -e
|
||||
- fetch('http://localhost:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1))
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 20s
|
||||
- fetch('http://127.0.0.1:3000/health').then(response=>process.exit(response.ok?0:1)).catch(()=>process.exit(1))
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
web:
|
||||
<<: *service-defaults
|
||||
build:
|
||||
context: .
|
||||
target: dev
|
||||
command:
|
||||
- npm
|
||||
- run
|
||||
|
|
@ -50,33 +65,30 @@ services:
|
|||
- 0.0.0.0
|
||||
environment:
|
||||
API_INTERNAL_URL: http://api:3000
|
||||
WATCHPACK_POLLING: "true"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
LOG_LEVEL: "${LOG_LEVEL:-info}"
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "20m"
|
||||
max-file: "10"
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3001:3001"
|
||||
- "127.0.0.1:3001:3001"
|
||||
# next dev grows steadily under long-running use; the limit turns that into
|
||||
# a container restart instead of host-wide 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:
|
||||
# Must stay on /health: "/" redirects to /projects and Node's fetch
|
||||
# follows redirects, which turned every probe into a full page render.
|
||||
test:
|
||||
- CMD
|
||||
- node
|
||||
- -e
|
||||
- fetch('http://localhost:3001/').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1))
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 20s
|
||||
- fetch('http://127.0.0.1:3001/health').then(response=>process.exit(response.ok?0:1)).catch(()=>process.exit(1))
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue