Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Encrypted disk & wipe-on-exit

The opt-in vmDiskSize (GiB) attaches a sparse disk image to back bulk, non-secret data: /scratch, and — under nix.enable — the writable /nix/store overlay upper. vmDiskSize = 0 (the default) keeps the VM pure-RAM.

The LUKS key is guest-only

The host attaches the sparse image but never the key. The guest generates the key from /dev/urandom in its own RAM and luksFormats the disk fresh every boot, so the host only ever sees ciphertext. (Verify: no key file in the seed; the wrapper writes only the vm-disk marker, never the key.)

The disk is mounted in the initrd by a fail-open LUKS oneshot — if it can’t set up, the system falls back to a tmpfs upper rather than failing to boot.

Wipe-on-exit is cryptographic

The key dies with guest RAM at power-off, so the image is inert ciphertext the instant QEMU stops — trap or no trap. The cleanup trap’s rm is belt-and-suspenders; the guarantee rests on the key being gone.

This is why an encrypted disk rather than a plain ephemeral one: wipe-on-exit must survive a crash that skips the cleanup trap, and on modern storage plain deletion ≠ erasure (async SSD TRIM, CoW snapshots retain freed blocks). With full-disk encryption, the image is unrecoverable the moment the key is gone, regardless of how the process ended.

Why one encrypted pool, not a second /nix/store disk

Once the disk is encrypted with a guest-RAM key, disk-vs-tmpfs makes no confidentiality difference to an in-guest attacker (it can read tmpfs or decrypt the disk equally). The right split is bulk on the encrypted disk, secrets in tmpfs — by placement, not a second disk. A second disk only earns its keep for a different lifecycle (a persistent content-addressed store cache) — a separate future feature, deliberately not folded into vmDiskSize.

Where the image must live

The host image MUST live in a disk-backed directory, never tmpfs / $TMP — that would put the “disk” back in RAM and defeat the point. The wrapper refuses a tmpfs target unless CCVM_SCRATCH_ALLOW_TMPFS=1.

/home and root deliberately stay tmpfs, so secrets never go on the disk. Never stage the LUKS key through the seed.

Cost

vmDiskSize > 0 adds ~4–5s to boot — the encrypted disk’s device-settle plus the per-boot luksFormat. This cost is inherent to the wipe-on-exit guarantee (a fresh luksFormat every boot, by design), not a regression. The pure-RAM default boots faster.

Measured baselines under KVM (8 vCPU / 8 GiB): a full boot is ~7.3s (≈277ms kernel + 3.9s initrd + 3.1s userspace); systemd-analyze blame’s top units are the disk device settling at ~4.6s. A running session sits around ~0.7–0.8 GiB RAM because the squashfs store and writable-store overlay upper live on the encrypted disk.