The build spec
peiso.toml is the declarative TOML spec that drives peiso. A single file describes the whole bootable image: the peipkg-compose manifest to build the package root from, how the initramfs is packed, and the optional squashfs, UKI, ISO, registry-seed and feature stages that the build pipeline layers on top. This page documents schema version 1 — the only schema peiso accepts — field by field.
The spec is passed to peiso positionally and defaults to peiso.toml in the current directory:
sudo peiso build [spec.toml]
Three properties hold across the whole file:
- TOML, parsed strictly. An unknown key anywhere in the spec is a hard error, not a warning — peiso rejects the first undecoded key by name. A mistyped field fails the build rather than being silently ignored.
schemais checked first. The spec must declareschema = 1; any other value (or a missingschema, which reads as0) fails withunsupported schema.- Relative paths resolve against the spec file's own directory — never the current working directory. So a build behaves the same wherever you invoke it from. There are two kinds of path handling, and each field uses one of them:
- Spec-relative paths (
root.manifest,root.out,initramfs.manifest,squashfs.out,uki.cmdline_file,iso.source,iso.out,inject.src) are made absolute against the spec's directory. An already-absolute value is kept as given. - Root-relative paths (
initramfs.dir,initramfs.cpio,uki.out,iso.efi_boot,inject.dest) are cleaned and kept relative (any leading/is stripped). These double as chroot-absolute paths inside the composed root, so they are validated to not escape with..(see Validation).
- Spec-relative paths (
A complete example #
A full spec that composes a root, packs the initramfs, squashes the rootfs, builds a UKI, emits a bootable ISO, and enables a feature on first boot:
= 1
= "2026-06-22T00:00:00Z" # pins reproducible build timestamps
# Let packages declaring `special_system_package` compose outside the layout
# rules. Needed by any image carrying the base-filesystem package, whose whole
# job is to lay down the mountpoint tree those rules protect.
= true
# The whole image. manifest.toml is multi-root — it composes the main system
# root into `out` and the nested initramfs root (at out/boot/initramfs) in one
# pass, so [initramfs] below needs no manifest of its own.
[]
= "manifest.toml"
= "root"
# How mkirf packs the already-composed initramfs root into a cpio. `dir` and
# `cpio` are root-relative: inside the chroot they are mkirf's /<dir> source and
# /<cpio> output. The peipkg database isn't needed at early boot, so it's excluded.
[]
= "boot/initramfs"
= "system/boot/initramfs.cpio.gz"
= ["var/state/peipkg", "lcl/conf/peipkg"]
# Read-only rootfs image, squashed from the WHOLE composed root. `out` is written
# OUTSIDE root/ so the image can never contain itself.
[]
= "rootfs.squashfs"
= "zstd"
# Unified Kernel Image: kernel + initramfs cpio + cmdline in one EFI binary.
# cmdline_file is read from the composed root, so build-time and runtime agree.
[]
= "root/usr/share/live-boot/cmdline"
= "boot/efi/EFI/BOOT/BOOTX64.EFI"
# Bootable UEFI ISO built from the ESP tree at `source`, with the UKI marked as
# the EFI boot image at `efi_boot` (relative to source).
[]
= "root/boot/efi"
= "EFI/BOOT/BOOTX64.EFI"
= "peios.iso"
= "PEIOS"
# Features this image enables on first boot (feat add <name>).
[]
= ["dynamic-boot"]
[squashfs], [uki], [iso], [registry] and [features] are all optional — a minimal spec is just schema, [root] and [initramfs], which composes the root and packs the initramfs.
Top-level fields #
| Key | Type | Required | Meaning |
|---|---|---|---|
schema | int | yes | Spec schema version. Must be 1; any other value fails. |
source_date | string | no | A fixed build timestamp for reproducible artifacts, passed through to mksquashfs. Empty (or omitted) means epoch 0. The format is not validated by peiso — the example uses an RFC 3339 timestamp, mirroring the compose manifest's source_date. |
bypass_path_restrictions | boolean | no | Permits packages that declare special_system_package to compose payloads outside the package layout rules — the base-filesystem package that mints the mountpoint tree being the case that needs it. Passed through to peipkg-compose as --dangerously-bypass-path-restrictions. Defaults to false. It exempts nothing that has not declared itself special: the package proposes, and this line is how the image decides. |
[root] — the package root (required) #
The main system root, composed by shelling out to peipkg-compose build. Both keys are required.
| Key | Type | Required | Meaning |
|---|---|---|---|
manifest | string (spec-relative) | yes | The peipkg-compose manifest to build the root from. A multi-root manifest composes both the main system root and the nested initramfs root in one pass. |
out | string (spec-relative) | yes | The directory to compose into and chroot into. peiso owns this as a rebuildable artifact and clears it before each build (a single clean that also clears the nested initramfs root). |
inject | array of tables | no | A packaging bypass. Files copied straight into the composed main root after compose. See below. |
[[root.inject]] #
Each entry copies one file into the composed main root, after compose and before every stage that reads the root — so an injected file is visible to the registry-seed staging, the squashfs, and the UKI. src and dest are both required.
| Key | Type | Required | Meaning |
|---|---|---|---|
src | string (spec-relative) | yes | The source file to copy. |
dest | string (root-relative) | yes | The destination inside the main root. Must not escape with ... |
mode | string (octal) | no | Permission bits as an octal string (e.g. "0644"). Empty (or omitted) preserves the source file's mode. |
An injected file has no package owner, so peipkg will not upgrade, verify or remove it. That cost is the reason to prefer a package for anything images should get properly. What it buys is a home for the composing artifact's own contributions — an image-specific registry seed, a boot script — which is the same authority peiso already exercises when it stages the seed queue and the autorun script into /lcl/policy.
The common pairing is a seed: inject a .reg master to usr/share/regim/<name> and name it in [registry] autoapply. The inject runs first, so by the time the queue is built the seed is indistinguishable from one a package shipped.
[initramfs] — packing the initramfs (required) #
Describes how peiso packs the initramfs root into a cpio archive using the composed root's own mkirf applet, run inside the chroot. dir and cpio are required; the rest are optional.
| Key | Type | Required | Meaning |
|---|---|---|---|
dir | string (root-relative) | yes | The initramfs root, relative to root.out (e.g. boot/initramfs). Inside the chroot this is mkirf's /<dir> source. Must not escape the root with ... |
cpio | string (root-relative) | yes | The cpio output path, relative to root.out (e.g. system/boot/initramfs.cpio.gz). Inside the chroot this is mkirf's /<cpio> output. Must not escape the root with ... |
exclude | array of strings | no | Globs passed to mkirf --exclude, relative to the initramfs root — paths omitted from the cpio (e.g. the peipkg database, not needed at early boot). |
manifest | string (spec-relative) | no | Legacy — normally unused. The older separate-compose form: a manifest that composes the initramfs root on its own. It is normally absent because a multi-root root.manifest already produces the nested initramfs root, and this section then only describes how mkirf packs it. When present, peiso composes it into root.out/<dir> as a second, separate compose. |
inject | array of tables | no | Temporary — a packaging bypass. Files copied straight into the initramfs root before mkirf runs, a stopgap for landing files (e.g. the live-boot hook) without packaging them. See below. |
[[initramfs.inject]] (temporary) #
Each entry copies one file into the initramfs root before it is packed. This is a transitional mechanism — the intended path is to ship such files in a package so a cross-root dependency pulls them into the initramfs root during compose. src and dest are both required.
| Key | Type | Required | Meaning |
|---|---|---|---|
src | string (spec-relative) | yes | The source file to copy. |
dest | string (root-relative) | yes | The destination inside the initramfs root. Must not escape with ... |
mode | string (octal) | no | Permission bits as an octal string (e.g. "0755"). Empty (or omitted) preserves the source file's mode. |
[squashfs] — the read-only rootfs image (optional) #
When present, peiso squashes the whole composed root into a read-only image (the live-boot lower layer). Presence of the table is what turns the stage on; out is then required.
| Key | Type | Required | Meaning |
|---|---|---|---|
out | string (spec-relative) | yes (when [squashfs] present) | The image output path. Conventionally written outside root.out so the image can never contain itself. |
exclude | array of strings | no | Source-relative paths to omit from the image. |
compression | string | no | The mksquashfs -comp value (e.g. zstd). Empty uses the mksquashfs default. |
[uki] — the Unified Kernel Image (optional) #
When present, peiso bundles a UKI — kernel + initramfs cpio + kernel cmdline in one EFI binary — using the composed root's own mkuki applet in the chroot. You must supply the cmdline exactly one way: cmdline or cmdline_file, never both and never neither.
| Key | Type | Required | Meaning |
|---|---|---|---|
cmdline | string | conditional | The literal kernel command line. Mutually exclusive with cmdline_file. |
cmdline_file | string (spec-relative) | conditional | A file holding the cmdline, read at build time. Pointing at a file inside the composed root keeps build-time and runtime single-sourced. Mutually exclusive with cmdline. |
out | string (root-relative) | yes (when [uki] present) | The UKI output, relative to root.out — the ESP EFI path (e.g. boot/efi/EFI/BOOT/BOOTX64.EFI). Must not escape the root with ... |
[iso] — the bootable ISO (optional) #
When present, peiso emits a UEFI-bootable ISO9660 image (via xorriso) from an ESP tree. source, efi_boot and out are required; label defaults.
| Key | Type | Required | Meaning |
|---|---|---|---|
source | string (spec-relative) | yes (when [iso] present) | The ESP directory tree packed into the ISO (holding the UKI). |
efi_boot | string (root-relative to source) | yes (when [iso] present) | The EFI boot binary (the UKI) inside the ESP tree, relative to source (e.g. EFI/BOOT/BOOTX64.EFI) — the file firmware boots from the ESP partition. peiso checks it exists before authoring the ISO. Must not escape source with ... |
out | string (spec-relative) | yes (when [iso] present) | The .iso output path. |
label | string | no | The ISO9660 volume label. Defaults to PEIOS. |
[[iso.include]] #
Places a file or a whole directory tree into the ISO9660 data area — the part of the medium that is neither the ESP nor the boot chain. Repeatable.
| Key | Type | Required | Meaning |
|---|---|---|---|
src | string (spec-relative) | yes | Source file or directory on the build host. |
dest | string (ISO-relative) | yes | Where it lands on the medium. Must not escape the ISO root with .., and must not collide with the rootfs squashfs peiso places there itself. |
peiso already puts the rootfs squashfs in the data area; this makes the same space available to the image.
[[]]
= "repo"
= "repo"
Data-area content is not in the squashfs, which is the point. It costs nothing in the root filesystem, is not decompressed at boot, and — because the squashfs is byte-identical to an installed root — is not inherited by machines installed from the medium. An installation medium can carry a package repository without imposing one on every system installed from it.
Files are hard-linked into the staging tree where the filesystems allow it, so including a large tree that already exists in the build directory costs no extra space or time.
[registry] — registry seeds auto-applied on first boot (optional) #
Selects which vendor registry-seed masters (shipped by packages into /usr/share/regim/) this image auto-applies on first boot. peiso stages each into the composed root's autoapply queue; peinit drains it at first boot. Selection lives here, in the image, not in the packages.
| Key | Type | Required | Meaning |
|---|---|---|---|
autoapply | array of strings | no | Bare seed filenames (no path separators), relative to /usr/share/regim/ in the composed root. Each entry is validated to be a bare filename. |
[features] — features enabled on first boot (optional) #
Selects which curated features (shipped by packages into /usr/libexec/features/ and exposed at runtime through /libexec/features/) this image enables on first boot. peiso writes a self-removing autorun script of feat add <name> lines; peinit runs it before Phase 2, so the services a feature creates start the same boot.
| Key | Type | Required | Meaning |
|---|---|---|---|
enable | array of strings | no | Feature names to feat add on first boot. Each must match feat's name grammar (see Validation); the name is embedded verbatim in a generated shell line. |
Validation #
peiso loads, decodes and validates the spec before running anything. The rules, exactly as enforced:
- Unknown keys are rejected. Any key the schema does not define — at any level — fails the build, naming the first offending key.
- Schema gate.
schemamust equal1, or the build fails withunsupported schema. - Required fields.
root.manifest,root.out,initramfs.dirandinitramfs.cpiomust all be present and non-empty. Within an optional table that is present, its own required keys apply:squashfs.out;uki.out;iso.source,iso.efi_bootandiso.out; and bothsrcanddeston everyinjectentry, in[[root.inject]]and[[initramfs.inject]]alike. - No-escape path checks. The root-relative paths
initramfs.dir,initramfs.cpio,uki.out,iso.efi_bootand everyinject.dest(both roots) may not be..or begin with../— they become chroot-absolute paths and must stay inside their tree. (Spec-relative paths are not subject to this check; they resolve to absolute paths.) - UKI cmdline mutual-exclusion. When
[uki]is present, exactly one ofcmdlineorcmdline_filemust be set. Supplying both, or neither, is an error. - Registry seeds must be bare filenames. Each
registry.autoapplyentry must be non-empty and contain no path separator (it must equal its own basename); a path component is a spec error. - Feature-name grammar. Each
features.enableentry must be non-empty, must not be.or.., and must consist only of ASCII alphanumerics plus-,_and.. This mirrors feat's own name grammar and doubles as a guard against shell metacharacters, since the name is embedded in a generatedfeat addline. - Inject mode.
inject.mode, when set, must parse as an octal integer; a malformed value fails the build. Applies to both inject tables.
A spec that passes all of these checks is what the build pipeline runs.
See also #
- Building a Peios image — what peiso is and where it sits above compose.
- The build pipeline — the stage each table drives, in run order.
- Running a build — the command, exit codes, and required tools.