Server-class boxes: staging template for control-plane-managed staging VMs
#68
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#68
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The incubator fleet has a
staging-server(M900) hostingstaging-box— an Incus VM registered in Coolify as a server and used to deploy staging resources. Today that VM is minted by hand: rawincus launchplus a cloud-init profile (incus/staging-vm.yaml) kept in the private infra repo, entirely outside box. This issue makes server-class staging boxes a first-class box use case.Design: box mints, rig converges, cast registers
Layering (companion rig issue linked below):
stagingtemplate (this issue).box shell→sudo rig bootstrap workload), hardens sshd and joins the tailnet astag:server. Box never sees theTS_AUTHKEY; rig holds it in process memory per its own contract.Why box's isolation stack needs no changes: the guest joins the tailnet itself, so the control plane's SSH and all app traffic arrive through a tunnel the guest established outbound. The host-side stack (boxnet ACL, port isolation, box-firewall, loopback-only
expose) only ever sees allowed outbound UDP. Isolation becomes a feature: the staging box still can't reach the LAN, the host, or sibling boxes — it is reachable only over the tailnet, which is exactly the staging security posture (tailnet-only, never LAN/public).What changes
templates/staging/(data-only):box.env: Debian 13 cloud image, userops, build-sized resources (the control plane builds on the target):BOX_CPU=4,BOX_MEMORY=8GiB,BOX_DISK=100GiB, plus the two new keys below.user-data.yaml: docker + curl/ca-certificates + preinstall rig (curl … rig/main/install.sh | bash). No tailscale, no sshd, no credentials — that is rig's job at bootstrap time.bin/box:BOX_REQUIRE_VM=1— the template refuses the container-mode fallback inpick_mode(); staging boxes must be VMs (the trust boundary, and docker-in-guest).BOX_AUTOSTART=1— setsboot.autostart=trueon the instance so staging boxes come back deterministically after a host reboot.docs/box-design.md): clarify that "no inbound route to a box" means no inbound via the host's network position — a guest can deliberately join an overlay (tailnet) and invite management in; the server-class template is the sanctioned use of that. Document the snapshot-before-join rule: clone from a pre-rig bootstrapsnapshot; post-join clones would duplicate tailnet identity.Non-goals
expose.Acceptance
box new --name staging-2 --template stagingon a KVM host mints a VM with docker + rig preinstalled; on a non-KVM host it refuses with a clear error (no silent container fallback).box shell→sudo rig bootstrap workloadsucceeds interactively.boot.autostartis set on staging boxes and absent on others.Companion rig issue: https://github.com/heavy-duty/rig/issues/22
Closing as superseded by the thin-template layering decided 2026-07-18 (#81, heavy-duty/rig#31): tenant content (docker, joins, hardening) moves to rig bootstrap roles — the staging posture becomes
rig bootstrap stagingper rig#31 — and box templates shrink to creds-free seeds. This issue's implementation PR (#69) was closed on the same decision; its mint-time survivors already landed via #82 (BOX_REQUIRE_VM / BOX_AUTOSTART keys + the dynamic template test suite, merged). The staging template itself returns as a thin seed under #81 once rig#31's role exists.