AOS Hub / Docs

Build and customize release images

This guide is for AOS release maintainers and platform integrators. End users should download the published AOS image, customize the host with host.nix, and install packages with apm.

Runtime host.nix activation evaluates and atomically applies networking, users, access, services, packages, and other general host policy. Keep only image capabilities, bootstrap reachability, and initial trust roots in the release image; put machine-specific policy in authenticated host.nix.

Changes to signing authorities or image-baked anchors must follow Maintain the AOS trust model. Operators can follow the resulting boot and package chain in Use Secure Boot and verify package trust.

#Create a system variant

Files under systems/ are discovered automatically. A file named systems/acme-server.nix produces an evaluated system at systems.acme-server, disk outputs named acme-server-image-<format>, and an associated default OCI artifact at systems.acme-server.build.defaultContainer.

# systems/acme-server.nix
{...}: {
  imports = [./server.nix];

  aos.roles.server.enable = true;
  aos.networking.hostName = "web-01";

  # Keep physical root capacity independent from the artifact growth gate.
  # The 1 GiB default can be overridden for a device class when needed.
  aos.image.rootPartitionMiB = 1024;

  # These maxima are release gates. Verity and ESP values also size their
  # partitions; maxRootMiB does not resize the root A/B slots.
  aos.image.budgets = {
    maxRootMiB = 512;
    maxVerityMiB = 16;
    maxInitrdMiB = 128;
    maxUkiMiB = 160;
    maxEspMiB = 384;
    maxRuntimeClosureMiB = 768;
    maxDownloadMiB = 640;
  };

  aos.networking.interfaces.eth0 = {
    address = "10.0.0.20/24";
    gateway = "10.0.0.1";
    dns = "10.0.0.53";
  };

  aos.services.ssh = {
    enable = true;
    port = 22;
    permitRootLogin = "prohibit-password";
    passwordAuthentication = false;
    kbdInteractiveAuthentication = false;
  };

  environment.etc."ssh/authorized_keys/root" = {
    text = "ssh-ed25519 AAAA_REPLACE_ME ops@example.com\n";
    mode = "0600";
  };

  aos.firewall.allowedTCP = [443];
}

Interface names are deployment-specific. The server default uses DHCP on Ethernet interfaces matching en* when no explicit interface is declared.

Keep private keys and service credentials out of the module and Nix store. Public trust anchors and SSH public keys may be part of a release image.

#Understand the shared artifact evaluation

Disk and OCI artifacts are projections of one system-module evaluation. The bootable host remains system.build.toplevel; replacing it with a container would erase the kernel, initrd, services, and activation contract. The sibling system.build.defaultContainer is the default container associated with every disk format of that system, while system.build.containers.<name> exposes all of its container definitions. This system-level association avoids repeating the same container pointer on raw, QCOW2, VMDK, and VHD encodings of one logical image.

Option ownership is explicit:

NamespaceApplies to
aos.system, environment.systemPackages, aos.releaseShared system identity, userland, and release/registry policy
aos.image and boot/storage/security optionsBootable disk artifacts only
aos.containersOCI filesystem, runtime, publication, and default-container selection only

The OCI projection consumes the evaluated userland packages and shared release profile; it does not package or retain system.build.toplevel, the kernel, initrd, bootloader, TPM state, or disk layout. Container-specific assertions are enforced by the same system evaluation and also when the container output is forced directly.

The public aos-testing system demonstrates the pattern. Its disk and OCI artifacts contain exactly one andyl/testing registry seed, select edge, and carry the same experimental-use warning and trust anchor. The disk displays the warning on the console and SSH login; the OCI entrypoint prints it to standard error before starting the requested command, based on the immutable release profile rather than an overridable OCI environment value. The production server's compatibility container remains available as container-aos-*; the testing outputs use container-aos-testing-*.

#Compose release policy

Put shared policy in an underscore-prefixed file so system discovery does not publish it as a standalone image:

# systems/_acme-common.nix
{pkgs, ...}: {
  aos.system = {
    locale = "C.UTF-8";
    timezone = "UTC";
  };

  environment.systemPackages = [
    pkgs.curl
    pkgs.jq
  ];

  aos.firewall = {
    enable = true;
    defaultPolicy = "drop";
  };
}

Import it from each concrete variant. Reusable modules should use lib.mkDefault where a concrete system is expected to override policy.

Registries needed from first boot can be seeded with their trust anchors:

{
  aos.apm.registries.acme = {
    url = "https://packages.example.com/";
    priority = 10;
    trustKeys = [
      "acme:Ed25519:AAAAC3NzaC1lZDI1NTE5AAAA_REPLACE_ME"
    ];
  };
}

The image writes this read-only seed under /etc/apm; runtime changes use the writable /var/lib/apm/config overlay.

#Build an image

The image pipeline targets x86_64-linux and aarch64-linux and produces a raw GPT disk, QCOW2, VMDK, and dynamic VHD from the same evaluated system:

git add systems/acme-server.nix
nix-build -A systems.acme-server.build.toplevel
nix build .#acme-server-image-raw
nix build .#acme-server-image-qcow2
nix build .#acme-server-image-vmdk
nix build .#acme-server-image-vhd

Build the experimental artifacts from the same variant evaluation:

nix build .#aos-testing-image-qcow2
nix build .#container-aos-testing-oci
nix build .#container-aos-testing-docker
nix build .#container-aos-testing-publication-inputs

From another architecture, use an x86 Linux remote builder and select the package set explicitly:

nix build .#packages.x86_64-linux.acme-server-image-qcow2

The raw output contains aos-<system>.img.zst and image-info.json. The outer zstd stream keeps fixed partition headroom and the empty inactive slot out of the transfer while the metadata separately binds both the compressed object and reconstructed GPT disk. Secure Boot plus dm-verity systems also expose system.build.recoveryUkiA, system.build.recoveryUkiB, and system.build.recoveryBundle. The bundle has a fixed aos/recovery/ layout containing the ten cataloged payload components, the db-signed manifest, and its detached signature. Preserve it with the release if removable-media recovery is supported. Converted outputs contain the corresponding disk file. Preserve the image metadata with every distributed format.

The raw-image builder calculates ESP capacity from the installed normal and recovery set plus one complete inactive-slot transaction. Inspect espBudget.installedBytes, espBudget.transactionBytes, espBudget.requiredBytes, and espBudget.partitionBytes in image-info.json when changing UKI contents or recovery tooling; a build fails instead of silently producing an ESP that cannot stage the transaction.

#Validate the release artifact

Build the variant's image contract check before publishing it:

nix-build -A systems.acme-server.checks.image-budget
cat result/report.json

Every discovered system exposes this check. It builds the root, initrd, UKI, runtime closure, and compressed raw image; fails if any declared maximum is exceeded; and writes the observed and maximum values to report.json. Building each publication artifact independently enforces the complete contract, records it in the integrity-bound image-info.json, and uses the declared storage maxima for partition geometry. The resulting logical GPT disk must also remain within the 8 GiB publication safety limit, which bounds image materialization before any compressed bytes are expanded. Increase a budget only as an intentional storage-format compatibility change; do not raise one merely to absorb an unexplained size regression. Migrate existing storage before deploying a payload that depends on larger root, verity, or ESP maxima. Use aos profile closure systems.acme-server.build.toplevel to attribute closure growth first.

UKI assembly and signing tools execute on the build platform. The EFI stub and kernel match the target architecture, and runtime PE inspection uses the small target-hosted pe-tools package without retaining the full binutils. AArch64's uncompressed kernel makes its UKIs larger than the x86_64 images:

Default maximum (MiB)x86_64AArch64
UKI160192
ESP, including update workspace384416
Runtime NAR closure768896

Recovery-enabled Secure Boot fixtures use ESP maxima of 544 MiB on x86_64 and 768 MiB on AArch64, retaining both recovery copies throughout an update. The server-2 HTTP fixture uses runtime closure maxima of 832 and 960 MiB, respectively, and an 800 MiB download ceiling to accommodate its x86_64 VHD. Development payload and forbidden-artifact checks remain the same on both architectures.

The server and edge golden images cap compressed raw downloads at 768 MiB with maxDownloadMiB. The uncompressed qcow2, VMDK, and VHD encodings use maxConvertedDownloadMiB, which defaults to the raw limit. Secure Boot test fixtures allow 800 MiB compressed raw because their recovery UKIs remain in the disk. The diagnostic server-test images also allow 800 MiB compressed raw on both architectures. Converted limits follow the measured target payloads:

Converted maximum (MiB)x86_64AArch64
Server and edge768801
Server-2800832
Diagnostic server-test832896
Secure Boot and recovery fixtures8961024

Each format manifest records its own limit in artifactBudgetsMiB.download. Profile the closure and artifacts before changing either ceiling.

Inspect the evaluated option before building:

nix-instantiate --eval --strict \
  -A systems.acme-server.config.aos.networking.hostName

Boot the exact disk artifact that will be published and check at least:

systemctl is-system-running
systemctl --failed
systemctl status sshd.service
systemctl status nftables.service
cat /etc/hostname
cat /etc/ssh/sshd_config
cat /etc/nftables.conf

The source-build tutorial supplies the AOS-built QEMU, OVMF, metadata ISO, and a complete UEFI boot command. The deployment guide covers qualification and promotion of the resulting immutable artifact.