Hetzner + Firecracker — sandbox operations

How we run the sandbox orchestrator on dedicated hardware, how gograb-dev interacts with it, and where to look in-repo for nginx/build details. Firecracker stays on Hetzner; not on Railway.

Hetzner + Firecracker — operations

What runs where

Piece Where
Firecracker VMs + sandbox-orchestrator Dedicated server (e.g. Hetzner AX), bare metal
TLS / reverse proxy Nginx on the same host (see deployment/sandbox-orchestrator/nginx.conf)
Observe servers, metrics, destroy sandbox gograb-dev → /infrastructure/hetzner (calls backend-ai-api)

Production sandboxes require KVM / TAP / iptables on that host. Do not expect a generic container host to replace it.

Dual-server fleet (AX102-U vs AX42-U)

We run two dedicated hosts (see SANDBOX_SERVERS_JSON + SELF_HOSTED_INFRA_JSON in .env.local / Railway). DNS is not interchangeable — each hostname points at one machine.

Concern Prefer ax102-01.gograb.space (AX102-U) Prefer ax42-01.gograb.space (AX42-U)
Git (PLATFORM_GIT_SERVER_URL, clone URLs, /git/*) Yes — repos live here since S260 No
Primary orchestrator URL (SANDBOX_SERVER_URL fallback when not using fleet JSON) Yes No
Customer Firecracker VMs (customer-sandbox role; default Vitto / standard workspace) Yes (priority 1 in JSON) Overflow when AX102 at capacity
Executive / persistent-VM workloads (executive-sandbox role; workloadType: executive in placement router; Computer Use persistentVm; Vitto executive full-stack workspace) Only if AX42 full and policy allows overflow Preferred first (lower priority in SANDBOX_SERVERS_JSON)
Dashboard visibility Row from SANDBOX_SERVER_URL + key Second row from SELF_HOSTED_INFRA_JSON (must be distinct healthUrl from AX102)

Git HTTP vs SANDBOX_SERVERS_JSON (authoritative)

Code paths: SandboxPlacementRouter (libs/libs-api/sandbox-orchestration) filters by role + priority + capacity. backend-ai-api ComputerUseLoop passes workloadType from goal.persistentVm. Vitto VmSessionManager passes workloadType: 'executive' for executive full-stack workspace cold boots so placement matches ai-api behavior.

Operators: deploy orchestrator to both hosts when you change sandbox-orchestrator; use yarn sandbox:deploy:all or per-host SANDBOX_ORCHESTRATOR_SSH. Snippets: apps/gograb-dev/.../hetzner-deploy-playbook.ts.

How gograb-dev “talks” to your infrastructure

  1. backend-ai-api reads SANDBOX_SERVER_URL (public origin of the primary orchestrator, e.g. https://ax102-01.gograb.space) and optionally SANDBOX_SERVERS_JSON for multi-host aggregation.
  2. With SANDBOX_API_KEY set on backend-ai-api (same value as on the orchestrator), the API can call GET /sandboxes/meta/dashboard-summary so this UI shows load, VMs, active sandboxes, Node + git identity (from the orchestrator), and can destroy a sandbox via the orchestrator.

No Hetzner Cloud token is required for that path. Optional HETZNER_API_TOKEN only adds Hetzner Cloud dedicated servers to the same table and enables cloud power actions.

Orchestrator build identity (git / Node)

UI: stale snapshot banner

If backend-ai-api’s list fetchedAt is older than ~2.5 minutes, gograb-dev shows an amber banner. That measures aggregation time on ai-api, not orchestrator clock skew (clocks >1 min ahead suppress the banner). Tune the interval in code if your refresh cadence changes.

Deploy script (recommended)

From the monorepo:

# On the Hetzner box (already SSH’d in, repo at ~/gspace1 or set SANDBOX_REMOTE_REPO)
yarn sandbox:deploy:host

# From your laptop (SSH key access to root@host; optional SANDBOX_REMOTE_REPO=/root/gspace1)
SANDBOX_ORCHESTRATOR_SSH=root@YOUR_HOST yarn sandbox:deploy:host -- remote
# or
yarn sandbox:deploy:host -- remote root@YOUR_HOST

Full header comment: deployment/sandbox-orchestrator/deploy-orchestrator-host.sh

Does this fix dashboard-summary 404s?
Yes, when the cause is an old orchestrator binary (missing /sandboxes/meta/dashboard-summary) and you deploy current code to the same origin as SANDBOX_SERVER_URL, then restart.
No, if SANDBOX_SERVER_URL is wrong, nginx does not proxy /sandboxes/ to Node, or the failure is something else.

Day-to-day operator flow

  1. Monitor — Open Dedicated infrastructure in gograb-dev; use Refresh or wait for the auto refresh.
  2. Rebuild orchestrator after code changes — use yarn sandbox:deploy:host (on host or remote via SSH), or the Operations cheatsheet on that page for manual copy/paste.
  3. Verify — From your laptop, curl /health and /sandboxes/meta/dashboard-summary with X-API-Key (snippet on the same page).
  4. Nginx — After editing deployment/sandbox-orchestrator/nginx.conf on the server, nginx -t and reload.

If dashboard metrics show 404 hints

See §3 in sandbox-orchestrator optional Railway & dashboard troubleshooting — the 404 pattern section applies to whatever host SANDBOX_SERVER_URL points at (almost always: stale orchestrator binary on Hetzner or wrong URL). That doc’s Railway sections are not part of your Firecracker path.

Related files

File Purpose
deployment/sandbox-orchestrator/nginx.conf Example reverse proxy for the orchestrator host
APIs/sandbox-orchestrator/ Nest app (orchestrator + dashboard-summary routes)
APIs/backend-ai-api/.../hetzner-infra.service.ts Aggregates self-hosted + optional Hetzner Cloud