Images with nixos-generators
Images with nixos-generators
Goal: Build a bootable artifact (cloud image, ISO, or VM format) from your flake modules using nixos-generators (and related patterns), then boot it once and prove it is the same module graph as a live host—not a snowflake fork.
Images are first-class deploy targets, not a side quest. The win is one module tree → install path and golden image path. NixOS 26.05 pin stays consistent with the rest of this book.
Why it matters
Not every node is nixos-install by hand. Cloud and edge want images: qcow2, AMIs (via pipelines), raw disks, installer ISOs. Generating images from the same modules as deploy targets closes a huge consistency gap—and catches “works on my VM but not in the AMI” earlier.
Without an image story you either:
- Hand-maintain cloud user-data that reimplements half of NixOS, or
- Drift image hosts away from Colmena/deploy-rs nodes until nobody trusts either.
Theory 1 — Image = NixOS config + format
same modules as hosts/edge
│
▼
nixos-generators / make-disk-image / sd-image
│
▼
artifact (iso, qcow2, raw, gce, azure, …)
| Format (examples) | Use |
|---|---|
iso |
Installer / live recovery |
qcow / qcow2 / vmware / virtualbox |
Hypervisors |
amazon / gce / azure |
Cloud (tooling evolves) |
sd-card / raw |
SBC / dd |
Check the current nixos-generators format list for your pin—names and defaults change. Prefer the project README over stale blog posts.
Mental model
An image build is still a NixOS evaluation: modules merge, activation scripts exist, store paths land on a disk layout the format owns. You are not “exporting files”; you are realizing a system derivation into a disk picture.
Theory 2 — nixos-generators integration shapes
Flake app / package patterns
Community patterns include:
# Conceptual — verify against current nixos-generators docs
inputs.nixos-generators.url = "github:nix-community/nixos-generators";
inputs.nixos-generators.inputs.nixpkgs.follows = "nixpkgs";nix run github:nix-community/nixos-generators -- --help
# format flags and flake integration evolveModule specializations
Images often need:
| Concern | Image-friendly approach |
|---|---|
| Hostname | Generic or cloud-init later |
| SSH keys | Inject via cloud-init / metadata; or bake lab keys carefully |
| Disko | May differ from bare-metal Disko; know format’s disk story |
| Size | Minimal profiles; avoid workstation GUIs on cloud images |
| First boot | growpart, regenerate machine-id policies |
Theory 3 — Same modules, different entrypoints
# modules shared
imports = [
../modules/profiles/baseline.nix
../modules/roles/web.nix
];
# hosts/edge — deploy target
# images/edge — generators entry with format-specific tweaks only| Allowed in image entry | Forbidden without reason |
|---|---|
| Format modules | Entirely different nginx config |
| Cloud agent packages | Hardcoded prod secrets |
| Temporary serial console | Divergent firewall policy forever |
If image and live host diverge by 200 lines, you have two products.
Theory 4 — Cloud-init and metadata boundary
Terraform/cloud metadata often supplies:
- SSH keys
- Hostname
- Network
NixOS modules should consume those mechanisms or disable conflicting agents intentionally.
| Smell | Fix |
|---|---|
| 500-line bash user-data | Bake image; tiny user-data |
| Image with prod secrets | Inject at instance start via sops/metadata carefully |
| No SSH to new instance | Console + known image user/key story |
Theory 5 — Build performance and caches
Image builds are heavy. Use:
- Binary caches for the system closure
- Remote builders with disk
- CI only on
mainor nightly for full images; PR builds eval/smaller checks
nix build .#images.edge-qcow -L
ls -lh result*Theory 6 — ISO as recovery
A flake-built installer ISO that includes your SSH key and network helpers is a recovery product:
laptop dies → boot ISO → clone flake → Disko → install
Pair with Disko chapter reinstall proofs. Store ISO hashes in runbooks if you distribute them.
Worked example — qcow lab image (shape)
# Illustrative structure — adapt to current generators API
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
# inputs.nixos-generators = …;
outputs = { self, nixpkgs, ... }: {
nixosConfigurations.edge-image = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./modules/profiles/baseline.nix
./modules/roles/web.nix
./images/edge-image.nix
];
};
# packages.x86_64-linux.edge-qcow = … generators expression …
};
}# images/edge-image.nix
{ lib, modulesPath, ... }:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
services.openssh.enable = true;
# lab only:
users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA… lab" ];
system.stateVersion = "26.05";
}Boot under qemu:
qemu-system-x86_64 -m 2048 -enable-kvm -drive file=./result/nixos.qcow2,if=virtio
# paths vary by generator output layoutProve:
ssh root@… cat /etc/os-release
# compare packages/options to live edge host where possibleWorked example — consistency checklist
# IMAGE-CONSISTENCY.md
- Module list shared with hosts/edge: yes/no
- Divergences (intentional): …
- Format: qcow2 / iso / …
- Build command:
- Boot proof date:
- SSH method:Lab — multi-step
Suggested notes: ~/lab/nixos/ops/images
Step 1 — Pick a format
qcow2 for local hypervisor is the friendliest lab path. ISO if you care about recovery.
Step 2 — Wire generators input
follows nixpkgs; read current README for flake examples.
Step 3 — Image module entry
Import the same baseline/role as a live host. Only format-specific imports extra.
Step 4 — Build artifact
# per current docs
nix build … -L
du -h result*Step 5 — Boot once
QEMU/libvirt/VirtualBox—whatever you have. Reach SSH or console.
Step 6 — Consistency proof
Compare nixos-version, hostname policy, and one role-provided package/service to the live host story.
Step 7 — Size diet (optional)
Remove fat packages; rebuild; note image size delta.
Step 8 — Document CI policy
When do you rebuild images? Who may publish them?
Step 9 — Stretch cloud
If credentials exist, note AMI/GCE path as stretch—do not block on cloud.
Exercises
- List five things that must never be baked into a published image.
- Design a versioning scheme for golden images (flake rev + date).
- Explain why Disko for metal and cloud image root disk may differ.
- Sketch a pipeline: CI builds qcow → uploads → TF launches.
- Recovery ISO contents checklist (network, git, ssh, disko).
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Format name changed | Read current generators docs |
| Image huge | Minimal profile; multi-out not enough—fewer packages |
| Cannot SSH | Console; authorizedKeys; cloud-init race |
| Divergent modules | Re-unify with live host |
| Build OOM/disk | Builder sizing; GC |
| Secrets in image | Remove; inject later |
| Slow every PR | Cache; nightly images |
| Wrong system arch | Match hypervisor/cloud |
Checkpoint
- Generators (or equivalent) wired
- Image built from shared modules
- Booted once with proof
- Consistency notes vs live host
- CI/publish policy sketched
- No secrets baked intentionally
Commit
git add .
git commit -m "ops: nixos-generators image from shared modules"Write three personal gotchas before Terraform boundary.