krun microVM setup¶
The krun backend runs the sandbox image inside a libkrun microVM via podman --runtime krun, putting the workload behind a hardware virtualization boundary (KVM on Linux, Hypervisor.framework on Apple Silicon) with its own guest kernel. Use it for genuinely untrusted code, where a host-kernel exploit must not be able to reach the host.
krun is experimental and opt-in: --isolation=auto never selects it. The bwrap and docker backends need no extra setup; this page is only for enabling krun. For the security model and trade-offs, see How sandboxing works and the krun backend reference.
Prerequisites¶
| Requirement | Why | Check |
|---|---|---|
/dev/kvm accessible |
Hardware virtualization for the microVM | ls -l /dev/kvm |
Rootless podman ready |
Drives the microVM as your user | /etc/subuid and /etc/subgid have a line for your user; newuidmap/newgidmap present |
| Kernel with rootless overlay | Tool dirs (kernel 5.13+) | Any modern distro |
nft or iptables (proxy mode only) |
Port-scopes guest egress to the proxy inside the VMM netns; krun + proxy on Linux fails closed without one | nft --version or iptables --version (usually already present) |
On Linux you need bare-metal KVM or a VM with nested virtualization enabled. If /dev/kvm is missing, krun cannot run here. The subuid/subgid mappings are configured by default on most distributions when podman is installed. For proxy mode you also need nft or iptables on the host, with the nf_tables and nf_conntrack kernel modules loaded - if the lockdown cannot be applied a krun + proxy launch aborts (devsandbox doctor reports this as the top-level proxy: firewall row, which gates the bwrap backend's proxy mode too). devsandbox doctor also checks the rootless prerequisites directly: krun: system pasta (the host pasta binary podman needs - the copy devsandbox embeds for bwrap does not count) and krun: rootless id mapping (your /etc/subuid and /etc/subgid ranges). All krun rows are advisory warnings, so doctor still exits zero on a host that only runs bwrap or docker.
Install¶
You need three things: podman (the container CLI), the krun OCI runtime (a crun built with libkrun, which exposes a krun runtime on PATH), and passt (provides pasta for podman rootless networking, used for default networking and the proxy gateway).
Arch Linux¶
The krun package installs /usr/bin/krun and pulls in crun + libkrun.
Fedora¶
crun-krun provides the krun runtime. On older releases it lives in a Copr:
sudo dnf copr enable -y slp/libkrunfw
sudo dnf copr enable -y slp/libkrun
sudo dnf copr enable -y slp/crun-krun
sudo dnf install -y crun-krun
Debian / Ubuntu¶
podman and passt are packaged (sudo apt install podman passt), but the libkrun-based krun runtime is not packaged on most releases. Build libkrun and a libkrun-enabled crun from source per the libkrun and crun projects, ensuring a krun binary ends up on PATH.
macOS (Apple Silicon)¶
libkrun uses Hypervisor.framework on Apple Silicon. Intel Macs have no supported path and are refused at preflight with a --isolation=docker remediation, rather than failing opaquely after the image build. On Apple Silicon this path is not yet validated for devsandbox; on macOS use the Docker backend for now. Proxy mode is refused on macOS: the egress lockdown that forces guest traffic through the proxy is Linux-only, so krun + proxy would otherwise run with open egress. Run krun without proxy mode on macOS, or run on Linux for the full proxy egress lockdown.
Verify¶
First, confirm podman can boot a microVM with the krun runtime:
This should exit 0 (it pulls a tiny image on first run). If it fails, krun is not set up correctly - fix this before involving devsandbox.
Then confirm devsandbox sees the prerequisites:
The krun rows should read ok. On Linux doctor reports two extra krun rows the
run path depends on - krun: system pasta (rootless podman networking) and
krun: rootless id mapping (the subuid/subgid ranges --userns=keep-id needs) -
plus the backend-neutral proxy: firewall row, which is what proxy mode's egress
lockdown needs on krun and bwrap:
│ proxy: firewall │ ✓ ok │ /usr/sbin/nft (lockdown rules apply)│
│ krun: podman │ ✓ ok │ /usr/bin/podman │
│ krun: runtime │ ✓ ok │ /usr/bin/krun │
│ krun: kvm │ ✓ ok │ /dev/kvm accessible │
│ krun: system pasta │ ✓ ok │ /usr/bin/pasta │
│ krun: rootless id mapping│ ✓ ok │ /etc/subuid and /etc/subgid map you │
These rows are informational - they warn rather than error when unmet, so doctor never fails for users who do not use the feature they gate. proxy: firewall only matters for proxy mode, which fails closed at launch without it.
Run¶
Or make it the default in ~/.config/devsandbox/config.toml:
First run is slow. krun uses podman, which keeps an image store separate from Docker's, so the base image (ghcr.io/zekker6/devsandbox:latest, ~2.5 GB) is pulled and the local image built from scratch the first time - expect a few minutes. Subsequent runs reuse the podman image and boot in seconds.
Ephemeral by design. Each launch boots a fresh microVM (a clean guest kernel every run); there is no keep_container reuse. Files the workload writes to your project come back owned by you (rootless podman with --userns=keep-id).
Host mise tools are available. On Linux hosts your ~/.local/share/mise/installs is shared read-only into the guest and seeded into the sandbox's mise data dir at boot, so host-installed toolchains (go, node, task, ...) resolve inside the microVM without reinstalling or network access. In proxy mode the guest additionally runs mise offline (MISE_OFFLINE=1) - remote version-list lookups cannot traverse the egress lockdown and would otherwise stall listing commands for minutes; everything resolves from the seeded installs instead, and an explicit MISE_OFFLINE=0 mise install ... re-enables the network (through the proxy) when you really want an in-sandbox install. See Tools: mise for details and caveats.
Confirm the microVM boundary¶
The point of krun is a separate guest kernel. Compare the kernel inside the sandbox with your host kernel - they differ:
A different kernel version inside the sandbox confirms the workload is running behind the hardware virtualization boundary, not just in a namespace on your host kernel.
Security: disable the Docker tool¶
If [tools.docker] is enabled, devsandbox mounts a filtered Docker socket into the sandbox - and a loud warning is printed at startup:
Mounting your host Docker into a microVM meant for untrusted code hands the guest host-level access and defeats the whole point of krun. Turn it off:
Build-time trust boundary¶
The microVM isolates the workload at run time, but the sandbox image is built before the guest boots. If you point krun at a project-provided Dockerfile (sandbox.docker.dockerfile), devsandbox builds it with host podman build, so every RUN step in that Dockerfile executes on the host - outside the krun guest and outside the proxy egress lockdown.
krun prints a warning before such a build:
krun: building project Dockerfile <path> on the host via podman build; its RUN steps run outside the microVM guest and the proxy egress lockdown. Only build Dockerfiles you trust.
The build still proceeds - this is a disclosure, not a hard stop. Only build Dockerfiles you trust under krun; the run-time microVM boundary does not cover the image build. The default (auto-generated) Dockerfile is trusted devsandbox content and builds silently.
Status and limitations¶
- Egress lockdown (proxy mode) is applied host-side in the VMM's pasta network namespace and is validated on a
/dev/kvmhost. See the krun backend reference for the full networking and security-boundary explanation. devsandbox forwardis best-effort for krun: the session is registered, but reaching a listener inside the guest through the microVM network namespace is not yet validated.- macOS (HVF) is not yet validated, and proxy mode is refused there because the egress lockdown is Linux-only; krun + proxy on macOS fails closed rather than run with open egress. Non-proxy krun on macOS is unaffected.
- IPv6 is disabled in the guest under proxy mode: pasta is invoked with
-4, so the guest is given IPv4 only and has no IPv6 route or IPv6 host-loopback map - there is no IPv6 egress path for the IPv4 lockdown to miss. - No
pidslimit.memoryandcpusfrom[sandbox.resources]apply normally, and krun fills them with microVM defaults of4gand2when you leave them unset.pidsis not enforceable here: a pids limit caps a container's process cgroup, but a krun sandbox is the libkrun VMM process, so the flag would cap the VMM's own host-side threads rather than the processes inside the guest, whose PID space belongs to the guest kernel and cannot be reached from the host. krun skips the flag and prints a warning at startup saying so, rather than breaking the VM while protecting nothing. Use the bwrap or docker backend when you need a pids limit. See Resource Limits.
Next step¶
Back to Quick start, or read How sandboxing works for the full isolation model.