Overview
Bootstrap provisions a target before the regular deploy workflow continues: it converts a bare-metal or existing system into a freshly installed NixOS machine without physical access or installation media, or it installs Nix itself so profile deployments can run on targets that lack it.
Two Bootstrap Modes
Section titled “Two Bootstrap Modes”The Bootstrap phase runs one of two modes, picked by the installable type’s bootstrap mode (built-in types infer it, custom types declare bootstrap_mode, see Output Types):
| Mode | Types | What runs |
|---|---|---|
| NixOS bootstrap | nixosConfigurations, custom types with bootstrap_mode: nixos | kexec into a NixOS installer (if needed), disko partitioning, nixos-install, reboot |
| Nix installer bootstrap | systemConfigs, homeConfigurations, darwinConfigurations, nixOnDroidConfigurations, packages, maidConfigurations, custom types with bootstrap_mode: nix-install | fetch and run the Nix installer when the target lacks nix, before Build, Transfer, Secrets, and Activate |
Custom output types are not bootstrapped by default: their targets must already have Nix. They opt in by declaring bootstrap_mode in their output_types declaration (see Output Types).
NixOS Bootstrap
Section titled “NixOS Bootstrap”The Bootstrap Flow
Section titled “The Bootstrap Flow”- Inspect phase detects the target OS; if not NixOS, kexec boots into a NixOS installer image
- Disko partitions disks according to your configuration
- Transfer the system closure
- nixos-install lays down the system
- Reboot into your new NixOS system
Disk encryption keys are transferred before disko runs, and hooks can run at several points in between:
Fragment: nest under fleet.flakes.<flake>.installables.<type>.<name>.machines.
machines: bare-metal: ssh: hostname: 192.168.1.100 bootstrap: disk_encryption_keys: # Transferred BEFORE disko runs - local_path: ./secrets/luks.key remote_path: /tmp/luks-key post_bootstrap_hooks: # Run after disko partitioning - systemd-cryptenroll --tpm2-device=auto /dev/nvme0n1p2See Bootstrap SSH for SSH configuration, Kexec for custom images and local tarballs, Bootstrap Hooks for hook points, and Force Reinstall for redoing already running systems.
Hardware Config
Section titled “Hardware Config”Once connected to the target, retrieve the hardware configuration for your NixOS config:
nixos-generate-config --no-filesystems --show-hardware-configOn non-NixOS systems:
nix-shell -p nixos-install-tools --command "nixos-generate-config --no-filesystems --show-hardware-config"Panix can automate this: set hardware_config_path on the machine and Panix runs the same nixos-generate-config --show-hardware-config --no-filesystems command when it finds the machine in the NixOS installer, reads the config it prints, and writes it to the local path on the machine running Panix. On a non-NixOS machine that still needs kexec, the command cannot run before kexec (nixos-generate-config does not exist on the original OS), so Bootstrap generates the config right after kexec into the installer and before disko, which evaluates the configuration and may import the file. A single bootstrap therefore produces the file before it is needed (see Configuration Reference). Keep the path inside your flake, at the path your configuration imports: the flake is evaluated in pure mode, so it cannot read a file outside its own source.
After writing the file, Panix marks it for addition (git add -N, forced when gitignored) so the build that follows already has it: Nix snapshots a git-backed flake from the git tree, so an untracked file would be missing.
Encryption Password
Section titled “Encryption Password”If your bootstrap uses disk encryption, create the password file:
# `-n` prevents adding newline (problematic for password enrollment)echo -n "test" > /tmp/disko-encryption-password.txtWhen specified as a disk encryption key in config, this file is transferred to the target during bootstrap, before disko runs.
Nix Installer Bootstrap
Section titled “Nix Installer Bootstrap”Every non-NixOS built-in type, and every custom type with bootstrap_mode: nix-install, is bootstrappable via a Nix installer step: when the target lacks nix, the Bootstrap phase installs it before Build, Transfer, Secrets, and Activate run. This covers systemConfigs (system-manager), homeConfigurations, darwinConfigurations, nixOnDroidConfigurations, packages, and maidConfigurations targets, including Debian, Ubuntu, or macOS machines that were never Nix machines.
The flow:
- Inspect probes
nixon the target; a missing Nix is expected and tolerated (unlessdisable_nix_installis set, see below) - Bootstrap fetches the installer to the target (with
curlfor URLs, withrsyncfor local paths) and marks it executable, runs it elevated with the configured args (a shell script runs via its shebang, an installer binary runs directly), verifiesnixworks, then runspost_bootstrap_hooks - Build, Transfer, Secrets, and Activate proceed as usual, now against a target with Nix
The step is zero-config by default: the installer from https://install.determinate.systems/nix (the Determinate nix-installer) runs with install --no-confirm, the canonical unattended invocation. The platform planner is auto-detected (linux, macos), so the same config works on both. On a systemd Debian or Ubuntu server this performs the default multi-user daemon install (nix-daemon). The installer writes the store and profiles system-wide, so it runs elevated: via the configured sudo_program, or bare when the SSH user is already root. Root or passwordless sudo is required.
Configuration
Section titled “Configuration”Fragment: nest under fleet.flakes.<flake>.installables.<type>.<name>.machines. Like all bootstrap settings, nix and disable_nix_install inherit down from fleet, flake, or installable level. Both apply to the Nix installer bootstrap only; for the NixOS bootstrap (nixosConfigurations, custom types with bootstrap_mode: nixos) they have no effect.
bootstrap: nix: url: https://install.determinate.systems/nix # default; shell script or installer binary, https URL or local path args: ["install", "--no-confirm"] # default; full replacement of the installer args curl_default_flags: ["--fail", "-#", "-L"] # default; base curl flags for the download disable_nix_install: false # default; opt out of the automatic installnix.url: installer source, a shell script or thenix-installerbinary. An https URL is downloaded on the target withcurl; a local path is transferred withrsync(see Requirements). Any other URL scheme is rejected.nix.args: the argument list passed to the installer after its path. It is a full replacement of the default["install", "--no-confirm"], not an append, so keep the subcommand and flags you want (for exampleinstall --no-confirm) in the value. An explicitly empty list ([]) runs the installer with no arguments.nix.curl_default_flags: base flags forcurlwhennix.urlis downloaded on the target. Default["--fail", "-#", "-L"]; set to[]to clear them. Same semantics asbootstrap.kexec.curl_default_flags. Resume flags (-C,-C-,--continue-at) are always stripped from bootstrap artifact downloads, even when added back here: each run downloads into a fresh, private per-run staging path, and the root-executed artifact must never resume bytes left by an earlier run.disable_nix_install: trueopts out of the automatic install. A target without Nix then fails at Inspect with thenix not found on remote machineerror, which mentions the opt-out. Install Nix manually, or unset the flag.
For machines without internet access, stage the installer binary locally and point nix.url at its path; Panix transfers it with rsync and runs it directly. The nix-installer binary is self-contained (it bundles the Nix it installs), so nothing is downloaded on the target.
Custom output types become bootstrappable at the type level: declare bootstrap_mode under output_types, nix-install for this Nix installer bootstrap or nixos for the full NixOS bootstrap (which requires system_level: true and build_path: config.system.build.toplevel: the output must be a standard NixOS system evaluation, since nixos-install installs the built system closure and disko resolves config.system.build.diskoScript under the same flake output); omitted means no bootstrap. Per-installable preset: values are ignored (see Output Types).
Planner Arguments
Section titled “Planner Arguments”Panix invokes the installer with install --no-confirm, the canonical unattended invocation; planner subcommands and planner-specific flags are documented in the nix-installer docs.
Pinning and Idempotence
Section titled “Pinning and Idempotence”- Pin the installer for reproducible provisioning with a versioned installer URL, e.g.
url: https://install.determinate.systems/nix/tag/v3.23.0. - Re-running the installer with settings matching
/nix/receipt.jsonon the target succeeds (exit 0); changed settings require an uninstall first.
Forcing a Re-run
Section titled “Forcing a Re-run”force_bootstrap: true re-runs the installer even when Nix is already present. Like the NixOS bootstrap it requires allow_destructive_actions: true:
Fragment: nest under fleet.flakes.<flake>.installables.<type>.<name>.machines.
machines: server: bootstrap: allow_destructive_actions: true # Required for force_bootstrap force_bootstrap: true # Re-run the Nix installer even when Nix is presentSee Force Reinstall for the NixOS counterpart and the full flag set.
Target Requirements
Section titled “Target Requirements”Installing Nix on a target needs network access to the installer URL (https://install.determinate.systems/nix by default), curl on the target (rsync instead when nix.url is a local path), and root or passwordless sudo. See Requirements.