Docker bypasses ufw and exposes 0.0.0.0-bound ports directly via
iptables DNAT rules, even when ufw default policy is deny. Bind
every service port to 127.0.0.1 so only nginx (and SSH tunnels for
wizard) can reach them from outside.
Runs the monitor container with network_mode: host and /:/host:ro
bind mount so gopsutil reads host-level telemetry. Backend URL,
identifier, and API key are sourced from .env via variable
interpolation (no secrets in compose).
The abstract-wizard image runs as nonroot (65532), but the named
volume is created with root:root ownership, causing PUT writes to
harborforge.json to return 500. Add a busybox wizard_init service
that chowns /config to 65532:65532 before wizard starts.
- Add hf_db_init sidecar that ensures the HarborForge database exists on
every `compose up` (idempotent CREATE DATABASE IF NOT EXISTS), so the
shared MySQL instance can host both hangmanlab and harborforge schemas
without touching existing data.
- Wire hf_backend's DATABASE_URL directly from compose env vars and gate
it on hf_db_init completing successfully.
- Add a mysqladmin-ping healthcheck on mysql so dependents can wait on
service_healthy.
- Drop dead Vite runtime envs from hf_frontend (build-time only) and
make wizard CORS_ORIGINS configurable via HF_FRONTEND_HOST.
- Seed .env.example with all variables the stack reads.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- hf_backend: zhi/harborforge-backend:multi-stage on port 8000
- hf_frontend: zhi/harborforge-frontend:latest on port 3000
- wizard: nav/abstract-wizard:latest on port 8082
- All services share app-network bridge