Dataset layout — a reference for --dataset
keyboot does not bake an opinionated dataset profile. By default install-os
creates only the BE root (rpool/ROOT/<be>); you spell out the leaves you want
with --dataset (repeatable), and an opinionated default profile is a future
addition (parameterize-now, defaults-later — ADR 0005 refinement). This page is
the reference for what to pass.
The rule (ADR 0005)
- Binary-coupled state stays in the BE root — anything whose state must match
the installed binaries rolls back as one atomic unit. Notably
/var(including the package DB) and/etcstay in the BE. - Split out only binary-independent leaves — data whose lifetime is
independent of the OS image, or that wants its own
recordsize/snapshot policy. That’s the only reason to make a separate dataset.
Two classes, distinguished purely by where the dataset lives:
- BE-scoped — a child of
rpool/ROOT/<be>(e.g.{BE}/home): clones + rolls back with the BE. - Persistent — outside
rpool/ROOT(e.g.rpool/var/log): survives a BE switch/rollback.
--dataset encodes this with the {BE} token: {BE}/... is BE-scoped,
anything else is persistent.
Reference table
| Path | Placement | --dataset example | Props / notes |
|---|---|---|---|
/ (/usr /etc /bin) | BE root | (created by default) | rolls back as a unit |
/var (+ /var/lib/<pkgdb>) | in BE root | (stays in BE) | pkgdb lockstep with /usr |
/home | BE-scoped or persistent | '{BE}/home:/home' or 'rpool/data/home:/home' | two-homes rule — pick per host |
/var/log | persistent | 'rpool/var/log:/var/log:exec=off' | logs survive a rollback (so you can read why you rolled back) |
/var/cache | persistent, discardable | 'rpool/var/cache:/var/cache:com.sun:auto-snapshot=false' | don’t snapshot heavy caches |
/var/lib/<db> | persistent | 'rpool/data/<db>:/var/lib/<db>:recordsize=16K' | DB page recordsize (8K/16K); own snap policy |
/var/lib/docker | persistent | 'rpool/data/docker:/var/lib/docker' | container store |
/srv | persistent | 'rpool/srv:/srv' | app data |
/tmp | tmpfs (not ZFS) | — | never snapshot |
/boot/efi (ESP) | vfat mdraid1 (not ZFS) | — | keyboot-owned; ro-by-default (ADR 0004) |
| swap | partition or zvol | --swap (separate param) | not on a snapshotted dataset |
Intermediate parents (e.g. rpool/var for rpool/var/log) are auto-created
with mountpoint=none, so they never shadow /.
Recommended per-dataset defaults
Set where it pays — the reason to split a leaf at all:
compression=lz4(cheap, near-universal win;zstdfor cold/archival data)atime=off(avoid write amplification on reads)xattr=sa,acltype=posixacl(SA xattrs; needed by systemd/journald, samba)recordsize: leave the 128K default for general data; 8K–16K for DB datasets matching their page sizeexec=off/setuid=off/devices=offon data-only datasets (/var/log,/srv, caches) — defence in depthcom.sun:auto-snapshot=falseon discardable datasets (/var/cache, swap)
Swap (--swap, GL#35)
install-os --swap none|zvol:<size> (default none). zvol:<size> creates
<pool>/swap as a ZFS volume and mkswaps it. Because the zvol lives on the
pool, swap is encrypted at rest via the same LUKS keyfile as everything
else — no extra key management.
The zvol uses OpenZFS swap-tuned props (sync=always, primarycache=metadata,
secondarycache=none, logbias=throughput, compression=zle, 4K volblock,
com.sun:auto-snapshot=false).
Boot-time activation is a dedicated keyboot-swap service, not fstab. At
boot the zvol’s device nodes fire their uevents inside keyboot’s initramfs
(before the BE’s real udev), and minimal BEs lack the ZFS udev rules that make
/dev/zvol/<pool>/swap, so a plain swapon -a from fstab races a non-existent
symlink. Instead /usr/local/sbin/keyboot-swapon re-triggers udev and swapons
the swap-signed zdN device directly (validated by swapon itself, so no
blkid flag dependence). It runs via an openrc init service in the boot
runlevel or a systemd oneshot (keyboot-swap.service, WantedBy=multi-user).
The install marker prints KEYBOOT-SWAP <active-count> so the boot test can
assert swap actually came up.
Caveat — swap-on-zvol can deadlock under heavy memory pressure (ZFS may need to allocate memory to write out swap — the memory it’s trying to free). It’s fine for moderate overcommit, but for swap-heavy workloads a raw, keyfile-encrypted swap partition is safer. That backing (
--swap partition:<size>) is deferred to GL#34: it can’t live on the pool, so it needs the partition-role work.
Example: a typical server
install-os debian --disk … \
--dataset '{BE}/home:/home' \
--dataset 'rpool/var/log:/var/log:exec=off' \
--dataset 'rpool/var/cache:/var/cache:com.sun:auto-snapshot=false' \
--dataset 'rpool/data/docker:/var/lib/docker'
This table is a recommendation, not a baked default. Fleet policy belongs in Ansible (the installer accepts a spec; ADR 0005 / 0015). An opinionated one-flag default profile is deferred to a future pass.