nexcage

ADR-001: Container Runtime Selection

Status

Revised 2026-09-27. This supersedes the decision of 2024-12-01, which chose crun as the primary container runtime with runc as an automatic fallback. That decision was never what shipped; the review that produced this revision is #85.

Context

nexcage is one binary on a Proxmox VE host, called from two directions (OVERVIEW.md): a person or a script manages LXC containers with it, and a container engine — podman, containerd, CRI-O, a kubelet — drives it with the OCI runtime-spec command line. The 2024 decision was taken before the first of those existed as the centre of the project, and it assumed nexcage would call a runtime binary. Two questions need an answer that the code agrees with: which backend a container goes to by default, and how that is decided for a container whose caller never says.

Decision

  1. The Proxmox LXC backend is the default. It turns commands into pct and Proxmox API calls, across every node of the cluster, and is what a plain build contains. Nothing selects it; everything not routed elsewhere lands there.

  2. The crun backend is libcrun, linked in. For an OCI bundle — what an engine hands over — nexcage does the container work through the vendored libcrun (deps/crun, a fork), not by executing a crun binary. It is an opt-in build (-Denable-backend-crun=true) and ships as the -crun release binary. It is the backend an engine’s host is configured for.

  3. Routing is a name lookup, in the configuration, first match wins. runtime.routing is a list of { "pattern", "runtime" }; a pattern is a glob unless it starts with ^ or ends with $, in which case it is a regular expression. * is the catch-all. --runtime overrides it for one command. Since 0.17.0 it decides only where a new container goes; every later command goes to the backend that has the container (ADR-005, #372). An engine never passes --runtime, so a host that runs containers for one is configured with a single rule:

    { "runtime": { "routing": [ { "pattern": "*", "runtime": "crun" } ] } }
    

    container_config.routing and container_config.default_runtime are also read, for older files; when both lists are present the container_config one replaces the other wholesale. container_config.crun_name_patterns, a glob list routing to crun that predates routing, is removed in this revision: a glob under routing is the same matcher, so each entry had a one-line equivalent. A file that still carries it gets a warning naming the replacement — since 0.17.0 it refuses the file with that message, as any key nexcage does not read does (#371).

  4. There is no fallback between backends. The 2024 design had auto_fallback_enabled: try the primary, use the other if it is unavailable. A container is one thing on one backend — an LXC container and a libcrun container are not interchangeable — so a command routed to a backend that is compiled out fails with UnsupportedOperation and says how to build it, rather than quietly making a different kind of container.

  5. There is no runc backend. src/backends/runc shelled out to a runc binary for create/start/kill/delete; no workflow ever built it and it was never run, and a second OCI backend would implement the same interface libcrun already answers. It was removed in 0.13.0 (#88). A routing rule that still names runc routes to crun with a warning — the container it describes is an OCI container either way — and --runtime runc is refused with the replacement named.

  6. There is no VM backend. src/backends/proxmox-vm was compiled out by default and built by no workflow; the router refused every operation routed to it without calling the driver, and the driver imported a module the build did not define. It was removed in 0.14.0 (#306). Unlike runc, nothing can stand in for it — the default backend would make a container where a VM was asked for — so a configuration naming vm (or proxmox, which was mapped to it) is refused as a whole, and --runtime vm exits 2 saying why. qm can come back with a test behind it.

Consequences

Positive:

Negative:

What changed since 2024-12-01, and why

Then Now Why
crun primary, runc fallback Proxmox LXC default; crun for engines; no runc nexcage became a Proxmox-first runtime; the OCI half is driven by engines, and libcrun answers them
call the crun binary link libcrun one process, libcrun’s own errors, no PATH dependence
automatic fallback none a container is one thing on one backend
crun_name_patterns runtime.routing one matcher, one list

References


Last updated: 2026-09-27 (revision); original 2024-12-01