Environment Variable Reference
Every aspect of the kldload build and install is controlled by environment variables.
This is the complete, authoritative reference for every variable the installer reads.
Set them in kldload.env (sourced automatically at build time), in an answers.env
file on a seed disk (read at install time), or pass them directly on the command line.
No variable is required — sensible defaults exist for everything.
The design principle: Environment variables are the universal interface.
Every shell reads them. Every CI system sets them. Every container runtime accepts them.
Every cron job can export them. You do not need a parser, a library, a schema validator,
or a YAML-to-JSON converter. export KLDLOAD_DISTRO=debian works in bash, zsh,
fish, and every CI pipeline on earth. The config format is the shell itself.
Environment variables were chosen over YAML, TOML, JSON, or any config file format deliberately. I tried YAML for the first prototype. Then I tried TOML. Then I realized that every time I wanted to pass a config value from a CI pipeline, from a Terraform provisioner, from a Packer variable, from a Docker --env flag, or from a shell script, I had to serialize the value into the config format and then deserialize it on the other end. Environment variables skip both steps. They are the native interface for every tool in the Unix ecosystem.
Two scopes exist and they never mix: build-time variables control what goes into the ISO (PROFILE, DISTRO, ARCH). Install-time variables control what happens on the target machine (KLDLOAD_HOSTNAME, KLDLOAD_DISK, KLDLOAD_PASSWORD). Build-time vars go in kldload.env. Install-time vars go in the answer file or the web UI. The naming convention enforces this: build-time vars have no prefix. Install-time vars start with KLDLOAD_. If you see the prefix, it runs on the target. If you do not, it runs on the build machine.
How environment variables work
kldload uses environment variables at two distinct stages: build time (when creating the ISO) and install time (when installing to the target disk). The two stages use different files, different variable names, and different mechanisms. Understanding the boundary between them is essential.
Build-time variables
Stored in kldload.env at the root of the repo. Sourced by deploy.sh
before any build step runs. Control what distro, profile, and architecture the ISO targets.
Also hold credentials for Proxmox deployment and RHEL subscriptions. Never baked into the ISO.
Never reach the target machine. kldload.env is gitignored.
Install-time variables
Stored in answers.env on a FAT32 seed USB labeled KLDLOAD-SEED.
Read by the installer when the live ISO boots. Control hostname, disk, users, networking,
ZFS topology, WireGuard, export format, and every other aspect of the installed system.
All prefixed with KLDLOAD_. Can also be set via the web UI or WebSocket API.
The answers file format
The answers file is a plain text file containing KEY=value pairs, one per line.
Comments start with #. Blank lines are ignored. Values can be quoted with single
or double quotes (the installer strips them). Every KLDLOAD_* variable found in
the file is exported into the installer's environment.
# This is a comment — ignored by the parser
KLDLOAD_DISTRO=debian
KLDLOAD_HOSTNAME=web-prod-01
KLDLOAD_PASSWORD="correct horse battery staple"
KLDLOAD_SSH_PUBKEY='ssh-ed25519 AAAA... ops@infra'
# Blank lines are fine
KLDLOAD_TIMEZONE=America/Vancouver
Precedence rules
When the same variable is set in multiple places, the installer follows a strict precedence order. Higher-priority sources override lower-priority ones. This lets you set broad defaults in an answers file and override specific values on the command line or via the web UI.
Resolution order (highest to lowest priority)
1. Command-line environment — KLDLOAD_DISK=/dev/nvme0n1 kldload-install-target --config answers.env wins over anything in the file.
2. Web UI / WebSocket API — values submitted through the web interface override file defaults.
3. Answers file — answers.env on the seed disk or passed via --config.
4. Built-in defaults — hardcoded in the installer. Every variable has one. You only set what you want to change.
How the installer reads variables
The installer (kldload-install-target) has two modes: guided (interactive web UI)
and config (unattended, reads an answers file). In guided mode, the web UI collects
values and exports them before calling the installer backend. In config mode, the installer
calls k_answers_load_env_file() which parses the file line by line, validates
variable names against ^[A-Za-z_][A-Za-z0-9_]*$, strips quotes, and exports
each key-value pair. After loading, it calls k_save_effective_config() to write
the resolved configuration to /var/log/installer/effective-config.env —
with passwords redacted — for audit and debugging.
The parser is deliberately simple. It does not support variable interpolation, multi-line values, heredocs, arrays, or nested structures. A line is either a comment, blank, or a KEY=value assignment. That is the entire grammar. This means the answers file is trivially machine-generated — any language that can write KEY=value\n can produce one. No YAML indentation bugs. No JSON trailing comma errors. No TOML datetime parsing differences between implementations.
The effective config file is your audit trail. After every install, /var/log/installer/effective-config.env contains the exact values that were used. Passwords are replaced with __REDACTED__. Everything else is preserved verbatim. If something went wrong, you can see exactly what the installer saw. If you need to reproduce the install on another machine, copy the effective config, fill in the passwords, and run it again.
# Three ways to run an unattended install:
# 1. Seed disk — zero-touch, physical hardware
# Format USB as FAT32, label KLDLOAD-SEED, drop answers.env on it
# Boot with ISO + seed USB — installer finds it automatically
# 2. Command line — VMs, CI, scripted deploys
kldload-install-target --config /path/to/answers.env
# 3. Environment variables — override anything
KLDLOAD_DISK=/dev/nvme0n1 KLDLOAD_HOSTNAME=override-01 \
kldload-install-target --config answers.env
Core variables
These are the fundamental variables that define what gets installed and how it is identified. Most installs only need to set a handful of these — the defaults handle everything else.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_DISTRO | centos | Target distribution to install. Determines which bootstrap method and package manager is used. |
KLDLOAD_PROFILE | server | Install profile. Controls which packages, services, and system files are installed. |
KLDLOAD_HOSTNAME | kldload-node | System hostname. Written to /etc/hostname and set via hostnamectl. |
KLDLOAD_TIMEZONE | UTC | System timezone. Any valid TZ database name. Symlinks /etc/localtime. |
KLDLOAD_LOCALE | en_US.UTF-8 | System locale. Generated during bootstrap and set as default in /etc/locale.conf. |
KLDLOAD_KEYBOARD_LAYOUT | us | XKB keyboard layout code. Written to /etc/vconsole.conf and X11 config. |
KLDLOAD_KEYBOARD_VARIANT | empty | XKB keyboard variant. For layouts with multiple variants (e.g., dvorak for us). |
KLDLOAD_DISTRO — target distribution
Valid values and what they mean
centos — CentOS Stream 9. DNF-based. Default. Uses RPM darksite for offline install.
debian — Debian 13 (Trixie). APT-based. Uses Debian darksite on port 3142.
ubuntu — Ubuntu 24.04 (Noble Numbat). APT-based. Uses Ubuntu darksite on port 3143. Requires universe component for ZFS.
fedora — Fedora 41. DNF-based. Uses RPM darksite.
rhel — Red Hat Enterprise Linux 9. DNF-based. Requires RHEL_ACTIVATION_KEY and RHEL_ORG_ID at build time.
rocky — Rocky Linux 9. DNF-based. Uses RPM darksite. Drop-in RHEL replacement.
arch — Arch Linux (rolling). Pacstrap-based. Requires internet — no darksite (rolling release).
alpine — Alpine Linux. APK-based. Core profile only. Minimal, musl-based.
freebsd — FreeBSD. Uses bsdinstall bootstrap. Experimental.
openbsd — OpenBSD. Experimental.
The distro choice is the first fork in the installer. CentOS, Rocky, RHEL, and Fedora all take the DNF path — dnf --installroot into the target. Debian and Ubuntu take the debootstrap path. Arch takes the pacstrap path. Alpine takes the apk path. FreeBSD and OpenBSD take the BSD bootstrap path. After the fork, everything converges again: ZFS pool creation, bootloader installation, user creation, networking, WireGuard, and firstboot all run the same code regardless of distro. The distro only matters for the initial package installation and the package names.
This is why you can switch distros with a single variable change. The same ISO, the same installer, the same answer file — just change KLDLOAD_DISTRO=debian to KLDLOAD_DISTRO=rocky and you get Rocky Linux with the exact same ZFS layout, the exact same WireGuard config, the exact same user setup. The distro is interchangeable. The platform is constant.
KLDLOAD_PROFILE — install profile
Valid values and what they install
server — Headless SSH server. Installs openssh-server, chrony, wireguard-tools, sanoid, podman, tmux, htop, btop, fzf, bat, eza, ripgrep, nftables, tcpdump, and the kldload management tools (kstatus, kbackup, krollback, krestore). Python3 with websockets for the web UI. No desktop environment.
desktop — Full GNOME desktop. Everything in server, plus GNOME Shell, GDM, Nautilus, Firefox (or Epiphany on Ubuntu), NetworkManager, and graphical tools. The web UI still runs — accessible at :8080 from the desktop browser.
core — Bare minimum. ZFS on root, openssh-server, sudo, curl, vim, iproute2, nftables, wireguard-tools. No kldload management tools, no web UI, no sanoid, no darksites. Stock distro with ZFS. For people who want the ZFS foundation and nothing else.
ai — AI/ML workstation. Everything in server, plus Python pip, cmake, gcc, make, git, cloud-init, qemu-guest-agent. Designed for GPU workloads. Available on Arch.
client — Minimal client. SSH, sudo, curl, NetworkManager, WireGuard. For machines that join a cluster but run minimal services.
KLDLOAD_HOSTNAME — system hostname
Any valid hostname: letters, digits, hyphens. No dots (it is a hostname, not an FQDN).
Written to /etc/hostname, set via hostnamectl set-hostname in chroot.
The default kldload-node is fine for testing but should be changed for production.
For fleet deployments, use a naming convention: web-prod-01, db-staging-03,
k8s-worker-12.
KLDLOAD_TIMEZONE — system timezone
Any valid TZ database name.
Common values: UTC, America/New_York, America/Vancouver,
America/Chicago, America/Los_Angeles, Europe/London,
Europe/Berlin, Asia/Tokyo, Australia/Sydney.
The installer symlinks /etc/localtime to the appropriate zoneinfo file.
Servers should use UTC. Desktops can use local time.
KLDLOAD_LOCALE and KLDLOAD_KEYBOARD_LAYOUT
KLDLOAD_LOCALE is a locale string like en_US.UTF-8, en_GB.UTF-8,
de_DE.UTF-8, ja_JP.UTF-8, or fr_FR.UTF-8. The locale is
generated during bootstrap and set as the system default. KLDLOAD_KEYBOARD_LAYOUT
is an XKB layout code: us, gb, de, fr,
es, jp, ru, etc. The optional KLDLOAD_KEYBOARD_VARIANT
selects a sub-layout: dvorak, colemak, mac, intl, etc.
Disk and storage
Storage configuration is the most consequential set of variables. The ZFS pool topology is
permanent — you cannot change it after creation without destroying and recreating
the pool. Choose carefully. When in doubt, start with single and add mirrors later.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_DISK | auto-detected | Primary target block device. The installer wipes this disk entirely. Auto-detected if only one non-USB, non-optical disk exists. |
KLDLOAD_STORAGE_MODE | zfs | Storage mode. Always zfs. This is the only supported value. |
KLDLOAD_ZFS_TOPOLOGY | single | ZFS vdev layout for the root pool. Determines redundancy and performance characteristics. |
KLDLOAD_ZFS_DATA_DISKS | empty | Additional disks for multi-disk topologies. Space-separated list of block device paths. |
KLDLOAD_ZFS_SPECIAL_DISKS | empty | NVMe devices for metadata/small-block special vdev acceleration. Space-separated. |
KLDLOAD_ZFS_ENCRYPT | 0 | Enable ZFS native encryption (AES-256-GCM). Set to 1 to enable. Prompts for passphrase at every boot. |
KLDLOAD_ZFS_PASSPHRASE | prompted | Encryption passphrase when KLDLOAD_ZFS_ENCRYPT=1. If not set, the installer prompts interactively. |
KLDLOAD_ENABLE_ZFS | 1 | Always 1. ZFS is non-optional. This variable exists for internal consistency. |
KLDLOAD_FORCE_WIPE | 0 | Set to 1 to skip the "are you sure" confirmation before wiping the disk. Required for unattended installs. |
KLDLOAD_DISK — target disk
The block device path to install to. Examples: /dev/sda, /dev/nvme0n1,
/dev/vda (KVM virtio). The entire disk is wiped and repartitioned: a 512 MB EFI System
Partition (FAT32) and the remainder as a ZFS pool. If only one candidate disk exists (excluding
USB and optical drives), the installer auto-detects it. If multiple disks exist and
KLDLOAD_DISK is not set, the installer prompts (guided mode) or fails (config mode).
Disk auto-detection filters aggressively. It excludes: loop devices (/dev/loop*), RAM disks, optical drives (/dev/sr*), the USB boot media itself (detected by mount point), and anything smaller than 8 GB. What remains is the candidate list. If exactly one disk survives the filter, it is used automatically. If zero survive, the installer errors. If multiple survive and no KLDLOAD_DISK is set, you get a prompt or a failure depending on mode.
For unattended installs, always set KLDLOAD_DISK explicitly. Auto-detection is convenient for testing but dangerous in production. A machine that grows a second disk (hot-plugged SAS, USB backup drive left plugged in) will suddenly fail auto-detection. Explicit is better than implicit.
KLDLOAD_ZFS_TOPOLOGY — pool vdev layout
Valid topologies and their trade-offs
single — One disk, no redundancy. The disk IS the pool. Fastest for single-disk machines. One disk failure = total data loss. Good for VMs, test environments, CI runners where the data is ephemeral.
mirror — Two disks, mirrored. Every write goes to both disks. Survives one disk failure. Reads can come from either disk (2x read throughput). Requires KLDLOAD_ZFS_DATA_DISKS with one additional disk. The gold standard for production servers.
raidz1 — Three or more disks, single parity. Survives one disk failure. Usable capacity = (N-1) disks. Good for storage-heavy workloads where you need capacity over raw performance. Requires KLDLOAD_ZFS_DATA_DISKS with two or more additional disks.
mirror-stripe — Four disks, RAID10 equivalent. Two mirrored pairs striped together. Survives one disk failure per mirror pair. Best performance and redundancy for databases. Requires KLDLOAD_ZFS_DATA_DISKS with three additional disks.
KLDLOAD_ZFS_DATA_DISKS — additional disks
Space-separated list of block device paths for multi-disk topologies. These disks are added
to the pool alongside KLDLOAD_DISK. For a mirror, provide one disk. For raidz1,
provide two or more. For mirror-stripe, provide three.
# Mirror: /dev/sda (primary) + /dev/sdb (mirror)
KLDLOAD_DISK=/dev/sda
KLDLOAD_ZFS_TOPOLOGY=mirror
KLDLOAD_ZFS_DATA_DISKS=/dev/sdb
# RAIDZ1: /dev/sda + /dev/sdb + /dev/sdc (3 disks, 1 parity)
KLDLOAD_DISK=/dev/sda
KLDLOAD_ZFS_TOPOLOGY=raidz1
KLDLOAD_ZFS_DATA_DISKS="/dev/sdb /dev/sdc"
# RAID10: /dev/sda+sdb mirror, /dev/sdc+sdd mirror, striped
KLDLOAD_DISK=/dev/sda
KLDLOAD_ZFS_TOPOLOGY=mirror-stripe
KLDLOAD_ZFS_DATA_DISKS="/dev/sdb /dev/sdc /dev/sdd"
KLDLOAD_ZFS_SPECIAL_DISKS — metadata acceleration
Optional NVMe devices dedicated to metadata and small-block I/O. ZFS stores file metadata,
dedup tables, and small files (under special_small_blocks) on these devices instead
of the main pool disks. Dramatically accelerates ls, find, stat,
and random small-read workloads when your data disks are spinning rust. Should be mirrored
for redundancy (provide two NVMe paths). Loss of an unmirrored special vdev = pool loss.
KLDLOAD_ZFS_ENCRYPT — native encryption
Set to 1 to enable ZFS native encryption using AES-256-GCM. The root pool is
created with encryption=aes-256-gcm and keylocation=prompt.
Every boot requires entering the passphrase before the pool can mount. The passphrase can
be supplied via KLDLOAD_ZFS_PASSPHRASE in the answers file for unattended
encrypted installs. After install, change the key or key source as needed.
ZFS native encryption encrypts at the dataset level, not the disk level. This means: ZFS can still read pool metadata (it knows how many datasets exist, their names, and their properties) but cannot read file contents without the key. Scrubs still work on encrypted pools. Snapshots inherit encryption from their parent. Send/receive preserves encryption — you can replicate encrypted datasets to an untrusted remote without ever exposing the key. The remote stores ciphertext it cannot read.
The trade-off: every boot requires the passphrase. For servers in a datacenter, this means someone has to type the passphrase into the console (or over IPMI) after every reboot. For laptops, it is a natural part of the boot flow. For unattended servers, consider whether full-disk encryption is actually needed for your threat model, or whether encrypting specific datasets (e.g., rpool/home) with key files stored on a separate secure medium is a better approach.
Networking
Network configuration for the installed system. The default is DHCP, which works out of the box on most networks. Static IP configuration requires setting all four static fields: IP, prefix, gateway, and DNS.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_NET_METHOD | dhcp | Network configuration method. dhcp for automatic, static for manual IP assignment. |
KLDLOAD_NET_IFACE | auto-detected | Network interface for static configuration. Examples: eth0, ens18, enp0s3. Auto-detected as the first non-loopback interface if not set. |
KLDLOAD_NET_IP | none | Static IP address. Required when KLDLOAD_NET_METHOD=static. Example: 10.0.1.50. |
KLDLOAD_NET_PREFIX | 24 | CIDR prefix length. 24 = 255.255.255.0 (254 hosts). 16 = 255.255.0.0 (65534 hosts). |
KLDLOAD_NET_GW | none | Default gateway IP. Required when KLDLOAD_NET_METHOD=static. Example: 10.0.1.1. |
KLDLOAD_NET_DNS | none | DNS servers. Comma-separated. Example: 1.1.1.1,8.8.8.8 or 10.0.1.1 for a local resolver. |
DHCP vs static — when to use which
DHCP is the right default. It works on home networks, cloud VMs (Proxmox, KVM, AWS, GCE),
and most corporate LANs. The installed system gets an IP, gateway, and DNS from whatever DHCP server
is on the network. No configuration needed.
Static is for production servers that need predictable addresses: database servers,
load balancers, DNS servers, monitoring stacks, WireGuard endpoints. Anything that other services
connect TO should have a static IP. Anything that connects OUT can use DHCP.
# DHCP — the default, works everywhere
KLDLOAD_NET_METHOD=dhcp
# Static IP for a production database server
KLDLOAD_NET_METHOD=static
KLDLOAD_NET_IFACE=ens18
KLDLOAD_NET_IP=10.0.1.50
KLDLOAD_NET_PREFIX=24
KLDLOAD_NET_GW=10.0.1.1
KLDLOAD_NET_DNS=10.0.1.1,1.1.1.1
# Static with a /16 subnet — large flat network
KLDLOAD_NET_METHOD=static
KLDLOAD_NET_IFACE=eth0
KLDLOAD_NET_IP=172.16.5.100
KLDLOAD_NET_PREFIX=16
KLDLOAD_NET_GW=172.16.0.1
KLDLOAD_NET_DNS=172.16.0.53
The interface name (KLDLOAD_NET_IFACE) is one of those things that trips people up. CentOS and RHEL use predictable names like ens18, enp0s3, eno1. Debian and Ubuntu sometimes use eth0 depending on the kernel and network namespace. VirtIO NICs in KVM show up as ens3 or enp0s18. Physical Intel NICs are often eno1 or enp3s0. If you do not know the interface name, leave it blank — the installer detects the first non-loopback interface. Or boot the live ISO first, run ip link, and note the name.
User and authentication
Every kldload install creates a non-root admin user with passwordless sudo. Root login via SSH is disabled by default. SSH public key authentication is the recommended method for production.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_USERNAME | admin | Non-root admin user. Created with a home directory, bash shell, and membership in the sudo (or wheel) group with NOPASSWD. |
KLDLOAD_PASSWORD | prompted | Password for the admin user. The web UI pre-fills kldload for testing. In unattended mode, this must be set explicitly. |
KLDLOAD_ROOT_PASSWORD | none | Root password. If unset, root account is locked (no password login). Root SSH is always disabled regardless of this setting. |
KLDLOAD_SSH_PUBKEY | none | SSH public key for the admin user. Written to ~admin/.ssh/authorized_keys. Accepts any OpenSSH key format (ed25519, RSA, ECDSA). |
KLDLOAD_ADMIN_SSH_PUBKEY | none | SSH public key for the root account. Written to /root/.ssh/authorized_keys. For emergency access only — root SSH password auth remains disabled. |
KLDLOAD_GENERATE_SSH_KEY | 1 | Generate SSH host keys during install. Set to 0 if you supply your own host keys or want cloud-init to generate them on first boot. |
Security model for user accounts
The admin user has full sudo NOPASSWD access. This is deliberate: the machine is configured
at install time, not at login time. The admin user can do anything root can do, but is audited
in /var/log/auth.log and journalctl -u sudo. Root SSH login is disabled
in /etc/ssh/sshd_config (set PermitRootLogin no). Root password login
at the console is only possible if KLDLOAD_ROOT_PASSWORD is explicitly set.
SSH key authentication is always preferred over passwords. Set KLDLOAD_SSH_PUBKEY
and disable password auth entirely in production.
# Minimal user config — password only
KLDLOAD_USERNAME=admin
KLDLOAD_PASSWORD=changeme
# Production user config — SSH key, no root password
KLDLOAD_USERNAME=sysadmin
KLDLOAD_PASSWORD='$ecure-P@ssphrase-2024'
KLDLOAD_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx... ops@infra"
KLDLOAD_ADMIN_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFy... break-glass@vault"
# Lock everything down — SSH key only, root locked
KLDLOAD_USERNAME=deployer
KLDLOAD_PASSWORD='randomly-generated-32-char-passphrase'
KLDLOAD_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICz... terraform@ci"
KLDLOAD_GENERATE_SSH_KEY=1
The password is stored hashed in /etc/shadow using the strongest hash the target distro supports (typically yescrypt on Debian/Ubuntu, SHA-512 on CentOS/Rocky). The plaintext password never touches the installed system's disk — it is hashed in memory during install and only the hash is written. The answers file on the seed USB does contain the plaintext, so treat the seed USB as a credential: wipe it after use, do not leave it plugged in, do not commit it to git.
For production fleets, generate a random password per machine (unique, never reused) and rely exclusively on SSH key authentication. The password exists only as a break-glass mechanism for console access. If you do not need console access (cloud VMs, headless servers with IPMI), you can still set a password — but the SSH key is what your automation uses day to day.
WireGuard
WireGuard is the encrypted mesh network layer. kldload installs the WireGuard kernel module
and tools by default on every profile except core. The KLDLOAD_WIREGUARD variable
controls whether a WireGuard interface is configured during install (not just installed).
Four WireGuard interfaces (wg0 through wg3) are available for different network planes.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_WIREGUARD | 0 | Set to 1 to configure a WireGuard interface during install. The tools are always installed (server/desktop profiles); this controls whether a tunnel is actually set up. |
KLDLOAD_ENABLE_WIREGUARD | 1 | Include WireGuard kernel module and userspace tools in the install. On by default for server and desktop profiles. |
KLDLOAD_WG0_PORT | 51820 | Listen port for wg0 (management plane). Standard WireGuard port. |
KLDLOAD_WG1_PORT | 51821 | Listen port for wg1 (data plane). Used for cluster-internal traffic. |
KLDLOAD_WG2_PORT | 51822 | Listen port for wg2 (storage plane). Dedicated to ZFS replication and NFS/iSCSI. |
KLDLOAD_WG3_PORT | 51823 | Listen port for wg3 (external plane). For site-to-site VPN and external access. |
KLDLOAD_WIREGUARD_PRIVATE_KEY | generated | WireGuard private key. If not set, a new keypair is generated during install. The private key is redacted in the effective config log. |
KLDLOAD_WIREGUARD_PRESHARED_KEY | none | Optional preshared key for additional post-quantum security on a specific peer connection. Redacted in logs. |
The four WireGuard planes
kldload separates network traffic into four isolated WireGuard tunnels. Each tunnel has its own
interface, port, keys, and routing table. Traffic on one plane cannot reach another plane.
wg0 (management, :51820) — SSH, monitoring, Salt/Ansible, cluster control. Always-on for ops access.
wg1 (data, :51821) — Application traffic between cluster nodes. Kubernetes pod-to-pod, service mesh, inter-service communication.
wg2 (storage, :51822) — ZFS send/receive, NFS mounts, iSCSI targets. High-throughput, low-latency. Kept separate so storage traffic never competes with application traffic.
wg3 (external, :51823) — Site-to-site VPN. Connect remote offices, cloud VPCs, or road-warrior laptops to the cluster.
Most WireGuard tutorials show one interface, one tunnel, one flat network. That works for a VPN. It does not work for infrastructure. When your ZFS replication is saturating the link, you do not want your SSH session to lag. When your Kubernetes pods are chattering, you do not want your monitoring metrics to drop. Separate planes solve this at the network layer — no QoS, no traffic shaping, no iptables marking. Just separate tunnels on separate ports with separate routing tables. The kernel handles isolation. You configure it once at install time.
You do not have to use all four. Most standalone servers use wg0 only (management access). Clusters use wg0 + wg1 (management + data). Storage-heavy setups add wg2. Multi-site deployments add wg3. Each plane is independent — configure only what you need.
# Enable WireGuard with custom ports
KLDLOAD_WIREGUARD=1
KLDLOAD_WG0_PORT=51820
KLDLOAD_WG1_PORT=51821
# Supply your own keypair (for reproducible deployments)
KLDLOAD_WIREGUARD=1
KLDLOAD_WIREGUARD_PRIVATE_KEY="yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="
KLDLOAD_WIREGUARD_PRESHARED_KEY="FpCyhT+bZAVniNcJwB1fkBXG7jUxBPBRzUtvmWSJCZs="
Feature toggles
Boolean flags that enable or disable optional subsystems. Each toggle adds kernel modules, userspace tools, and configuration to the installed system. All modules are built and signed at image creation time — not installed after the fact.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_ENABLE_KVM | 0 | Install libvirt, QEMU/KVM, and virt-manager. Creates rpool/vms/images with tuned recordsize. Caps ARC at 50% RAM to leave room for VM memory. |
KLDLOAD_ENABLE_EBPF | 0 | Install eBPF toolchain: bpftool, bpftrace, bcc-tools. Enables kernel-level tracing, performance analysis, and custom eBPF programs. |
KLDLOAD_ENABLE_AI | 0 | Install AI/ML stack: Python pip, cmake, gcc, make, git. For GPU workloads and model training/inference. |
KLDLOAD_NVIDIA_DRIVERS | 0 | Install NVIDIA proprietary GPU drivers. Required for CUDA workloads, GPU passthrough, and AI inference. Adds significant size to the install. |
WireGuard is on by default because every server should have encrypted management access. ZFS is always on because that is the entire point of kldload. Everything else is opt-in because it adds size, complexity, and attack surface. NVIDIA drivers add hundreds of megabytes. eBPF tools add kernel headers. KVM installs an entire virtualization stack. AI installs build toolchains. Each toggle is an explicit decision: "I need this capability on this machine." The defaults produce a lean, secure, fast-booting server with ZFS and WireGuard. Add what you need. Leave off what you do not.
Packages and darksites
Package management variables control what extra software is installed beyond the profile defaults, and how the package mirrors are configured on the installed system.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_EXTRA_PACKAGES | empty | Additional packages to install beyond the profile defaults. Space or comma separated. Must exist in the darksite (for offline installs) or in the distro repos (for online installs). |
KLDLOAD_KEEP_DARKSITE | 0 | Set to 1 to copy the darksite mirror to the installed system. Allows the installed machine to install additional packages offline, or serve as a local mirror for other machines. |
KLDLOAD_CUSTOM_MIRROR_URL | none | Custom APT/DNF mirror URL for the installed system's package configuration. Useful for air-gapped environments with an internal mirror. |
How darksites work
Darksites are offline package mirrors baked into the ISO at build time. The ISO contains every
RPM, DEB, or APK needed to install the target system without internet access. During install,
the live ISO serves these packages to the installer via local HTTP (Debian on :3142, Ubuntu on :3143)
or direct filesystem access (RPM distros via file:///root/darksite/). After install,
the target system's repos are reconfigured to point at the upstream internet mirrors by default.
Set KLDLOAD_KEEP_DARKSITE=1 to preserve the local mirror on the installed system.
# Install extra packages (available in darksite or online repos)
KLDLOAD_EXTRA_PACKAGES="nginx postgresql-server redis"
# Air-gapped server — keep the darksite for future package installs
KLDLOAD_KEEP_DARKSITE=1
# Point at your internal Artifactory mirror post-install
KLDLOAD_CUSTOM_MIRROR_URL=https://artifactory.internal.corp/debian
KLDLOAD_KEEP_DARKSITE=0
The darksite is the secret weapon for air-gapped and compliance-constrained environments. Government networks, classified facilities, industrial control systems, ships at sea, remote mining sites — anywhere the machine cannot reach the internet. The ISO contains everything. No apt-get update that fails because the mirror is unreachable. No dnf install that times out because the satellite link is 9600 baud. The packages are right there on the USB stick, pre-resolved, pre-downloaded, checksummed. It just works.
The KLDLOAD_EXTRA_PACKAGES variable is additive. It does not replace the profile packages — it adds to them. If your profile installs nginx and you set KLDLOAD_EXTRA_PACKAGES=redis, you get both nginx and redis. The package names must match the target distro's package naming: nginx on Debian, nginx on CentOS (same in this case), but postgresql on Debian vs postgresql-server on CentOS. Know your distro's package names.
Export — golden image workflow
The export workflow converts a freshly installed system into a cloud-ready golden template.
The installer does the OS install, seals the image for cloning (clears machine-id, removes
SSH host keys, enables cloud-init), then uses qemu-img convert to produce the
image in the requested format. Optionally uploads it via SCP to a remote host.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_EXPORT_FORMAT | none | Image export format. none = normal install (no export). Other values produce a golden image file. |
KLDLOAD_EXPORT_SCP_HOST | none | Remote hostname or IP for SCP upload. If set, the image is uploaded after creation. |
KLDLOAD_EXPORT_SCP_USER | root | SSH user for SCP upload. |
KLDLOAD_EXPORT_SCP_PATH | /root/ | Remote destination path for SCP upload. |
KLDLOAD_EXPORT_SCP_KEY | none | Path to SSH private key for SCP authentication. If set, key-based auth is used. |
KLDLOAD_EXPORT_SCP_PASS | none | SSH password for SCP (if no key). Uses sshpass if available. Redacted in logs. |
KLDLOAD_EXPORT_FORMAT — output formats
Valid export formats and their targets
none — Normal install. No image export. The system is installed to the disk and boots normally.
qcow2 — QEMU Copy-On-Write v2. For KVM, Proxmox, OpenStack. Supports thin provisioning, snapshots, compression. The most common format for Linux hypervisors.
vmdk — VMware Virtual Machine Disk. For ESXi, vSphere, Workstation, Fusion. Widely supported.
vhd — Virtual Hard Disk. For Hyper-V and Azure. Microsoft ecosystem standard.
ova — Open Virtualization Archive. Portable format containing OVF metadata + VMDK. Import into VirtualBox, vSphere, or any OVF-compatible hypervisor.
raw — Raw disk image. Byte-for-byte copy. For dd to physical disks, Firecracker microVMs, or custom toolchains.
Image sealing process
Before export, k_seal_image_for_clone() prepares the system for cloning:
What gets cleared
/etc/machine-id — truncated (systemd regenerates on first boot).
/var/lib/dbus/machine-id — removed.
/etc/ssh/ssh_host_* — all host keys removed (sshd-keygen regenerates).
DHCP leases — cleared from NetworkManager, dhclient, systemd-networkd.
Cloud-init instance data — removed so cloud-init re-runs on next boot.
/root/.bash_history — deleted.
wtmp, btmp, lastlog — truncated.
Install manifest — removed (contains build-time passwords).
What gets enabled
Cloud-init services — cloud-init.service, cloud-init-local.service,
cloud-config.service, cloud-final.service all enabled.
Datasource config — accepts NoCloud, ConfigDrive, OpenStack, Azure, GCE, EC2.
The image boots, cloud-init runs, and the machine personalizes itself from whatever datasource is available.
# Export a qcow2 golden image and upload to the Proxmox host
KLDLOAD_EXPORT_FORMAT=qcow2
KLDLOAD_EXPORT_SCP_HOST=proxmox.lab.local
KLDLOAD_EXPORT_SCP_USER=root
KLDLOAD_EXPORT_SCP_PATH=/var/lib/vz/template/images/
KLDLOAD_EXPORT_SCP_KEY=/root/.ssh/id_ed25519
# Export a vmdk for VMware — no upload, retrieve manually
KLDLOAD_EXPORT_FORMAT=vmdk
# Export raw for dd to physical disks or Firecracker
KLDLOAD_EXPORT_FORMAT=raw
The golden image workflow replaces Packer for the base image. Packer is great at layering application packages onto an existing base image. But Packer cannot build the base image itself — it cannot set up ZFS on root, configure boot environments, build DKMS modules, or create a darksite. kldload does all of that, then exports the result as a qcow2/vmdk/vhd that Packer can consume as a source image. The pipeline is: kldload builds the OS layer, exports a golden image, Packer layers the application on top, Terraform deploys the result. Each tool does what it is best at.
The SCP upload is optional but extremely convenient for CI pipelines. Build the ISO, boot a VM, run the unattended install with KLDLOAD_EXPORT_FORMAT=qcow2 and KLDLOAD_EXPORT_SCP_HOST=artifact-server, and the golden image lands on your artifact server automatically. No manual retrieval. No additional scripting. The installer does the install, exports the image, uploads it, and powers off. Your CI pipeline picks up the artifact from the server and feeds it to Packer or Terraform.
Cluster and infrastructure mode
Infrastructure mode transforms a standalone machine into a node in a managed cluster. A cluster manager (control plane) coordinates multiple minion nodes via WireGuard mesh networking, Salt configuration management, and automated blue/green deployment.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_INFRA_MODE | standalone | Deployment intent. standalone = single machine. control-plane = cluster manager (hub). join = join an existing cluster as a minion. |
KLDLOAD_CLUSTER_CIDR | none | WireGuard cluster CIDR block. A /20 is split into 16 x /24 subnets (8 blue, 8 green). Example: 10.78.0.0/20. |
KLDLOAD_CLUSTER_DOMAIN | infra.local | Internal DNS domain for the cluster. Used for service discovery and inter-node resolution. |
KLDLOAD_CLUSTER_SIZE | 16 | Maximum number of nodes in the cluster. Determines the subnet allocation table. |
KLDLOAD_HUB_LAN | none | LAN IP of the cluster manager. Used by join-mode nodes to locate and connect to the hub at first boot. |
KLDLOAD_NODE_ROLE_N | minion | Role for node N in the cluster. Values: minion, k8s-control, k8s-worker, k8s-lb, storage, prometheus, grafana, custom. Node 0 is always master/hub. |
Infrastructure mode is the difference between "I installed one server" and "I deployed a cluster." A standalone install is one machine with ZFS, WireGuard, and whatever profile you selected. A control-plane install is the same machine, plus Salt master, plus cluster subnet allocation, plus blue/green deployment zones, plus a node role table. A join install is a minion that auto-discovers the hub on the LAN, exchanges WireGuard keys, and registers itself into the cluster. Boot the hub first, then boot the minions. They find each other automatically.
Distro-specific and mirror configuration
Variables that only apply to specific distributions or control package mirror behavior for the installed system.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_RELEASE | 9 | Release version for CentOS/RHEL/Rocky (e.g., 9). Determines which repo metalink is used. |
KLDLOAD_DEBIAN_RELEASE | trixie | Debian suite codename. trixie (Debian 13) or bookworm (Debian 12). |
KLDLOAD_DEBIAN_SUITE | trixie | Alias for KLDLOAD_DEBIAN_RELEASE. Either can be used. |
KLDLOAD_DEBIAN_MIRROR | https://mirror.it.ubc.ca/debian | Upstream Debian APT mirror. Used when building the darksite and for post-install repo configuration. |
KLDLOAD_MIRROR | auto-detected | APT mirror URL. Auto-detects the local darksite if running from the live ISO. Falls back to KLDLOAD_DEBIAN_MIRROR. |
KLDLOAD_SUITE | trixie | APT suite for debootstrap. Matches KLDLOAD_DEBIAN_RELEASE. |
KLDLOAD_BOOTLOADER_ID | KLDload | EFI boot entry name. Shown in the firmware boot menu and efibootmgr output. |
Build-time variables
These variables are set in kldload.env and control the ISO build process.
They run on the build machine (your laptop, CI runner, or build server) and never reach
the target system.
| Variable | Default | Description |
|---|---|---|
PROFILE | desktop | Build profile. desktop includes GNOME, Firefox, webui. server is minimal with SSH and ops tools. core is ZFS only, stock distro. |
DISTRO | centos | Target distro for the ISO. Same values as KLDLOAD_DISTRO. |
ARCH | x86_64 | Target architecture. Currently only x86_64 is fully supported. |
RELEASE | 9 | Distro release version (e.g., 9 for CentOS Stream 9). |
EDITION | free | Build edition. Always free. |
OUTPUT_DIR | live-build/output | Where the built ISO is written. |
LOG_DIR | live-build/logs | Where build logs are written. |
BUILDER_IMAGE | kldload-live-builder:latest | Docker/Podman image used for the build container. |
RHEL subscription (RHEL builds only)
| Variable | Default | Description |
|---|---|---|
RHEL_ACTIVATION_KEY | none | Red Hat activation key name. Required for RHEL builds. Used at build time only — never baked into the ISO. |
RHEL_ORG_ID | none | Red Hat organization ID. Found at console.redhat.com under your account settings. |
Proxmox deployment
| Variable | Default | Description |
|---|---|---|
PROXMOX_HOST | none | Proxmox host IP or hostname for API access. |
PROXMOX_NODE | none | Proxmox node name (as shown in the Proxmox web UI). |
PROXMOX_TOKEN_ID | none | Proxmox API token ID. Format: user@realm!tokenname (e.g., root@pam!kldload). |
PROXMOX_TOKEN_SECRET | none | Proxmox API token secret. UUID format. |
VMID | 902 | VM ID in Proxmox. Must be unique on the cluster. |
VM_NAME | kldload-free | VM display name in Proxmox. |
VM_MEMORY | 4096 | VM RAM in megabytes. |
VM_CORES | 4 | Number of CPU cores assigned to the VM. |
VM_DISK_GB | 40 | VM disk size in gigabytes. |
VM_BRIDGE | vmbr0 | Proxmox network bridge for the VM NIC. |
VM_SECURE_BOOT | no | Enable UEFI Secure Boot on the VM. Requires MOK enrollment on first boot. |
VM_TPM | yes | Enable TPM 2.0 emulation on the VM. |
USB & physical deploy
| Variable | Default | Description |
|---|---|---|
USB_DEVICE | none | USB block device for ISO burning. Must be set explicitly. Never auto-detected (safety). |
USB_BURN_ON_DEPLOY | no | Set to yes to auto-burn the ISO to USB_DEVICE after build. |
kldload.env which is
excluded from git. Never committed. Never in the ISO. Build-time only.
The security boundary is clear. Build-time credentials (kldload.env) never enter the ISO. The ISO contains packages, scripts, and darksites — not your Proxmox tokens or RHEL activation keys. Install-time credentials (passwords, SSH keys) go into the answer file on the seed disk — which you control physically. At no point does any credential touch a network, a cloud API, or a third-party service. The build machine has your build creds. The seed disk has your install creds. The ISO has neither.
Advanced and internal variables
Variables that control internal installer behavior, logging, and advanced workflows. Most users never need to set these. They exist for CI pipelines, custom integrations, and debugging.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_TARGET_MNT | /target | Mount point for the target filesystem during install. All chroot operations happen under this path. |
KLDLOAD_LOG_DIR | /var/log/installer | Directory for all installer log files. Contains kldload-installer.log, bootstrap.log, storage.log, zfs.log, security.log, network.log, bootloader.log, and effective-config.env. |
KLDLOAD_INSTALLER_VERSION | 1.0.0 | Installer version. Written to the install manifest for audit. |
KLDLOAD_TEMPLATE | empty | Named template identifier. Written to the install manifest for tracking which template was used. |
KLDLOAD_SECURE_BOOT | auto-detected | Set to 1 if Secure Boot is enabled. Auto-detected via mokutil --sb-state. Affects ZFS module loading strategy. |
KLDLOAD_TPM_PRESENT | auto-detected | Set to 1 if a TPM 2.0 device is detected at /dev/tpm0 or /dev/tpmrm0. |
Cloud provider variables (cluster mode)
When running in control-plane infrastructure mode, these optional variables
enable cloud provider integration for hybrid cluster deployments.
| Variable | Default | Description |
|---|---|---|
KLDLOAD_AWS_ACCESS_KEY_ID | none | AWS access key for hybrid cloud deployments. |
KLDLOAD_AWS_REGION | none | AWS region (e.g., us-west-2). |
KLDLOAD_AZURE_SUBSCRIPTION_ID | none | Azure subscription ID for hybrid deployments. |
KLDLOAD_GCP_PROJECT_ID | none | Google Cloud project ID for hybrid deployments. |
Complete examples
Full answers.env files for real-world scenarios. Copy, modify, deploy.
Each example shows every relevant variable for that use case — but remember, you
only need to set what differs from defaults.
Example 1: Minimal headless server (Debian, DHCP, single disk)
The simplest production install
Three lines define the essentials. Everything else uses sane defaults: DHCP networking,
admin user, UTC timezone, server profile, single-disk ZFS.
# answers.env — Minimal Debian server
KLDLOAD_DISTRO=debian
KLDLOAD_DISK=/dev/sda
KLDLOAD_HOSTNAME=web-01
KLDLOAD_USERNAME=admin
KLDLOAD_PASSWORD=changeme
KLDLOAD_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx... ops@infra"
KLDLOAD_FORCE_WIPE=1
Example 2: Encrypted desktop workstation (Ubuntu, GNOME, NVMe)
Developer workstation with full-disk encryption
Ubuntu desktop with GNOME, ZFS native encryption, local timezone, and NVIDIA GPU drivers for CUDA development. The encryption passphrase is prompted at every boot.
# answers.env — Encrypted Ubuntu desktop
KLDLOAD_DISTRO=ubuntu
KLDLOAD_PROFILE=desktop
KLDLOAD_DISK=/dev/nvme0n1
KLDLOAD_HOSTNAME=dev-workstation
KLDLOAD_USERNAME=developer
KLDLOAD_PASSWORD='$trong-Desktop-P@ss!'
KLDLOAD_TIMEZONE=America/Vancouver
KLDLOAD_LOCALE=en_US.UTF-8
KLDLOAD_KEYBOARD_LAYOUT=us
# ZFS encryption — prompted for passphrase at boot
KLDLOAD_ZFS_ENCRYPT=1
KLDLOAD_ZFS_PASSPHRASE='my-encryption-passphrase-change-me'
# GPU + containers for development
KLDLOAD_NVIDIA_DRIVERS=1
KLDLOAD_ENABLE_EBPF=1
KLDLOAD_EXTRA_PACKAGES="git build-essential python3-venv nodejs npm"
KLDLOAD_NET_METHOD=dhcp
KLDLOAD_FORCE_WIPE=1
Example 3: KVM hypervisor with mirrored ZFS (Rocky, static IP)
Production hypervisor with redundancy
Rocky Linux server with KVM, mirrored ZFS pool across two NVMe drives, static IP,
eBPF monitoring, and WireGuard management tunnel. The KLDLOAD_ENABLE_KVM=1 flag
creates the rpool/vms/images dataset with tuned recordsize and caps ARC at 50% RAM.
# answers.env — KVM hypervisor on Rocky Linux
KLDLOAD_DISTRO=rocky
KLDLOAD_PROFILE=server
KLDLOAD_DISK=/dev/nvme0n1
KLDLOAD_HOSTNAME=kvm-host-01
KLDLOAD_USERNAME=sysadmin
KLDLOAD_PASSWORD='Hyp3rv1sor-Adm!n-2024'
KLDLOAD_TIMEZONE=UTC
# Mirrored ZFS across two NVMe drives
KLDLOAD_ZFS_TOPOLOGY=mirror
KLDLOAD_ZFS_DATA_DISKS=/dev/nvme1n1
# Static IP on the management network
KLDLOAD_NET_METHOD=static
KLDLOAD_NET_IFACE=eno1
KLDLOAD_NET_IP=10.0.1.10
KLDLOAD_NET_PREFIX=24
KLDLOAD_NET_GW=10.0.1.1
KLDLOAD_NET_DNS=10.0.1.1,1.1.1.1
# Enable KVM + eBPF + WireGuard
KLDLOAD_ENABLE_KVM=1
KLDLOAD_ENABLE_EBPF=1
KLDLOAD_WIREGUARD=1
# SSH key for automation
KLDLOAD_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICz... terraform@ci"
KLDLOAD_ADMIN_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFy... break-glass@vault"
KLDLOAD_FORCE_WIPE=1
Example 4: Golden image export for Packer pipeline (CentOS, qcow2)
Automated golden image factory
CentOS server installed to a VM disk, sealed for cloning (machine-id cleared, SSH keys removed, cloud-init enabled), exported as qcow2, and uploaded to the artifact server via SCP. The Packer pipeline picks up the artifact and layers application packages on top.
# answers.env — Golden image for CI pipeline
KLDLOAD_DISTRO=centos
KLDLOAD_PROFILE=server
KLDLOAD_DISK=/dev/vda
KLDLOAD_HOSTNAME=golden-centos-base
KLDLOAD_USERNAME=packer
KLDLOAD_PASSWORD='packer-build-temporary'
KLDLOAD_TIMEZONE=UTC
# Export as qcow2 and upload to artifact server
KLDLOAD_EXPORT_FORMAT=qcow2
KLDLOAD_EXPORT_SCP_HOST=artifacts.internal.corp
KLDLOAD_EXPORT_SCP_USER=deploy
KLDLOAD_EXPORT_SCP_PATH=/var/artifacts/images/
KLDLOAD_EXPORT_SCP_KEY=/root/.ssh/deploy_key
# DHCP — the VM gets a temporary IP during build
KLDLOAD_NET_METHOD=dhcp
KLDLOAD_FORCE_WIPE=1
# The image is sealed automatically:
# - machine-id cleared
# - SSH host keys removed
# - cloud-init enabled (NoCloud, ConfigDrive, OpenStack, Azure, GCE, EC2)
# - install manifest removed (no passwords on disk)
Example 5: Air-gapped CI runner fleet (Debian, RAIDZ1, darksite)
Classified network CI runner
Debian server on a classified network with no internet access. Three-disk RAIDZ1 for data protection. Darksite preserved on the installed system so the CI runner can install additional packages offline. Custom internal mirror for future updates when the mirror is available on the secure network.
# answers.env — Air-gapped CI runner
KLDLOAD_DISTRO=debian
KLDLOAD_PROFILE=server
KLDLOAD_DISK=/dev/sda
KLDLOAD_HOSTNAME=ci-runner-01
KLDLOAD_USERNAME=ci
KLDLOAD_PASSWORD='c1-Runn3r-@ir-g@pped'
KLDLOAD_TIMEZONE=UTC
# RAIDZ1 across three disks — one parity drive
KLDLOAD_ZFS_TOPOLOGY=raidz1
KLDLOAD_ZFS_DATA_DISKS="/dev/sdb /dev/sdc"
# Static IP on the isolated network
KLDLOAD_NET_METHOD=static
KLDLOAD_NET_IFACE=ens18
KLDLOAD_NET_IP=192.168.100.50
KLDLOAD_NET_PREFIX=24
KLDLOAD_NET_GW=192.168.100.1
KLDLOAD_NET_DNS=192.168.100.1
# Keep the darksite — no internet, need offline packages
KLDLOAD_KEEP_DARKSITE=1
KLDLOAD_CUSTOM_MIRROR_URL=http://mirror.secure.internal/debian
# Extra CI tooling
KLDLOAD_EXTRA_PACKAGES="git make gcc podman buildah skopeo jq"
# SSH key for the CI orchestrator
KLDLOAD_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDz... jenkins@ci-master"
KLDLOAD_FORCE_WIPE=1
Example 6: Cluster manager with blue/green deployment zones
Infrastructure control plane
CentOS control-plane node managing a 16-node cluster over WireGuard mesh. Four network planes (management, data, storage, external). Blue/green deployment zones for zero-downtime upgrades. The cluster CIDR is a /20 split into 16 x /24 subnets.
# answers.env — Cluster manager (hub)
KLDLOAD_DISTRO=centos
KLDLOAD_PROFILE=server
KLDLOAD_DISK=/dev/nvme0n1
KLDLOAD_HOSTNAME=hub-01
KLDLOAD_USERNAME=ops
KLDLOAD_PASSWORD='Hub-M@ster-Key-2024'
KLDLOAD_TIMEZONE=UTC
# Infrastructure mode — this is the control plane
KLDLOAD_INFRA_MODE=control-plane
KLDLOAD_CLUSTER_CIDR=10.78.0.0/20
KLDLOAD_CLUSTER_DOMAIN=prod.infra.local
KLDLOAD_CLUSTER_SIZE=16
# Mirrored NVMe for the hub
KLDLOAD_ZFS_TOPOLOGY=mirror
KLDLOAD_ZFS_DATA_DISKS=/dev/nvme1n1
# Static IP — the hub must be reachable
KLDLOAD_NET_METHOD=static
KLDLOAD_NET_IFACE=eno1
KLDLOAD_NET_IP=10.0.1.5
KLDLOAD_NET_PREFIX=24
KLDLOAD_NET_GW=10.0.1.1
KLDLOAD_NET_DNS=10.0.1.1
# All four WireGuard planes
KLDLOAD_WIREGUARD=1
KLDLOAD_WG0_PORT=51820
KLDLOAD_WG1_PORT=51821
KLDLOAD_WG2_PORT=51822
KLDLOAD_WG3_PORT=51823
# Enable KVM + eBPF for the hub
KLDLOAD_ENABLE_KVM=1
KLDLOAD_ENABLE_EBPF=1
KLDLOAD_FORCE_WIPE=1
Example 7: Fedora desktop with Arch-style minimalism
# answers.env — Fedora desktop, single NVMe, encrypted
KLDLOAD_DISTRO=fedora
KLDLOAD_PROFILE=desktop
KLDLOAD_DISK=/dev/nvme0n1
KLDLOAD_HOSTNAME=fedora-desktop
KLDLOAD_USERNAME=todd
KLDLOAD_PASSWORD='f3d0r@-D3skt0p'
KLDLOAD_TIMEZONE=America/Vancouver
KLDLOAD_LOCALE=en_US.UTF-8
KLDLOAD_KEYBOARD_LAYOUT=us
KLDLOAD_ZFS_ENCRYPT=1
KLDLOAD_ZFS_PASSPHRASE='boot-passphrase-for-nvme'
KLDLOAD_NET_METHOD=dhcp
KLDLOAD_FORCE_WIPE=1
These examples are not theoretical. They are the actual answers files from real deployments. The minimal server is three production web servers behind a load balancer. The encrypted desktop is my daily-driver laptop. The KVM hypervisor runs 40 VMs on mirrored NVMe. The golden image pipeline produces weekly base images consumed by Terraform. The air-gapped CI runner lives on a network that has never seen the internet. The cluster manager coordinates 16 nodes across two racks.
The pattern is always the same: decide what you need, set the variables, plug in the USB, boot, walk away. The machine configures itself. Whether it is one machine or fifty, the process is identical. The answer file is the only thing that changes.
Complete variable reference table
Every install-time variable in one table. Sorted by category. This is the authoritative reference — if a variable exists, it is listed here.
| Variable | Type | Default | Description |
|---|---|---|---|
| Core | |||
KLDLOAD_DISTRO | enum | centos | Target distro: centos, debian, ubuntu, fedora, rhel, rocky, arch, alpine, freebsd, openbsd |
KLDLOAD_PROFILE | enum | server | Install profile: server, desktop, core, ai, client |
KLDLOAD_HOSTNAME | string | kldload-node | System hostname (letters, digits, hyphens) |
KLDLOAD_TIMEZONE | string | UTC | TZ database timezone name |
KLDLOAD_LOCALE | string | en_US.UTF-8 | System locale |
KLDLOAD_KEYBOARD_LAYOUT | string | us | XKB keyboard layout |
KLDLOAD_KEYBOARD_VARIANT | string | empty | XKB keyboard variant (optional) |
| Disk & Storage | |||
KLDLOAD_DISK | path | auto | Target block device (e.g., /dev/sda, /dev/nvme0n1) |
KLDLOAD_STORAGE_MODE | enum | zfs | Storage mode (always zfs) |
KLDLOAD_ZFS_TOPOLOGY | enum | single | Pool layout: single, mirror, raidz1, mirror-stripe |
KLDLOAD_ZFS_DATA_DISKS | string | empty | Additional disks (space-separated paths) |
KLDLOAD_ZFS_SPECIAL_DISKS | string | empty | NVMe metadata vdev disks (space-separated) |
KLDLOAD_ZFS_ENCRYPT | bool | 0 | Enable ZFS native encryption (AES-256-GCM) |
KLDLOAD_ZFS_PASSPHRASE | string | prompted | Encryption passphrase (redacted in logs) |
KLDLOAD_ENABLE_ZFS | bool | 1 | Always 1 (ZFS is non-optional) |
KLDLOAD_FORCE_WIPE | bool | 0 | Skip disk wipe confirmation |
| Networking | |||
KLDLOAD_NET_METHOD | enum | dhcp | Network method: dhcp or static |
KLDLOAD_NET_IFACE | string | auto | Network interface name |
KLDLOAD_NET_IP | string | none | Static IP address |
KLDLOAD_NET_PREFIX | int | 24 | CIDR prefix length |
KLDLOAD_NET_GW | string | none | Default gateway |
KLDLOAD_NET_DNS | string | none | DNS servers (comma-separated) |
| User & Auth | |||
KLDLOAD_USERNAME | string | admin | Non-root admin user |
KLDLOAD_PASSWORD | string | prompted | Admin password (redacted in logs) |
KLDLOAD_ROOT_PASSWORD | string | none | Root password (if unset, root is locked) |
KLDLOAD_SSH_PUBKEY | string | none | SSH public key for admin user |
KLDLOAD_ADMIN_SSH_PUBKEY | string | none | SSH public key for root (emergency access) |
KLDLOAD_GENERATE_SSH_KEY | bool | 1 | Generate SSH host keys during install |
| WireGuard | |||
KLDLOAD_WIREGUARD | bool | 0 | Configure WireGuard interface |
KLDLOAD_ENABLE_WIREGUARD | bool | 1 | Include WireGuard module and tools |
KLDLOAD_WG0_PORT | int | 51820 | Management plane listen port |
KLDLOAD_WG1_PORT | int | 51821 | Data plane listen port |
KLDLOAD_WG2_PORT | int | 51822 | Storage plane listen port |
KLDLOAD_WG3_PORT | int | 51823 | External plane listen port |
KLDLOAD_WIREGUARD_PRIVATE_KEY | string | generated | WireGuard private key (redacted in logs) |
KLDLOAD_WIREGUARD_PRESHARED_KEY | string | none | Preshared key for post-quantum security (redacted) |
| Feature Toggles | |||
KLDLOAD_ENABLE_KVM | bool | 0 | Install KVM/QEMU/libvirt |
KLDLOAD_ENABLE_EBPF | bool | 0 | Install eBPF toolchain |
KLDLOAD_ENABLE_AI | bool | 0 | Install AI/ML stack |
KLDLOAD_NVIDIA_DRIVERS | bool | 0 | Install NVIDIA GPU drivers |
| Packages & Mirrors | |||
KLDLOAD_EXTRA_PACKAGES | string | empty | Additional packages (space/comma separated) |
KLDLOAD_KEEP_DARKSITE | bool | 0 | Preserve darksite on installed system |
KLDLOAD_CUSTOM_MIRROR_URL | string | none | Custom package mirror URL |
| Export (Golden Image) | |||
KLDLOAD_EXPORT_FORMAT | enum | none | Export format: none, qcow2, vmdk, vhd, ova, raw |
KLDLOAD_EXPORT_SCP_HOST | string | none | Remote host for SCP upload |
KLDLOAD_EXPORT_SCP_USER | string | root | SCP user |
KLDLOAD_EXPORT_SCP_PATH | string | /root/ | SCP destination path |
KLDLOAD_EXPORT_SCP_KEY | path | none | SSH private key for SCP |
KLDLOAD_EXPORT_SCP_PASS | string | none | SSH password for SCP (redacted) |
| Cluster & Infrastructure | |||
KLDLOAD_INFRA_MODE | enum | standalone | Deploy intent: standalone, control-plane, join |
KLDLOAD_CLUSTER_CIDR | string | none | Cluster WireGuard CIDR (e.g., 10.78.0.0/20) |
KLDLOAD_CLUSTER_DOMAIN | string | infra.local | Cluster DNS domain |
KLDLOAD_CLUSTER_SIZE | int | 16 | Maximum cluster nodes |
KLDLOAD_HUB_LAN | string | none | Hub LAN IP (for join mode) |
| Distro-Specific | |||
KLDLOAD_RELEASE | string | 9 | CentOS/RHEL/Rocky release version |
KLDLOAD_DEBIAN_RELEASE | string | trixie | Debian suite codename |
KLDLOAD_DEBIAN_SUITE | string | trixie | Alias for KLDLOAD_DEBIAN_RELEASE |
KLDLOAD_DEBIAN_MIRROR | string | https://mirror.it.ubc.ca/debian | Debian APT mirror |
KLDLOAD_MIRROR | string | auto | APT mirror (auto-detects darksite) |
KLDLOAD_SUITE | string | trixie | APT suite for debootstrap |
KLDLOAD_BOOTLOADER_ID | string | KLDload | EFI boot entry name |
| Advanced & Internal | |||
KLDLOAD_TARGET_MNT | path | /target | Target filesystem mount point |
KLDLOAD_LOG_DIR | path | /var/log/installer | Installer log directory |
KLDLOAD_INSTALLER_VERSION | string | 1.0.0 | Installer version (audit) |
KLDLOAD_TEMPLATE | string | empty | Template identifier (audit) |
KLDLOAD_SECURE_BOOT | bool | auto | Secure Boot detected |
KLDLOAD_TPM_PRESENT | bool | auto | TPM 2.0 detected |
That is the entire API surface for kldload installation. Roughly 70 variables across 10 categories. Most installs use fewer than 15. The minimal install uses 3 (distro, disk, hostname). Everything else has a sane default. The table above is the single source of truth — if it is not in this table, the installer does not read it. If it is in this table, it does exactly what the description says. No hidden behavior. No undocumented side effects. No magic.
The variable naming convention is deliberate and consistent. KLDLOAD_ prefix for all install-time variables. KLDLOAD_NET_ for networking. KLDLOAD_ZFS_ for ZFS. KLDLOAD_EXPORT_ for golden image export. KLDLOAD_WGN_ for WireGuard planes. KLDLOAD_ENABLE_ for feature toggles. KLDLOAD_CLUSTER_ for infrastructure mode. The prefix tells you the category. The suffix tells you the specific setting. You can grep for KLDLOAD_NET_ and find every networking variable. You can grep for KLDLOAD_EXPORT_ and find every export variable. Discoverability by convention.