Skip to content

Hetzner Two-Server Deployment

Production layout for Linux:

  • CCX23: public dashboard + PostgreSQL + report engine
  • AX42-U: private runner host + claude/codex + Docker labs
  • Cloudflare in front of the dashboard origin

Why this layout

  • The dashboard is a web application with a database and report generation.
  • The runner launches AI CLIs on the host and should not be exposed publicly.
  • Labs, Playwright, and pentest-tools belong on the dedicated runner server.

Repository changes required for this deployment

This repository now includes the minimum production changes for the two-server setup:

  • PostgreSQL driver installed in backend requirements
  • Production compose uses postgresql+psycopg://...
  • Production compose accepts RUNNER_URL
  • Production compose mounts engagements/ read-write
  • Demo seed users can be disabled in production with SEED_DEMO_USERS=false

Production server (CCX23)

Required files

  • dashboard/docker-compose.production.yml
  • dashboard/.env.production.example
  • dashboard/deploy/Caddyfile.example

Environment

Copy dashboard/.env.production.example to dashboard/.env and set:

  • DASHBOARD_URL=https://app.example.com
  • CLIENT_PORTAL_URL=https://app.example.com
  • RUNNER_URL=http://<private-or-firewalled-runner-ip>:8881
  • CORS_ORIGINS=https://app.example.com
  • SEED_DEMO_USERS=false
  • SEEDED_USERS_MUST_CHANGE_PASSWORD=true

Notes

  • Keep PostgreSQL internal only.
  • Expose only 80/443 publicly.
  • Put Cloudflare in Full (strict) mode.

Runner server (AX42-U)

Required components

  • claude CLI on host
  • codex CLI on host if used operationally
  • Docker
  • built pentest-tools image
  • Python venv for dashboard.runner.runner
  • systemd unit based on dashboard/deploy/bd-runner.service.example

Network exposure

  • expose 8881/tcp only to the production server IP or private network
  • do not publish the runner behind Cloudflare
  • do not expose 8881 to the public internet

Verification checklist

  • curl http://127.0.0.1:8880/api/v1/health succeeds on CCX23
  • curl http://127.0.0.1:8881/health succeeds on AX42-U
  • curl http://<runner-ip>:8881/health succeeds from CCX23
  • dashboard login works with the configured admin credentials
  • no demo non-admin accounts exist in production when SEED_DEMO_USERS=false
  • generated reports are written successfully
  • creating/updating engagements and findings works without filesystem permission errors