chore: harden docker-compose deployment (bind, memory limits, no-new-privileges)

- frontend publishes on ${FRONTEND_PUBLISH_ADDR:-127.0.0.1} not 0.0.0.0 (M4).
- mem_limit on backend(4g)/postgres(1g)/frontend(512m), .env-overridable (M3).
- security_opt: no-new-privileges on app + postgres services.
New knobs documented + allowlisted in the config meta-test; compose validates.
Deferred (need host verification): image pinning, backend healthcheck, cap_drop
on postgres/tor, Postgres backup sidecar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-13 22:10:29 +02:00
parent e6aefaa804
commit 70a6598924
3 changed files with 41 additions and 1 deletions

View File

@@ -427,3 +427,14 @@ VISION_MANAGER_DATABASE_URL=
# VISION_MEM_LOW_WATERMARK_PCT used% >= this → inhibit starts (keep HIGH - LOW >= ~10 so the band beats jitter). Default 80.
# VISION_MEM_YIELD_COOLDOWN Seconds after a pressure-stop during which restart is refused regardless of backlog. Default 300.
# VISION_MEM_POLL_INTERVAL Mem sub-poll cadence, seconds (<= VISION_POLL_INTERVAL); catches spikes between backlog ticks. Default 5.
# ---- Deployment resource bounds & exposure (docker-compose.yml) ----
# Host interface the frontend port publishes on. Default 127.0.0.1 so SvelteKit
# (plain HTTP) is reachable only by a host-local reverse proxy. Set 0.0.0.0 only
# if your TLS terminator runs on a different host.
FRONTEND_PUBLISH_ADDR=127.0.0.1
# Per-container memory ceilings (docker mem_limit). Generous defaults; tune to
# your host. The backend runs OCR + headless Chromium and is the heavy one.
BACKEND_MEM_LIMIT=4g
FRONTEND_MEM_LIMIT=512m
POSTGRES_MEM_LIMIT=1g

View File

@@ -1096,6 +1096,13 @@ mod tests {
"BACKEND_URL",
"BACKEND_PROXY_TIMEOUT_MS",
"VISION_MANAGER_DATABASE_URL",
// Compose-level deployment knobs (port publish interface + per-container
// memory ceilings) — consumed by docker-compose.yml itself, never read
// by the backend process, so they don't belong in its environment block.
"FRONTEND_PUBLISH_ADDR",
"BACKEND_MEM_LIMIT",
"FRONTEND_MEM_LIMIT",
"POSTGRES_MEM_LIMIT",
];
/// Keys whose compose RHS is intentionally NOT a `${KEY...}`

View File

@@ -23,6 +23,12 @@ services:
# takes the whole stack offline. Match the policy already set on
# tor / docker-socket-proxy / vision-manager.
restart: unless-stopped
# Bound Postgres so a runaway query/backlog can't consume all host RAM.
# Tune to your host via .env (should comfortably exceed shared_buffers +
# work_mem * max_connections).
mem_limit: ${POSTGRES_MEM_LIMIT:-1g}
security_opt:
- no-new-privileges:true
tor:
# SOCKS5 proxy for the crawler, plus a control port so the backend
@@ -197,6 +203,13 @@ services:
expose:
- "8080"
restart: unless-stopped
# Bound the heaviest consumer: the backend hosts the OCR/analysis worker and
# drives a headless Chromium crawler, either of which can balloon and
# OOM-kill the host (taking Postgres with it) with no limit. Tune to your
# host via .env — this default is a generous ceiling, not a target.
mem_limit: ${BACKEND_MEM_LIMIT:-4g}
security_opt:
- no-new-privileges:true
frontend:
build: ./frontend
@@ -213,8 +226,17 @@ services:
# to 300000 (5 min) in hooks.server.ts; raise/lower via .env.
BACKEND_PROXY_TIMEOUT_MS: ${BACKEND_PROXY_TIMEOUT_MS:-300000}
ports:
- "3000:3000"
# Bind to loopback by default so SvelteKit (plain HTTP; COOKIE_SECURE=true
# expects TLS in front) is reachable ONLY by a host-local reverse proxy,
# not cleartext over the LAN/internet. A host-external TLS terminator can
# set FRONTEND_PUBLISH_ADDR=0.0.0.0 (or a specific interface) in .env.
- "${FRONTEND_PUBLISH_ADDR:-127.0.0.1}:3000:3000"
restart: unless-stopped
# Cap runaway memory (SvelteKit is light, but bound it anyway) and forbid
# privilege escalation. Tune the limit to your host via .env.
mem_limit: ${FRONTEND_MEM_LIMIT:-512m}
security_opt:
- no-new-privileges:true
# ----- Vision autoscaling (profile: ai) -----------------------------------
# Two extra containers that idle-stop the mangalord-vision (llama.cpp)