Signed NixOS images for OpenStack, built declaratively from a Nix
flake
Report a bug
·
Request a feature
This repo builds NixOS cloud images via a Nix flake that
imports nix-community/nixos-generators and
declares an OpenStack-friendly NixOS configuration. Build is driven by
the openimages.cloud reusable build-nix-flake-image.yml
workflow (third paradigm after libguestfs and DIB).
NixOS is a third paradigm vs the cloud-init-based distros (alpaquita,
alpine, AL2023, …): the OS doesn't ship cloud-init at all; instead a
custom systemd service (openstack-init) defined in build/config.nix
fetches the OpenStack /1.0/meta-data/ HTTP endpoint at boot to apply
hostname + SSH keys.
This pipeline is the openimages.cloud-aligned successor of the legacy
linitio/openstack-nixos-image (2024-04 vintage), rebuilt
around cosign-signed releases, R2/Garage object storage, and the
build-nix-flake-image.yml reusable.
<version> is the NixOS release (YY.MM, e.g. 25.11), which
maps to the matching nixos-X.Y branch of github.com/NixOS/nixpkgs.
The flake input nixpkgs.url is rewritten to that branch at build
time by build/nix-build.sh (substituting the literal VERSION token
in flake.nix.template).
system.stateVersion is set to the same value, pinning the on-disk
defaults to that release.
| NixOS release | Channel |
|---|---|
| 25.05 | nixos-25.05 |
| 25.11 | nixos-25.11 ← default |
Public CDN, served via Cloudflare in front of an R2 bucket (mirror of the source-of-truth Garage):
| URL pattern | Cache policy |
|---|---|
https://images.openimages.cloud/nixos/<version>/<filename> |
max-age=31536000, immutable |
https://images.openimages.cloud/nixos/latest/<filename> |
max-age=300 |
Browse: images.openimages.cloud/nixos/latest/
Filename: nixos-<version>-x86_64.qcow2 (e.g. nixos-25.11-x86_64.qcow2).
cosign 3.x:
sha256sum -c <filename>.sha256 # integrity
cosign verify-blob \
--bundle <filename>.bundle \
--new-bundle-format \
--certificate-identity-regexp '^https://github.com/open-img-cloud/\.github/\.github/workflows/build-nix-flake-image\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
<filename> # provenance# Pull the qcow2 (replace <V> with the desired NixOS release, e.g. 25.11)
curl -fLO https://images.openimages.cloud/nixos/<V>/nixos-<V>-x86_64.qcow2
openstack image create \
--disk-format qcow2 --container-format bare \
--min-disk 5 \
--file nixos-<V>-x86_64.qcow2 \
"NixOS <V>"The default user is nixos (passwordless sudo via wheel). SSH keys
come from /1.0/meta-data/public-keys/0/openssh-key of the OpenStack
metadata service.
⚠️ Known limitation: the currentopenstack-initservice inbuild/config.nixonly fetches metadata over HTTP from169.254.169.254. ConfigDrive (the vfat labelconfig-2Proxmox emits) is not read. NixOS images deployed on Proxmox won't pick up SSH keys / hostname unless the network metadata is reachable. Tracked as a follow-up — see the comment inconfig.nix.
watch.ymlruns daily 06:59 UTC, callsbuild/detect-upstream.shwhich queriesapi.github.com/repos/NixOS/nixpkgs/branchesand emits the highestnixos-X.Ybranch.- If the version differs from
VERSION, the workflow opens a PRauto/upstream-bump. - Merging the PR + pushing a
v<VERSION>tag firesrelease.yml, which calls the sharedbuild-nix-flake-image.yml@mainreusable workflow. - The reusable runs
build/nix-build.shinside thenixos/nix:2.21.2container on a GH-hosted ubuntu-latest runner. The script enables flakes + kvm system features in/etc/nix/nix.conf, rendersflake.nixfromflake.nix.template, runsnix build .#openstack, and copies the result to the workflow's output dir. - Output qcow2 is signed (cosign keyless), bundled with MANIFEST,
uploaded to Garage + R2, and Cloudflare cache for
latest/is purged.
VERSION single line, e.g. "25.11"
build/
nix-build.sh builds the flake's `openstack` package
detect-upstream.sh prints latest nixos-X.Y branch on stdout
flake.nix.template flake.nix with VERSION token to substitute
config.nix NixOS module (bootloader, openssh, openstack-init)
.github/workflows/
release.yml calls build-nix-flake-image.yml on tag push
watch.yml daily cron, calls upstream-watch.yml
.gitignore repo-local override for global build/ exclusion
LICENSE GPL-2.0
- No cloud-init. NixOS implements its own metadata fetching via the
openstack-initsystemd service inbuild/config.nix. The org-wide99_oic-policy.cfgcloud-init drop-in injected by the libguestfs reusable does NOT apply here. - No smoke test in the reusable. NixOS's metadata model is custom enough that the generic ConfigDrive smoke loop wouldn't validate anything useful. Validation happens after publication via deploying the image into an OpenStack tenant.
- Build is deterministic. Two builds of the same
flake.nix+flake.lockproduce byte-identical qcow2s — Nix's whole point.
Fork, branch, PR. Keep changes focused. The most-touched file is
build/config.nix — the NixOS module that defines the image. Adding
ConfigDrive support there (parse /dev/disk/by-label/config-2) is
the highest-priority follow-up.
Distributed under the GPL-2.0 License. See LICENSE.
Kevin Allioli — [email protected] · @stackopshq
Project: open-img-cloud/nixos