Setup a New Machine: Deployment Walkthrough
Duration: 30–90 minutes (target machine interaction + remote deployment)
This phase boots the target machine to a live environment and runs nixos-anywhere to deploy the NixOS system. Steps alternate between the target host (in live environment) and admin host (your development machine).
Prerequisites
You must have completed Setup New Machine - Pre-Deployment:
- ✅ NixOS configuration created
- ✅ SSH keypairs generated and staged
- ✅ Age keys authorized in
.sops.yaml - ✅ Secrets encrypted
- ✅ Flake builds successfully
- ✅ Deployment credentials prepared
Overview
- Part A: Boot target to live environment, perform minimal setup (~5–10 minutes)
- Part B: Run
nixos-anywherefrom admin host (~20–60 minutes) - Part C: Verify system booted successfully
- Part D: Clean up temporary files from admin host
Part A: Prepare Target Machine (Live Environment)
A.1 Boot NixOS Minimal Image
Prepare a bootable USB stick with the NixOS minimal image:
- Download from https://nixos.org/download/
- Write to USB using Etcher, dd, or Ventoy
- Boot the target machine from the USB stick
Wait for the minimal ISO to fully boot (you'll see a login prompt or shell).
A.2 (Optional) Set Keyboard Layout
If your keyboard is not QWERTY, change the layout. For example, Belgian AZERTY:
sudo loadkeys /etc/kbd/keymaps/i386/azerty/be-latin1.map.gz
Adjust the path based on your keyboard layout. Common layouts:
- Belgian: i386/azerty/be-latin1.map.gz
- French: i386/azerty/fr-latin1.map.gz
- German: i386/qwertz/de.map.gz
A.3 (Optional) Connect to WiFi
If you need WiFi (not needed if connected via Ethernet):
nmtui
Follow the on-screen menu to scan and connect to your network.
A.4 Set Temporary Root Password
Used only by nixos-anywhere to log in. Retrieve the password you generated during pre-deployment:
sudo passwd
# Enter the password from /tmp/${HOSTNAME}-root-password.txt on admin host
# Confirm it
Important: This password only exists during the live environment and is discarded after reboot.
A.5 Get Target IP Address
Determine the target's IP address so the admin host can connect via SSH:
For Ethernet (DHCP):
ip addr show
# Look for inet address on eth0 or ens* interface, e.g., 192.168.1.50
For VPS/Cloud targets: Use the IP provided by your cloud provider instead.
For local networks if DHCP fails: Configure a static IP:
ip addr add 192.168.1.100/24 dev eth0
ip route add default via 192.168.1.1
Note the IP address (e.g., 192.168.1.50); you'll need it for the next part.
A.6 Verify Disk Layout
Ensure the disks available match your disko configuration:
lsblk
Example output:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 500G 0 disk
sdb 8:16 0 2T 0 disk
nvme0n1 259:0 0 1.8T 0 disk
Important: Verify that: - The correct disk(s) are present (will be formatted and all data erased) - The number and size of disks match your disko config - You're formatting the right disks (double-check if you have multiple targets around)
If the layout doesn't match your configuration, stop here and adjust your disko config on the admin host before continuing.
A.7 Mount USB Key for LUKS (if using USB-based encryption)
If your disko configuration uses a USB keyfile for LUKS, mount it now so disko can find it during installation.
Find the USB device:
lsblk -d -o NAME,SIZE,SERIAL
# Look for your USB device, e.g., sdc (check the size to identify it)
Also get the USB device's filesystem UUID (you noted this during pre-deployment):
blkid /dev/sdX1
# Look for the UUID in the output, e.g., UUID="8B34-7D3C"
Mount the USB to /key/:
sudo mkdir -m 0755 -p /key
sudo mount -n -t vfat -o ro -U 8B34-7D3C /key
# Replace 8B34-7D3C with your actual USB UUID
Verify the keyfile is there:
ls -la /key/
# Should show your keyfile, e.g., hypervisor or hypervisor.key
If your disko config expects a specific filename at /key/${HOSTNAME}, ensure the file is named correctly. If it's named differently (e.g., hypervisor.key), you can rename it:
mv /key/hypervisor.key /key/hypervisor
A.8 Ready for Deployment
You now have:
- ✅ Live environment running
- ✅ Root password set
- ✅ IP address known
- ✅ USB keyfile mounted at /key/ (if using LUKS)
- ✅ Disks verified
All remaining steps happen on the admin host. You can now leave the target machine idle; it will reboot automatically during deployment.
Part B: Run nixos-anywhere (From Admin Host)
B.1 Verify Admin Host Prerequisites
On your admin host, ensure:
# You're in the devshell
nix flake --version # Should succeed
age --version # Should be available
ssh -V # Should be available
# Verify your staging directory exists
ls -la /tmp/${HOSTNAME}-deploy/
# Should show ssh keys and other staged files
B.2 Construct the nixos-anywhere Command
Build the command step-by-step. Start with the base:
TARGET_IP="192.168.1.50" # Replace with target's actual IP
TARGET_HOSTNAME="hypervisor" # Replace with your hostname
FLAKE_PATH="." # Or /path/to/repo if not in working directory
EXTRA_FILES="/tmp/${TARGET_HOSTNAME}-deploy"
Then the full command:
nix run github:nix-community/nixos-anywhere -- \
--flake "${FLAKE_PATH}#${TARGET_HOSTNAME}" \
--target-host "root@${TARGET_IP}" \
--generate-hardware-config nixos-generate-config \
"nix/hosts/${TARGET_HOSTNAME}/hardware-configuration.nix" \
--extra-files "${EXTRA_FILES}"
For LUKS with USB Keyfile
If using LUKS with both keyfile (on USB) and password:
--disk-encryption-keys \
"/tmp/secret.key" "/tmp/${TARGET_HOSTNAME}-luks-password.txt"
Important: The binary keyfile on USB is already mounted at /key/${TARGET_HOSTNAME} in the live environment (from step A.7), so disko will find it there. We only need to pass the password via --disk-encryption-keys as a fallback unlock method.
This way disko will:
1. Primary: Use the binary keyfile at /key/${TARGET_HOSTNAME} (which comes from the mounted USB)
2. Fallback: Use the password if the keyfile is unavailable
File Ownership (if needed)
If your SSH keypair file permissions need adjustment after copying:
--chown /etc/ssh/ssh_host_ed25519_key 0:0 \
--chown /etc/ssh/ssh_host_ed25519_key.pub 0:0
Or for desktop user keys:
--chown /home/snyssen/.ssh/id_ed25519 1000:1000 \
--chown /home/snyssen/.ssh/id_ed25519.pub 1000:1000
(Replace 1000:1000 with the actual uid:gid of the snyssen user.)
B.4 Run nixos-anywhere
Execute the full command. For example, with LUKS with USB keyfile and SSH keys:
nix run github:nix-community/nixos-anywhere -- \
--flake .#${TARGET_HOSTNAME} \
--target-host root@${TARGET_IP} \
--generate-hardware-config nixos-generate-config \
nix/hosts/${TARGET_HOSTNAME}/hardware-configuration.nix \
--extra-files /tmp/${TARGET_HOSTNAME}-deploy \
--disk-encryption-keys \
"/tmp/secret.key" "/tmp/${TARGET_HOSTNAME}-luks-password.txt" \
--chown /etc/ssh/ssh_host_ed25519_key 0:0 \
--chown /etc/ssh/ssh_host_ed25519_key.pub 0:0
Or without LUKS:
nix run github:nix-community/nixos-anywhere -- \
--flake .#${TARGET_HOSTNAME} \
--target-host root@${TARGET_IP} \
--generate-hardware-config nixos-generate-config \
nix/hosts/${TARGET_HOSTNAME}/hardware-configuration.nix \
--extra-files /tmp/${TARGET_HOSTNAME}-deploy \
--chown /etc/ssh/ssh_host_ed25519_key 0:0 \
--chown /etc/ssh/ssh_host_ed25519_key.pub 0:0
What happens next:
nixos-anywhereprompts for SSH password (enter the temporary password from A.4)- Kexec image boots on target (you'll see progress messages)
- Disko partitions and formats disks (watch for warnings about data loss)
- If using LUKS: Disko unlocks and formats encrypted partition using the keyfile at
/key/and password as fallback - Nix closure builds and copies to target
- NixOS configuration activates
- Target automatically reboots
Expected time: 20–60 minutes depending on network speed and disk performance
B.5 Monitor the Process
You'll see messages like:
activating the configuration...
setting up /etc/...
creating symlinks...
...
running activation script
...
Installation finished. No error reported.
When you see "Installation finished", the deployment succeeded. The target will reboot automatically.
B.6 Troubleshooting Common Errors
Error: "Password authentication failed"
- Verify root password was set correctly (A.4)
- Verify target's live environment is still running
- Try connecting manually: ssh root@${TARGET_IP}
Error: "Disks not found" or "cannot find partition"
- Stop the command (Ctrl+C)
- Go back to target machine (A.6) and verify disk layout with lsblk
- Update disko config if needed
- Try again
Error: "Permission denied" or "timeout" - Verify network connectivity: ping target from admin host - Verify target's IP address is correct
Kexec fails silently or hangs - May indicate insufficient RAM in target (need ≥1.5GB) - Try rerunning; sometimes network delays cause timeouts
Part C: Verify Successful Deployment
C.1 Wait for Target to Reboot
After "Installation finished", the target will: 1. Unmount filesystems 2. Reboot automatically (may take 1–5 minutes)
Do not interrupt the power; let it complete.
C.2 Verify System is Reachable
Once the target finishes rebooting, it should be reachable. If Tailscale is configured, add it to Tailscale and verify:
# On admin host, if target is on Tailscale
tailscale status | grep ${TARGET_HOSTNAME}
# Should show the target in the list
# Try to ping via Tailscale IP
ping <target-tailscale-ip>
Or via regular SSH (if configured):
ssh root@${TARGET_IP}
# or
ssh snyssen@${TARGET_HOSTNAME} # if user is configured
C.3 Verify Secrets Decrypted
If secrets are configured, verify they were decrypted during boot:
ssh root@${TARGET_IP} "cat /run/secrets/tailscale/authKey"
# Should print the actual auth key (not encrypted)
If this fails with "No such file", the secret wasn't properly decrypted. Check:
- Is the age key in the correct location? (/etc/ssh/ssh_host_ed25519_key)
- Is the secret file re-encrypted with the target's age key?
- Are there errors in the NixOS configuration?
C.4 Optional: Login and Quick Checks
Once logged in:
# Verify hostname
hostname # Should show your target hostname
# Check disk layout
lsblk # Should show formatted partitions
# Check LUKS (if encrypted)
dmsetup status # Should show luks partition is open
# Verify Tailscale (if configured)
tailscale status # Should show connected to tailnet
Part D: Clean Up Admin Host
D.1 Delete Temporary Deployment Files
rm -rf /tmp/${HOSTNAME}-deploy/
rm -f /tmp/${HOSTNAME}-root-password.txt
rm -f /tmp/${HOSTNAME}-luks-password.txt
D.2 Update SSH known_hosts
If this is a new machine, known_hosts may have a stale entry. Remove it:
ssh-keygen -R ${TARGET_IP}
# or for hostname
ssh-keygen -R ${TARGET_HOSTNAME}
This allows SSH to accept the new host key without warnings.
Summary
✅ Target machine deployed with NixOS ✅ SSH keypairs and secrets in place ✅ System booted successfully ✅ Temporary deployment files cleaned up
Next step: Proceed to Setup New Machine - Post-Deployment