Koder Runtime Strategy — kbox as the universal runtime
Status
draft — opened 2026-05-20 from the kbox 10-wave session. Owner ratifies before Phase 0 work begins.
Update 2026-05-31: Phase 1 shipped (system containers + cgroup-v2 delegation, #129#138). Axis C (QEMU) re-ratified by owner into a 3-layer split: a shared runtime substrate (C.0, Phase 3.5, #134) consumed by both kbox (C.1 microVM dispatch) and a thin appliance-VM front-end (C.2, `infranetvm`, #132) — so the QEMU boundary integrates without duplicating kbox's authtracing/lifecycle plumbing. Rationale in Axis C below.
Summary
Replace Docker, LXC, and (where feasible) QEMUKVM across the Koder Stack with `infranetkbox` (kbox) as the universal containerruntime substrate. Three substitution axes with different gaps + phasing per axis. Explicit boundary: QEMU stays for Windows builds, kernel testing, and arbitrary-distro chroots — kbox does not try to be a full hypervisor.
Motivation
Today the Stack runs four different runtimes for overlapping problems:
| Tech | Where | Pain |
|---|---|---|
| Docker | s.poc.vivver.com (4 containers), CI runners, dev |
One more dependency to keep current; supply-chain (Docker Inc) outside the Stack |
| LXC/Incus | s.khost1 (5 CIMMG LXCs + cert daemon + Koder VPN hub) |
Different mental model than Docker; per-host install + CLI culture; not aligned with Koder Stack tooling (no Jet integration, no Koder ID, no shared image layer cache) |
| QEMU/KVM | Win10 VM + kodix chroot + Koda toolchain VM (planned) | Necessary for the cases that need it; no Stack-side abstraction over libvirt |
| (cosign etc.) | tooling around runtime | — |
Convergence on one runtime — kbox — gives uniform multi-tenancy (#108), uniform tracing (#109+#122), uniform OAuth (#110), uniform signing (#114+#121), one set of dashboards, one runbook, one tooling install per host. This RFC says how that convergence happens without losing the cases QEMU genuinely covers.
Three substitution axes
Axis A — Docker → kbox
Gap: smallest. kbox v1.1.0 has OCI runtime + image builds + registry + compose + CRIU + farm. Waves 1-10 added the Stack-native pieces (TOML config, multi-tenancy, tracing, OAuth, signing, OTLP, SBOM, quotas). Pair #13 in self-hosted-pairs.md is at stable for single-node; the open gates are operational, not feature.
What's missing for production_proven: real deployment + soak + Hub publication + bench numbers. Code-complete.
Axis B — LXC → kbox
Gap: medium. LXC runs system containers (full OS userland + systemd + persistent multi-service). kbox today runs application containers (single-process tree). Translating Incus workloads (5 CIMMG LXCs) requires kbox to grow:
kbox run --system(PID 1 = systemd, runs forever, multi-process)kbox attach <id>(shell into running system, distinct fromexec <id> <cmd>)kbox profile create/apply(Incus-style reusable config template)kbox snapshot create/restore(disk-level snapshot, distinct from CRIU live-migration)- Stateful disk pool UX matching
lxc storage create
None of these are blocked technically — they're feature work not yet on the backlog.
Axis C — QEMU/KVM → kbox
Gap: largest, partially out-of-scope. Three workload classes use QEMU today:
- Windows guest (Flutter Windows build via
k.winWinRM) — kbox cannot run Windows. Hard boundary. - Kernel testing / kodix ISO chroot — needs separate kernel. Hard boundary unless kbox grows a microVM dispatch.
- Strong-isolation Linux workloads (Koda toolchain VM planned per
policies/test-host-isolation.kmd) — these CAN move to microVM if kbox grows the dispatch.
Decision: split this axis by workload contract, not by "container vs VM" (owner-ratified 2026-05-31).
The deciding lens is the workload's contract, not whether it's "a container" or "a VM":
- OCI run-to-lifecycle (image → boots → runs → exits): namespaced containers AND Firecracker microVMs share this exact contract — same
kbox run <image>semantics, only the isolation differs. → kbox backend (--runtime=namespace|microvm). - Stateful appliance VM (Windows guest, arbitrary-kernel test, cross-arch emulation): own disk image, BIOSUEFI, consoleRDPVNCWinRM, snapshot-as-disk. This does NOT fit
kbox run <oci-image>— it is closer to a persistent appliance than to a run-to-completion workload. → a distinct front-end, NOT a kbox backend.
This yields three layers (rejecting both "fold everything into kbox" — which makes kbox a god-object spanning two disjoint data models — and "standalone VM module that re-implements the plumbing" — which duplicates authtracinglifecycle, violating reuse-first.kmd):
- C.0 runtime substrate — the cross-cutting host-workload plumbing both fronts share. A
reuse-first.kmdwalk ofengines/sdk/go/(SDKGO-005, 2026-05-31) found this is mostly already shared SDK — there is no broad extraction to do:- Auth ✅ already
engines/sdk/go/auth(JWKS validator + middleware); kbox already consumes it (internal/server/oauth.gois a thin adapter). - Observability ✅ already
engines/sdk/go/obs(OTLP + spans + RED + structured logs, adopted by koder-notifysymbol-store). kbox still runs a bespoke `internaltracing/→ migrate kbox ontoobs` (BOX-140); not a new clone. - Multi-tenancy — the contract is
specs/multi-tenancy/; kbox'sinternal/server/tenant.gois small + container-specific → stays local. - Lifecycle verbs + host-instances registry — the only genuinely-new shared piece, but it has 1 consumer today (kbox); the VM module doesn't exist yet. Per
reuse-first.kmd(promote on the 2nd consumer), it is extracted with BOX-132, co-designed against its real 2nd consumer — not pre-emptively.
- Auth ✅ already
- C.1 microVM dispatch — kbox grows a Firecracker backend for OCI-shaped Linux workloads wanting kernel isolation. Same UX as
kbox run, just--runtime=microvm. kbox consumes C.0. Covers the strong-isolation Linux class. - C.2 permanent appliance-VM scope — Windows + kernel testing + cross-arch stay in QEMU forever (intentional boundary, not a gap). A thin
infra/net/vm/front-end wraps libvirtQEMU and consumes the same C.0 substrate — so it is NOT a copy of kbox's plumbing, just a different door for a different workload shape. Net effect: separate binaries only where the workload shape genuinely differs (neither program rots), with zero duplication of authtracing/audit and a single "what's running on this host" view spanning both containers and VMs.
Phasing
| Phase | Scope | Owner-side gate | Estimated duration |
|---|---|---|---|
| 0 — Docker production_proven | Replace Docker on s.poc.vivver.com (4 containers). Land bench numbers in perf-baseline.md. Publish to Hub. |
First external deployment + 7-day soak | ~1-2 months |
| 1 — System mode | Add kbox run --system + attach + profile + snapshot. Pilot: replace cert daemon LXC on s.khost1. |
Cert daemon (already stable per project_certs_deployed_skhost1) runs in kbox-system for 7 days without regression |
~2-3 months |
| 2 — LXC at scale | Migrate CIMMG LXCs (db, app, neogestor, tv-fila, esus) + Koder VPN hub. |
All 6 LXCs migrated; Incus dependency removable from s.khost1 (kept as fallback) |
~1-2 months |
| 3 — MicroVM dispatch | Add Firecracker backend to kbox. kbox run --runtime=microvm. Target: Koda toolchain VM per test-host-isolation.kmd. |
Toolchain VM runs in kbox/Firecracker without regression vs raw QEMU | ~3-4 months |
| 3.5 — Substrate audit (done) + obs adoption | SDKGO-005 walk: auth already SDK (adopted), tenancy stays local, lifecycleregistry deferred to Phase 4 (1 consumer today). Residual: kbox adopts `enginessdkgoobs, retiring bespoke internaltracing` (BOX-140). |
kbox on obs; OTLP E2E re-validated to Tempo; internal/tracing deleted |
~3-4 days (was over-scoped at ~3-4 weeks) |
| 4 — Appliance-VM boundary | Document permanent QEMU scope (Windows + kernel test + cross-arch). Thin infra/net/vm/ front-end over libvirtQEMU consuming the C.0 substrate (no re-implemented authtracing). |
Win10 VM + kodix ISO chroot still in QEMU but auth+tracing flow through the SAME substrate as kbox; one "what's running here" view spans containers+VMs | ~1 month |
Total elapsed: ~8-12 months, depending on parallelism with other Stack waves.
Per-phase implementation tickets
Opened against infra/net/kbox/backlog/pending/:
- #128 Phase 0 — Docker production_proven activation (deploy
s.poc.vivver.com, soak, bench, Hub publish) - #129 Phase 1 — System container mode (
--system+attach+profile+snapshot) - #130 Phase 2 — CIMMG + cert-daemon LXC migration
- #131 Phase 3 — Firecracker microVM dispatch backend
- SDKGO-005 (enginessdkgo) Phase 3.5 — DONE (audit): reuse-first walk found no broad substrate to extract (auth already shared, obs available, lifecycle/registry deferred). Spun off BOX-140.
- #140 (infranetkbox) — kbox adopts
engines/sdk/go/obs, retires bespokeinternal/tracing/ - #132 Phase 4 — thin
infra/net/vm/appliance-VM front-end; reusesengines/sdk/go/{auth,obs}; extracts the lifecycle/host-instances registry into the SDK as part of building this module (co-design with its 2nd consumer, per reuse-first)
Out of scope
- Windows guest in kbox. No. Stays in QEMU.
- Replacing Incus itself. Incus stays installed on
s.khost1as fallback during Phase 1-2 migration; removable only after Phase 2 completion. - Cross-architecture emulation (arm guest on amd host etc.). Edge case — handled by QEMU when needed.
- Live container migration across hosts. kbox has CRIU local-host migration already; cross-host depends on kdb cluster state (
kbox#115, blocked onkdb#414).
Open questions
Q1 — System-mode default for attach
LXC's attach enters the container as root with a fresh login shell; Docker's exec runs a new command in an existing namespace. For kbox attach, do we mimic LXC (fresh-shell semantics) or just alias to exec /bin/sh?
Recommendation: Mimic LXC (fresh login session, reads /etc/profile). Operators migrating from lxc shell expect this. Add kbox exec for the Docker-style behaviour as already shipped.
Q2 — Single Stack-wide runtime config or per-host?
Today kbox.toml is per-host. For multi-host Stack management (Koder Console fleet view), do we want one global runtime policy distributed via kdb, or stay per-host?
Recommendation: Stay per-host for Phases 0-2. Revisit when kbox#115 (cluster state) ships.
Q3 — Firecracker vs Cloud Hypervisor vs Kata
Three open-source microVM options. Trade-offs:
- Firecracker — minimal, AWS-backed, well-documented; Linux x86_64 + arm64 only
- Cloud Hypervisor — Intel-backed, similar surface, slightly heavier
- Kata Containers — full OCI-shim layer over Firecracker/CH; more abstraction, less control
Recommendation: Firecracker. Smallest surface area, longest production track record, simplest integration. Re-evaluate if a specific limitation bites (no GPU passthrough is the most-cited; if Koda toolchain testing needs GPU we'd revisit).
Q4 — Incus removal threshold
After Phase 2, when is it safe to apt remove incus from s.khost1? Risk: a future LXC need surfaces that kbox-system hasn't covered yet.
Recommendation: Keep Incus installed but inactive (no running containers) for 90 days post-migration. Hard removal after 90 days with no rollback request. Document the rollback procedure (re-create containers from snapshots) in infra/net/kbox/docs/migration-from-incus.kmd.
Acceptance criteria for this RFC
- Owner ratifies → Phase 0 work starts on
s.poc.vivver.com - 5 tracking tickets opened in
infra/net/kbox/backlog/pending/ registries/self-hosted-pairs.mdrow 13 references this RFC in the notes columnmeta/docs/stack/modules/box.mdgains a "Runtime strategy" section pointing at this RFC + the phasing
Cross-references
registries/self-hosted-pairs.mdrow 13 (Kbox) — current gate statepolicies/self-hosted-first.kmd— the gates this RFC closespolicies/test-host-isolation.kmd— Phase 3 / 4 boundary (test workloads in VMs)infrastructure/servers.md— list of hosts using each runtime todayinfra/data/kdb#414— blocks Phase 2.5 multi-host clustering (kept separate from this RFC)