nixos-anywhere bootstrap¶
Overview¶
This walkthrough wires a remote wipe-and-install from your laptop: a flake with nixosConfigurations, a disko disk layout, and one command that SSHs to bare metal or a VPS, kexecs into a NixOS installer, partitions, installs, and reboots. It is a file-layout and first-install story—not day-2 deploy. After the machine runs NixOS, use nixos-rebuild --target-host or fleet tools instead of re-running nixos-anywhere.
Pins such as nixos-26.05 and x86_64-linux are illustrative. Replace the disk device path and SSH authorized keys before any real wipe. Flake nix run needs experimental nix-command and flakes on the source machine.
Details¶
What you get¶
One repository directory with a flake, a host module, and a disko layout. The nixosConfigurations output name (myhost in the snippets below) is what you pass after # to nixos-anywhere and, later, to nixos-rebuild switch --flake. Evaluating that output produces a closed system generation that nixos-anywhere copies to the remote disk during the install phase.
Domains composed¶
This example pulls together teaching pages from several domains:
- nixos-anywhere — SSH → kexec → disko → install → reboot; flags and requirements
- disko — declarative partitioning consumed by the installer's disko phase
- NixOS configurations in flakes —
nixosSystem, modules, and the#namefragment nix-commandandflakes— experimental features required on the source machine fornix run- Install and bootstrap — when to choose anywhere vs ISO vs day-2 paths
- Remote deploy — post-install config changes (
nixos-rebuild --target-host) - Fleet deploy — when one host becomes many (colmena, deploy-rs, …)
Optional deepen: combine disko with impermanence for ephemeral root and persistent /persist—see the worked Disko + impermanence host (same flake shape; different disko.devices and persist list).
Default install flow¶
When --phases is unset, nixos-anywhere runs kexec → disko → install → reboot:
- kexec — If the target is not already a NixOS installer, load a bundled kexec tarball and boot into one.
- disko — Unmount and destroy target filesystems per the disko config, then create and mount partitions.
- install — Build (or upload) the flake's
nixosConfigurations.<name>closure and activate it on the target. - reboot — Unmount, export ZFS pools if any, reboot into the installed system.
Skip or reorder with --phases (comma-separated). The default disko phase is destructive—treat a full run as a wipe of disks named in your layout.
What you need¶
| Side | Requirement |
|---|---|
| Source | Linux, macOS, NixOS, or WSL2 with Nix; flakes + nix-command. You do not install nixos-anywhere locally—use nix run github:nix-community/nixos-anywhere. |
| Flake | nixosConfigurations.<name> that imports disko's NixOS module and declares disko.devices. |
| Target | SSH reachability (root, or a user with passwordless sudo). x86_64 or aarch64 Linux with kexec for the default path. |
| kexec image | Default bundled image is x86_64-only. aarch64 (and custom networking/VPN/Wi‑Fi) need --kexec with a matching tarball. |
| RAM | ≥ ~1.5 GB free RAM (excluding swap) when using kexec. |
| Network | Wired/public/local reachability assumptions; exotic networking may need a custom --kexec installer. |
File layout¶
.
├── flake.nix
├── flake.lock # after nix flake lock
├── hosts/
│ └── myhost/
│ ├── default.nix # host policy + disko import
│ └── disko.nix # ESP + root on one disk (by-id)
On a real install, replace the illustrative /dev/disk/by-id/… in disko.nix with the target disk from lsblk -o NAME,SIZE,MODEL,SERIAL. Optionally pass --generate-hardware-config so nixos-anywhere writes hardware-configuration.nix (or facter.json) to a local path in your flake during install—import that file from the host module before bare-metal runs that need detected kernel modules.
Annotated pieces¶
flake.nix — pin nixpkgs and disko, expose one nixosConfigurations entry:
{
description = "Remote bootstrap via nixos-anywhere";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
inputs.disko.url = "github:nix-community/disko/latest";
inputs.disko.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self, nixpkgs, disko, ... }@inputs: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
disko.nixosModules.disko
./hosts/myhost/default.nix
];
};
};
}
See NixOS configurations in flakes for specialArgs, multiple hosts, and dropping the legacy top-level system argument once nixpkgs.hostPlatform is set in the host module.
hosts/myhost/disko.nix — minimal GPT: EFI System Partition + ext4 root on one disk:
{
disko.devices = {
disk.main = {
type = "disk";
# Replace with the target disk — confirm with lsblk before install
device = "/dev/disk/by-id/nvme-VENDOR_MODEL_SERIAL";
content = {
type = "gpt";
partitions = {
boot = {
name = "ESP";
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
name = "root";
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
}
hosts/myhost/default.nix — host policy; imports disko layout. Enable SSH and put your public key in place before install, or you will lock yourself out after reboot:
{ config, pkgs, ... }: {
imports = [ ./disko.nix ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "myhost";
networking.networkmanager.enable = true;
services.openssh.enable = true;
users.users.alice = {
isNormalUser = true;
extraGroups = [ "wheel" ];
# Replace with your real public key string (no private keys in the repo).
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAA…REPLACE_ME alice@laptop"
];
};
# Set once at install to the release you started on; do not bump casually.
system.stateVersion = "26.05";
}
disko generates fileSystems from disko.devices—you normally omit a hand-written root fileSystems."/" block. Bootloader options still belong in the NixOS module (partitioning and bootloaders).
Install / verify¶
Lock and dry-run the flake and disko layout in a VM before touching remote disks:
# nix.conf or --extra-experimental-features 'nix-command flakes'
nix flake lock
nix run github:nix-community/nixos-anywhere -- --flake .#myhost --vm-test
Install onto a reachable SSH target (destructive on disks named in disko.nix):
Useful variants from the nixos-anywhere reference:
| Flag | Role |
|---|---|
--build-on auto\|local\|remote |
Where to build the closure (default auto) |
--kexec <path> |
Custom kexec tarball (aarch64, VPN, …) |
--phases kexec,disko,install |
Stop before reboot |
--generate-hardware-config nixos-generate-config ./hosts/myhost/hardware-configuration.nix |
Write hardware facts to a local flake path during install |
--no-disko-deps |
Upload only the disko script (less RAM on tight targets) |
--env-password |
Use SSHPASS for password-based ssh-copy-id |
After reboot, SSH host keys usually change—remove stale entries (ssh-keygen -R 203.0.113.10). Day-2 updates use the same flake with remote rebuild, not nixos-anywhere:
See remote deploy and fleet deploy when the fleet grows beyond one-off SSH.
Failure modes¶
| Symptom | Likely cause | What to check |
|---|---|---|
SSH login or ssh-copy-id fails |
Wrong user/key; non-root without passwordless sudo; password auth without SSHPASS + --env-password |
Reach the host manually; use -i or set SSHPASS |
| Cannot SSH after reboot | services.openssh off, or no authorizedKeys / root key in the installed config |
Put keys in the flake before install (see host module above) |
Failure unpacking initrd / kexec dies |
Under ~1.5 GB free RAM (excluding swap) | Add RAM; skip kexec if already in a NixOS installer; try --no-disko-deps |
| Default kexec image only supports x86_64 | aarch64 target without custom image | Pass --kexec with a matching tarball (e.g. from nixos-images) |
| Wrong disk wiped or empty layout | device in disko.nix points at the wrong drive |
Prefer /dev/disk/by-id/…; confirm with lsblk; run --vm-test first |
REMOTE HOST IDENTIFICATION HAS CHANGED |
Fresh install replaced SSH host keys | ssh-keygen -R <host> before reconnecting |
| Re-running nixos-anywhere for config tweaks | Tool is for install, not day-2 deploy | Use nixos-rebuild --target-host or fleet tools (remote deploy) |
| Flake attribute missing | #name does not match nixosConfigurations key |
Align myhost in flake, folder convention, and CLI fragment |
experimental Nix feature 'flakes' is disabled |
Source machine missing experimental features | Enable flakes and nix-command |
Examples¶
End-to-end picture: files from File layout and Annotated pieces, then validate and install:
nix flake lock
nix run github:nix-community/nixos-anywhere -- --flake .#myhost --vm-test
nix run github:nix-community/nixos-anywhere -- \
--flake .#myhost \
--target-host root@203.0.113.10
Match networking.hostName, the nixosConfigurations key, and the # suffix (myhost here). After the host is up, day-2:
aarch64 target (custom kexec required; build needs native aarch64, a remote builder, or boot.binfmt.emulatedSystems):
nix run github:nix-community/nixos-anywhere -- \
--kexec "$(nix build --print-out-paths github:nix-community/nixos-images#packages.aarch64-linux.kexec-installer-nixos-unstable-noninteractive)/nixos-kexec-installer-noninteractive-aarch64-linux.tar.gz" \
--flake .#myhost \
--target-host root@203.0.113.10
References¶
- nixos-anywhere (GitHub)
- nixos-anywhere documentation
- nixos-anywhere Quickstart
- nixos-anywhere Reference
- Using your own kexec image
- disko (GitHub)
See also¶
- nixos-anywhere — flags, phases, requirements
- disko — declarative disks and module import
- Install and bootstrap — path chooser (ISO vs anywhere vs day-2)
- Remote deploy — post-install
nixos-rebuild --target-host - Minimal flake NixOS host — local-first single-host layout without remote install
- Disko + impermanence host — ephemeral root + persist on a disko flake