# Peiso

> The Peios image builder — from a declarative peiso.toml to a composed root, packed initramfs, UKI, and bootable live ISO.

---

# Building a Peios image

_Peios / Peiso / Building images_

> peiso is the Peios image builder: it turns a declarative peiso.toml spec into a bootable image tree, layering boot machinery over a peipkg-compose root.

**`peiso`** is the Peios image builder. Given a declarative spec, it builds a bootable Peios image tree from scratch: it takes a release's worth of packages and produces an artifact that a machine can boot.

It is a **v1** tool with a deliberately narrow scope. It does not resolve packages, fetch payloads, or lay out a root — that is [`peipkg-compose`](/peios/using-peios/package-management/composing-a-root.md)'s job, and peiso calls it for exactly that work. peiso owns everything above the package root: the boot machinery that turns a directory of installed software into something bootable.

This page explains what a package root is versus what a bootable image is, the chain of artifacts peiso emits between the two, how you invoke it, and the limits of its v1 scope.

## Where peiso sits — above compose

The two tools split the work at a well-defined boundary.

`peipkg-compose` builds the **package root**: a directory tree with every package's payload laid out at its installed paths, a seeded peipkg state database, and each repository written back as a `.repo` file. The result is a valid peipkg system that can manage itself — but it is only that. Compose's contract stops at delivering a valid peipkg root: no bootloader, no packed initramfs, no kernel image, no live-boot wiring. Those belong to whatever assembles the image around it. See [Composing a root](/peios/using-peios/package-management/composing-a-root.md) for the full contract.

peiso is that outer assembler. It **shells out to `peipkg-compose build`** to produce the root, then layers boot machinery on top of it. The division of responsibility is:

- **compose = the package stage.** Resolve, verify, and lay out software into a root directory. Offline, deterministic, no boot concerns.
- **peiso = the bootable-image stage.** Take that root and produce an initramfs, a squashable rootfs, a kernel image, and a bootable medium.

Because the boot stage runs Peios' own applets — and runs them inside the composed root — peiso needs privilege where compose needs none. This requirement is covered below.

## The output chain

peiso builds an image as a chain of artifacts, each consuming earlier ones. The initramfs cpio is packed inside the composed root, so both the squashfs (a squash of the whole root) and the UKI (kernel + cpio + cmdline) contain it; the ISO then carries the UKI and the squashfs side by side.

```mermaid
flowchart LR
    A["package root<br/>(peipkg-compose)"] --> B["initramfs cpio<br/>(mkirf, in-root)"]
    B --> C["rootfs.squashfs<br/>(optional)"]
    B --> D["UKI — one EFI binary<br/>(mkuki, in-root)"]
    C --> E["bootable UEFI live ISO<br/>(optional)"]
    D --> E
```

1. **Package root.** peiso calls `peipkg-compose build` on the spec's manifest. A single multi-root manifest can compose both the main system root and a nested initramfs root in one pass. (An older spec form gave the initramfs root its own separate manifest to compose; that form is transitional — prefer the single multi-root manifest.)

2. **Initramfs cpio.** peiso chroots into the composed root and runs the root's own **[`mkirf`](/peios/security-fundamentals/boot-and-trust-establishment/mkirf.md)** applet to pack the initramfs root into a cpio archive. Running the shipped mkirf in its native environment means the tool that builds the initramfs is the same one the running system uses — no divergence between what is tested and what runs. (Before this step, extra files may be dropped straight into the initramfs root via the spec's file `inject` list — a temporary bypass of packaging, used for things like the live-boot hook until they are properly packaged.)

3. **Squashfs rootfs (optional).** peiso squashes the whole composed root into a read-only squashfs image (the reference spec names it `rootfs.squashfs`). squashfs **preserves existing extended attributes** — where [KACS security descriptors](/peios/security-fundamentals/security-descriptors/overview.md) ride — so the live rootfs is byte-identical to an installed system. The image is written outside the root tree so it can never contain itself.

4. **UKI (optional).** peiso chroots in again and runs the root's **[`mkuki`](/peios/security-fundamentals/boot-and-trust-establishment/mkuki.md)** applet to bundle the kernel, the initramfs cpio, and the kernel command line into a **Unified Kernel Image** — one EFI binary that UEFI firmware boots directly, with no separate bootloader.

5. **Bootable ISO (optional).** peiso emits `peios.iso`: a UEFI-bootable live image carrying the UKI on an EFI System Partition (ESP) and the rootfs squashfs in its data area, so the running system reads the OS off the medium rather than fitting the whole thing in RAM.

Steps 3, 4, and 5 are optional and driven by the spec — a build can stop at the packed root plus initramfs, or run all the way to an ISO.

### What lands on disk

A full build leaves, among other things:

- the composed `root/` tree (the package root plus the packed initramfs cpio inside it);
- the rootfs squashfs (named by `[squashfs].out`; `rootfs.squashfs` in the reference spec) — written beside `root/`, not inside it;
- the UKI at its ESP fallback path, e.g. `boot/efi/EFI/BOOT/BOOTX64.EFI`;
- `peios.iso` — the bootable UEFI live medium.

## How you run it

peiso has a single verb:

```
sudo peiso build [spec.toml]
```

It is driven by a **`peiso.toml`** spec (schema `1`) that declares the whole image in one file — the manifest to compose, how the initramfs is packed, and the optional squashfs, UKI, ISO, registry-seed, and feature stages. The spec path defaults to `peiso.toml`.

The build **must run as root** — it chroots into the composed root to run the Peios-native applets (mkirf, mkuki), and chroot is privileged; the full rationale is in [Running a build](/peios/peiso/building-images/running-a-build.md).

See [Running a build](/peios/peiso/building-images/running-a-build.md) for the command in detail, and [The build spec](/peios/peiso/reference/the-build-spec.md) for every field of `peiso.toml`.

## Scope and v1 caveats

peiso is at **v1**, and this page describes its current behaviour; the surface is not frozen and may change.

- **Distribution / live-image focused.** The chain above is built around producing a bootable live image (UKI + off-RAM squashfs on a UEFI-bootable ISO). This is the path v1 supports.
- **A single spec schema.** One `schema = 1` spec shape, parsed strictly.
- **Optional stages.** squashfs, UKI, ISO, registry seeds, and feature enablement are each opt-in through their spec sections; a minimal build composes the root and packs the initramfs and stops.
- **Transitional mechanisms exist.** The file-`inject` packaging bypass and the older separate-compose initramfs manifest are both temporary mechanisms on the way to fully-packaged inputs. They are documented where they are used, and labelled as temporary.

Note that v1 does **no signing**: peiso layers boot machinery; it does not create keys or sign binaries. Security properties come from the packages and their preserved security descriptors, not from anything peiso adds.

## Where to go next

- To understand each stage in order — the composes, the chroot, the layering that lets the squashfs embed the initramfs and the ISO carry both the UKI and the squashfs — read [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md).
- To run a build — the command, root requirement, and how peiso finds `peipkg-compose` — read [Running a build](/peios/peiso/building-images/running-a-build.md).
- For every field of `peiso.toml`, section by section, read [The build spec](/peios/peiso/reference/the-build-spec.md).
- For the package stage beneath peiso — how the root is resolved, verified, and laid out — read [Composing a root](/peios/using-peios/package-management/composing-a-root.md).

---

# Quick start

_Peios / Peiso / Building images_

> Run your first peiso build — a minimal peiso.toml that composes the package root and packs the initramfs, and what it leaves on disk.

This page runs the smallest build peiso can do. At the end you have a composed Peios package root on disk with the initramfs cpio packed inside it, produced by one `sudo peiso build` against a three-table `peiso.toml`. The squashfs, UKI, and ISO stages are opt-in and stay off here — see [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md) for the full chain.

## Prerequisites

peiso orchestrates other tools and composes real packages, so all three of these are hard requirements — there is no build without them.

- **Root.** The build chroots into the composed root to run its shipped applets, so it must run as root. Run it under `sudo`.
- **`peipkg-compose` on `PATH`.** peiso shells out to it to compose the root. If it is installed somewhere `sudo` cannot see, set `PEISO_COMPOSE` to its path — the full lookup chain is in [Running a build](/peios/peiso/building-images/running-a-build.md#environment).
- **A package source.** peiso does not fetch or build packages. You need built `.peipkg` files (a package farm) and a **multi-root compose manifest** that lays them out: it must produce both the main root and a nested initramfs root, and the packages it installs must include `peiosutils` (which ships the `mkirf` applet the build runs inside the root) and `fsbase` (which owns the `/system/boot` directory the cpio is written into). The Peios repository's `dist/prod/manifest.toml` is the reference manifest, composed from the repository's own farm; writing manifests is covered in [Composing a root](/peios/using-peios/package-management/composing-a-root.md).

No other host tools are needed for this build. `mksquashfs`, `xorriso`, and friends only come into play when you enable the optional stages.

## Lay out the build directory

Work in a directory that holds your compose manifest. With the reference setup that is the repository's `dist/prod/` directory, which already contains a full `peiso.toml`; to follow this page from scratch, use a fresh directory containing:

- `manifest.toml` — your multi-root compose manifest;
- the package farm it references via its `local_packages` globs;
- `peiso.toml` — written in the next step.

## Write the minimal spec

Create `peiso.toml` next to the manifest:

```toml
schema = 1

[root]
manifest = "manifest.toml"
out      = "root"

[initramfs]
dir  = "boot/initramfs"
cpio = "system/boot/initramfs.cpio.gz"
```

This is the whole spec — `schema`, `[root]`, and `[initramfs]` are the only required tables. `manifest` and `out` are resolved relative to the spec file's own directory, so the build behaves the same wherever you invoke it from. `dir` and `cpio` are relative to the composed root: `dir` names the nested initramfs root your multi-root manifest produces, and `cpio` is where `mkirf` writes the packed archive inside the root. Every field, including the optional ones this spec omits, is documented in [The build spec](/peios/peiso/reference/the-build-spec.md).

## Run the build

```bash
cd /path/to/your/build/dir
sudo peiso build
```

`build` takes one optional argument, the spec path, and it defaults to `peiso.toml` in the current directory — so the bare command above is enough. peiso prints one progress line per stage, with your absolute paths substituted:

```
peiso: composing root -> /path/to/your/build/dir/root
peiso: packing initramfs (chroot /path/to/your/build/dir/root) -> /system/boot/initramfs.cpio.gz
peiso: built /path/to/your/build/dir/root
```

`peipkg-compose`'s own output streams between the first two lines. On a rebuild, a `peiso: cleaning …` line appears first: peiso deletes the previous `root/` and composes fresh every time, so never keep anything you care about inside it. The command exits `0` on success.

If it fails, the error is printed as `peiso: <err>` and the exit code is `1`. The three you are most likely to hit first:

- `build chroots into the composed root and must run as root (try: sudo peiso build)` — you forgot `sudo`.
- `peipkg-compose not found on PATH or next to peiso (set PEISO_COMPOSE to override)` — see the [environment section of Running a build](/peios/peiso/building-images/running-a-build.md#environment); `sudo`'s `secure_path` is the usual culprit.
- `/usr/bin/mkirf not in the composed root … (is peiosutils composed in?)` — your manifest did not compose `peiosutils` in, so the root has no `mkirf` to pack the initramfs with.

## What landed on disk

The build leaves one artifact tree, rooted at the spec's `out` directory:

- `root/` — a complete, valid peipkg package root: every package's files at their installed paths, plus a seeded peipkg state database.
- `root/boot/initramfs/` — the nested initramfs root, composed in the same pass by the multi-root manifest.
- `root/system/boot/initramfs.cpio.gz` — the packed initramfs cpio, exactly where an installed system stores its own.

Confirm the cpio exists:

```bash
ls -lh root/system/boot/
```

There is no rootfs squashfs, no UKI, and no `peios.iso` — those stages only run when their spec tables are present, and this spec left them out.

## Where to go next

- **Understand what just ran.** [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md) walks every stage in order — the compose, the chroot, and the optional stages this build skipped.
- **The command in full.** [Running a build](/peios/peiso/building-images/running-a-build.md) covers the exit codes, host-tool checklist, environment, and the `dist/prod/` Makefile workflow that drives real builds.
- **Grow the spec.** Add `[squashfs]`, `[uki]`, and `[iso]` tables to carry this build all the way to a bootable live ISO — every field is in [The build spec](/peios/peiso/reference/the-build-spec.md).

---

# The build pipeline

_Peios / Peiso / Building images_

> Each stage of a peiso build in order, from compose to ISO — which stages run host tools, which run Peios applets in the chroot, and why the order matters.

A single `sudo peiso build <spec>` turns one declarative [build spec](/peios/peiso/reference/the-build-spec.md) into a bootable image. It does this by running a fixed, ordered sequence of **stages** — compose the root, pack the initramfs, stage first-boot state, squash the rootfs, bundle a UKI, author an ISO. This page walks each stage in the order peiso runs them and explains why the order matters.

## Two core ideas

Two ideas underpin every stage that follows.

**Compose does the packages; peiso does the boot machinery.** peiso does not resolve a package set or lay out a root itself — that is [`peipkg-compose`](/peios/using-peios/package-management/composing-a-root.md)'s job, and peiso shells out to it. What peiso adds is everything compose [deliberately does not do](/peios/using-peios/package-management/composing-a-root.md): pack the initramfs, seed first-boot registry and feature state, produce a rootfs image, bundle a Unified Kernel Image, and author a bootable ISO. Compose stops at delivering a valid peipkg root; peiso begins there.

**The chroot model lets peiso run Peios-native applets against the composed root.** Several stages are not host tools at all — they are Peios boot applets (`mkirf`, `mkuki`) that ship inside the composed root, at `/usr/bin/mkirf` and `/usr/bin/mkuki`. peiso `chroot`s into the composed root and runs them there, so they operate on the tree exactly as they would on a live, booted system. The full `/usr` paths are deliberate: image construction happens before the root-level StrataFS views exist, so Peiso self-hosting addresses package storage directly. This is why the build is privileged, and why the applets take root-relative paths. peiso never carries its own copy of these tools; it uses the ones the image was composed with.

Determinism runs through the whole pipeline. The spec's `source_date` is threaded into every build-stamped timestamp — compose derives its output stamps from it, and peiso pins the squashfs timestamps from it — so the same spec, manifest, lock, and packages yield a byte-reproducible image.

## The stages, in order

The stages below are the exact sequence peiso runs. Optional stages (marked *optional*) run only when their spec section is present.

### 1. Preconditions

peiso **must run as root** (effective UID 0), because it `chroot`s — it checks this first, and the failure message and full rationale are covered in [Running a build](/peios/peiso/building-images/running-a-build.md).

It then locates the `peipkg-compose` binary. The search order is: the `PEISO_COMPOSE` environment variable (overrides everything), else the binary on `PATH`, else a **sibling of the peiso binary itself** — the two are installed together, and `sudo`'s `secure_path` routinely drops the directory (e.g. a Go `GOBIN`) where the sibling sits, so peiso looks right next to itself as a fallback.

### 2. Clean the output root

> [!WARNING]
> peiso recursively deletes the prior `[root].out` directory, as root. This is necessary because `peipkg-compose` **refuses to write into an existing directory** — its build is atomic, so the output is either absent or a complete root. peiso owns `root/` as a rebuildable artifact, so it clears it for a clean rebuild. Because a stray recursive delete running as root is dangerous, the clean is guarded: it refuses obviously unsafe targets (empty, `.`, the filesystem root, or any path whose parent is itself). This single clean also clears the nested initramfs root underneath.

### 3. Compose the main root

peiso shells out to the package stage:

```
peipkg-compose build <[root].manifest> --update --out <[root].out>
```

This is where all package resolution and layout happens — see [Composing a root](/peios/using-peios/package-management/composing-a-root.md). `--update` always re-resolves, so a stale `manifest.lock.toml` (left after editing the manifest or republishing a package) never blocks the build with a digest mismatch; peiso rebuilds against the current manifest and farm.

In the normal v1 setup the manifest is **multi-root**: one compose produces both the main system root at `[root].out` and the nested [initramfs root](/peios/using-peios/package-management/named-roots.md) at `<out>/boot/initramfs` in the same pass. That single tree is why peiso needs none of the live consumer's cross-root transaction machinery here.

### 4. Compose a separate initramfs root — legacy, normally skipped

peiso composes the initramfs root as its own root **only if `[initramfs].manifest` is set**:

```
peipkg-compose build <[initramfs].manifest> --update --out <[root].out>/<[initramfs].dir>
```

This is the older, pre-multi-root form. With a multi-root main manifest the initramfs root is already nested in place from stage 3, so this stage is skipped. Treat `[initramfs].manifest` as **legacy**; new specs leave it unset and rely on the multi-root manifest.

### 5. Inject files into the initramfs root — temporary

If `[initramfs].inject` has entries, peiso copies each `src` file straight into the initramfs root at `dest`, forcing the declared `mode` (a `chmod` after write, so the executable bit survives `umask` — the initramfs prelude execs hooks via their shebang and they must stay executable in the cpio). If a `mode` is omitted the source file's permission bits are preserved.

`[root].inject` does the same for the **main system root**, in the same stage. It runs before every stage that reads the root, so an injected file is visible to the registry-seed staging, the squashfs and the UKI — which makes the common pairing possible: inject a `.reg` master into `usr/share/regim/` and name it in `[registry] autoapply`.

Both are a **bypass of packaging**: they drop files into the tree without a package owning them, so [peipkg](/peios/using-peios/package-management/overview.md) will not upgrade, verify or remove them. For content that images should get properly, a package is the answer — the live-boot hook started here and is now packaged (`live-boot-irf` ships it and a cross-root dependency pulls it into the initramfs root during compose). What stays is the composing artifact's own contributions: an image-specific registry seed, a bring-up affordance you want deletable by editing one spec rather than uninstalling a package.

### 6. Pack the initramfs

peiso chroots in and runs the composed root's own `mkirf`:

```
chroot <[root].out> /usr/bin/mkirf [--exclude <GLOB>]... /<[initramfs].dir> /<[initramfs].cpio>
```

This produces the gzip cpio (in the example spec, at `root/system/boot/initramfs.cpio.gz`) — the early-boot environment. The source and output paths are root-relative in the spec, so inside the chroot they are simply `/<dir>` and `/<cpio>`. `--exclude` globs keep things the early boot does not need out of the cpio (the peipkg database, for example). See [mkirf](/peios/security-fundamentals/boot-and-trust-establishment/mkirf.md) for what goes into the image and how it is assembled.

This runs **before** the squashfs (stage 9) deliberately, so the rootfs image contains this cpio — exactly as an installed system stores its initramfs under `/system/boot`. peiso does not create that destination directory; the `fsbase` package, composed into the main root, owns it.

### 7. Stage registry seeds

If `[registry].autoapply` lists any seed masters, peiso copies each one from the composed root's vendor library at `/usr/share/regim/` into the composer-owned queue at `/lcl/policy/autoapply.d/`, and drops a small autorun script (`10-apply-seeds.sh`) that drains the queue with `reg apply --once-delete` on boot. [peinit](/peios/using-peios/services-and-jobs/overview.md) runs the script every boot; it is a no-op once the queue is empty.

The point of the two directories is curation: packages may drop seed *masters* into `/usr/share/regim/`, but only peiso — the image — decides which of them auto-apply, by copying them into the `/lcl/policy` queue, which sits off peipkg's compiled-in allowlist so no package can write there. Nothing a package ships becomes boot-active unless this image listed it. This runs before the squashfs so the staged seeds ride into the rootfs image.

### 8. Stage features

If `[features].enable` lists any features, peiso writes a self-removing first-boot autorun script (`20-features.sh`, ordered after the seed-apply script) containing a `feat add <name>` line per entry. peinit runs it after [`registryd`](/peios/using-peios/registry-administration/lcs-and-sources.md) is up and **before** [Phase 2](/peios/using-peios/services-and-jobs/boot-and-boot-modes.md) enumerates services, so the services a feature creates start the same boot.

The script removes itself after running (`rm -- "$0"`). On a persistent install that whiteout is durable, so it runs once. On the live read-only image the removal is an ephemeral tmpfs-overlay whiteout, so the script reappears and re-runs each boot — re-creating the services the fresh tmpfs registry would otherwise lose. As with the seeds, the selection lives in the image spec, not in any package: a package ships a feature's lifecycle scripts, but only the image turns it on.

### 9. Squashfs — optional

If `[squashfs]` is present, peiso squashes the **whole** composed root into a read-only rootfs image:

```
mksquashfs <[root].out> <tmp> -noappend -xattrs -mkfs-time <t> -inode-time <t> [-comp <compression>]
```

The timestamps `<t>` are fixed from `source_date` (falling back to epoch `0`) for reproducibility. `-noappend` forces a fresh image rather than appending to any existing one. `-xattrs` **preserves existing extended attributes** — KACS security descriptors ride in them — so the live rootfs carries the same security metadata an installed system would; it does not sign or add anything.

The image is written to a **temp path that is a sibling of the root, outside `root/`**, and renamed into `[squashfs].out` on success. Writing outside `root/` is what lets the squash be of the whole root with no excludes even when `out` itself lives inside the root tree: the growing output can never capture itself. (`src` and `out` must therefore share a filesystem, since the finalisation is a rename.) The result is byte-identical to an installed system — it even contains the initramfs cpio from stage 6.

### 10. UKI — optional

If `[uki]` is present, peiso chroots in and runs the composed root's `mkuki`:

```
chroot <[root].out> /usr/bin/mkuki --kernel /usr/lib/modules/<release>/vmlinuz-<release> --initramfs /<[initramfs].cpio> --cmdline "<text>" --out /<[uki].out>
```

This bundles the kernel (resolved from `/usr/lib/modules/<release>/vmlinuz-<release>` inside the root), the initramfs cpio from stage 6, and the kernel cmdline into a single EFI binary that UEFI firmware boots directly — no bootloader. The cmdline comes from `[uki].cmdline` if given, otherwise the trimmed contents of `[uki].cmdline_file` (read on the host and passed as a literal `--cmdline`, so the chroot needs no in-root file). Reading from the composed root's own cmdline file keeps build-time and runtime agreement — see [mkuki](/peios/security-fundamentals/boot-and-trust-establishment/mkuki.md). Like `mkirf`, `mkuki` is a Peios Dynamic-Boot applet, which is why it runs in the chroot against the tree that ships it.

### 11. ISO — optional

If `[iso]` is present, peiso authors a UEFI, USB/block-bootable ISO. This stage runs **on the host, not in the chroot** — `xorriso` is a build-host tool and the ISO is a host-side artifact, not a Peios boot tool.

peiso builds a FAT32 ESP image from the ESP tree at `[iso].source` (sizing a zero-filled file, formatting it with `mkfs.vfat -F 32`, and populating it with `mcopy` — no mount, no loop device), then has `xorriso` **append** that image as a GPT ESP partition (type `0xEF`). Firmware treats the `.iso` as a disk, reads the GPT, and boots the ESP's UKI at `[iso].efi_boot`. If a squashfs was built, it is placed into the ISO9660 data area (hard-linked when it shares a filesystem, so a multi-GB image is never duplicated) as a file the live system's mount-root hook reads off the medium by volume label — keeping the whole OS off RAM. `-iso-level 3` lifts ISO9660's 4 GB single-file limit, so a multi-gigabyte squashfs fits as one file. The path is UEFI-only and USB/block by design: an El Torito EFI entry pointing at the appended partition is written but not relied on, and there is no BIOS/isolinux path at all — `dd` the `.iso` to a USB stick and firmware boots it as a disk.

## External tools

peiso is an orchestrator: most of the work is done by tools it shells out to. Some run on the build host; the Peios boot applets run inside the chroot against the composed root.

| Tool | Where it runs | Stage | Role |
|---|---|---|---|
| `peipkg-compose` | host | 3, 4 | Resolve and lay out the package-owned root(s). |
| `chroot` → `/usr/bin/mkirf` | inside the chroot | 6 | Pack the initramfs cpio (Peios applet shipped in the root). |
| `mksquashfs` | host | 9 | Build the read-only rootfs image. |
| `chroot` → `/usr/bin/mkuki` | inside the chroot | 10 | Bundle the UKI (Peios applet shipped in the root). |
| `xorriso` | host | 11 | Author the ISO9660 image and append the ESP partition. |
| `mkfs.vfat` | host | 11 | Format the FAT32 ESP image. |
| `mcopy` (mtools) | host | 11 | Populate the ESP image without mounting. |

The `mkirf` and `mkuki` invocations are `chroot` calls into the composed root; every other tool runs directly on the host.

## Why the stages run in this order

The dependencies fix the order. Compose must produce the tree before anything can operate on it. The initramfs is packed before the squashfs so the rootfs image contains it. Registry seeds and feature scripts are staged before the squashfs so they ride into the rootfs image. The UKI needs the packed initramfs cpio, so it follows stage 6 (and follows the squashfs in sequence). The ISO needs the UKI. Read top to bottom: each stage consumes what the stages above it produced.

## Where to go next

For the full field-by-field reference of every spec section named here, read [The build spec](/peios/peiso/reference/the-build-spec.md).

For how to invoke a build and what it prints, read [Running a build](/peios/peiso/building-images/running-a-build.md).

---

# Running a build

_Peios / Peiso / Building images_

> How to run peiso build: the one command and its spec argument, why it must run as root, its exit codes, the tools it needs, and the dist Makefile workflow.

`peiso build` takes a declarative spec and produces a bootable Peios image tree from it — it composes the package root, packs the initramfs, and layers on the optional squashfs, UKI, and ISO stages. This page covers running that command: its one argument, why it needs root, what it needs on the host, and how the `dist/prod/` workflow invokes it. For what each stage does, see [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md); for the spec it reads, see [The build spec](/peios/peiso/reference/the-build-spec.md).

## Synopsis

```
peiso build [spec.toml]

peiso -h | --help | help
```

`build` is peiso's only working verb. It takes **one optional positional argument** — the path to the build spec — and defines **no flags**; anything after the spec path is ignored. The spec path is the only input to the command; everything else about the build is declared inside the spec.

When the positional is omitted, the spec path **defaults to `peiso.toml`** in the current directory:

```
$ cd dist
$ sudo peiso build          # builds ./peiso.toml
$ sudo peiso build peiso.toml   # the same, spelled out
```

`peiso -h`, `peiso --help`, and `peiso help` all print the usage text and exit. Running peiso with no arguments at all is a usage error (it prints usage and exits `2`).

## The build must run as root

The build **chroots into the composed root** to run Peios' own applets — `mkirf` to pack the initramfs, and `mkuki` to bundle the UKI — in their native environment. `chroot` is privileged, so peiso requires an effective UID of `0`.

peiso checks this **first**, before it composes anything, and fails fast with a message that tells you what to do, rather than composing for minutes and then failing at the chroot:

```
peiso: build chroots into the composed root and must run as root (try: sudo peiso build)
```

Run it under `sudo` (or as root):

```
sudo peiso build peiso.toml
```

This root requirement is the one operational difference from [`peipkg-compose`](/peios/using-peios/package-management/composing-a-root.md), which needs no privilege because it only ever writes inside its output directory. peiso needs privilege precisely because the boot stage runs the shipped applets inside the root.

## Exit status

| Code | Meaning |
|---|---|
| `0` | The build succeeded — the image tree (and any optional artifacts) were produced. |
| `1` | The build failed. The error is printed to stderr as `peiso: <err>` — a missing/invalid spec, not running as root, `peipkg-compose` not found, a compose failure, a missing in-root applet, or any stage that errored. |
| `2` | A usage error — no command at all, or an unknown command — with the usage text printed to stderr. |

## Prerequisites

A build uses two distinct sets of tools: programs peiso runs **on the host**, and applets it runs **inside the composed root** by way of `chroot`. Which stages use each are covered in [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md); this section is the checklist.

### Host tools

These must be present on the build host (on `PATH`, unless noted):

| Tool | Package | Used for |
|---|---|---|
| `peipkg-compose` | peipkg | Composing the package root(s). peiso shells out to it. Located via `PATH`, a peiso-binary sibling, or `PEISO_COMPOSE` — see [Environment](#environment). |
| `mksquashfs` | squashfs-tools | Building the rootfs squashfs from the composed root (the squashfs stage). |
| `xorriso` | xorriso | Emitting the bootable `peios.iso` (the ISO stage). |
| `mkfs.vfat` | dosfstools | Building the FAT32 ESP image the ISO carries (the ISO stage). |
| `mcopy` | mtools | Copying the UKI into that ESP image (the ISO stage). |

The last three are only needed if the spec's ISO stage is enabled; `mksquashfs` only if the squashfs stage is. A minimal spec that stops at the packed root needs only `peipkg-compose`.

### In-root applets

Two applets are run **inside the composed root** via `chroot`, so they are not host tools — they must be present in the root itself, which means the **spec's manifest must install the packages that ship them**:

| Applet | Path in the root | Used for |
|---|---|---|
| `mkirf` | `/usr/bin/mkirf` | Packing the initramfs root into a cpio archive. Always run. |
| `mkuki` | `/usr/bin/mkuki` | Bundling the Unified Kernel Image (the UKI stage). |

Running the shipped applets in their own environment is deliberate: the tool that builds the initramfs is the same one the running system carries, so there is no divergence between what is tested and what runs. If `mkirf` is missing from the composed root, the build fails with a hint that the applet package (peiosutils) was not composed in.

These are package-storage paths deliberately. Peiso self-hosts before the root-level StrataFS views are mounted, so it must not depend on `/bin` or another projected view. On x86-64 the composed root must still carry the base-filesystem `/lib64 → usr/lib/x86_64-linux-peios` mapping required by the ELF ABI to start dynamically linked applets.

## Environment

peiso reads a **single** environment variable:

| Variable | Effect |
|---|---|
| `PEISO_COMPOSE` | Overrides the location of the `peipkg-compose` binary. When set, peiso uses it verbatim and skips the search below. |

When `PEISO_COMPOSE` is unset, peiso finds `peipkg-compose` by:

1. looking it up on `PATH`; then
2. falling back to a **sibling of the peiso binary itself** — the two are installed together (`go install` drops both in `GOBIN`), and `sudo`'s `secure_path` routinely drops `GOBIN` from `PATH`, hiding the sibling installed next to peiso.

If it is found by none of these, the build fails:

```
peiso: peipkg-compose not found on PATH or next to peiso (set PEISO_COMPOSE to override)
```

No other environment variables are read.

## What you supply

Running a build takes two inputs, both authored by you:

- **A `peiso.toml` spec** — the declaration of the whole image: the manifest to compose, how the initramfs is packed, and the optional squashfs, UKI, ISO, registry-seed, and feature stages. Every field is documented in [The build spec](/peios/peiso/reference/the-build-spec.md).
- **The peipkg-compose manifest** the spec references — the package set and repositories that compose into the root. This is an ordinary compose manifest; see [Composing a root](/peios/using-peios/package-management/composing-a-root.md).

Everything else — the initramfs cpio, the squashfs, the UKI, the ISO — is produced by the build. peiso owns its output tree as a rebuildable artifact and clears a prior one before each run, so a rebuild always starts clean.

## The dist workflow

The reference workflow lives in the repository's `dist/prod/` directory, driven by its `Makefile`. The `root:` target is the canonical way to run a build, and it handles two operational details for you — locating peiso and elevating to root:

```make
PEISO := $(shell command -v peiso)

root: peiso.toml manifest.toml repo
	@test -n "$(PEISO)" || { echo "peiso not found on PATH — run 'go install' in ../../peiso" >&2; exit 1; }
	sudo $(PEISO) build peiso.toml
```

The target resolves `peiso` **as the invoking user**, while `PATH` is still intact, and then `sudo`-runs it by **absolute path**. That indirection matters: `sudo`'s `secure_path` drops `~/go/bin`, so a bare `sudo peiso` would not find peiso (nor the `peipkg-compose` it calls). Resolving the absolute path first sidesteps that entirely.

The whole build is then:

```
cd dist/prod
make root
```

which expands to `sudo /abs/path/to/peiso build peiso.toml` against the `dist/prod/peiso.toml` spec and its `manifest.toml` (the `repo` prerequisite publishes the medium's offline package repository first, for the spec's `[[iso.include]]`). The other `dist/prod/` targets boot the result under QEMU: `make boot` boots `peios.iso` under OVMF — the real UEFI, GPT-ESP, UKI path, the same one `dd`ing the ISO to a USB stick exercises on metal — with `boot-break` and `boot-quiet` variants, and `make boot-install` / `make boot-installed` drive the installation round trip against a scratch disk (`make clean-disk` resets it).

## What a build leaves behind

A full build (all stages enabled) produces the chain of artifacts described in [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md). In brief:

- the composed **`root/`** tree — the package root with the packed initramfs cpio inside it;
- the **rootfs squashfs** (named by `[squashfs].out`) — written beside `root/`, not inside it;
- the **UKI** at its ESP fallback path, e.g. `boot/efi/EFI/BOOT/BOOTX64.EFI`;
- **`peios.iso`** — the bootable UEFI live medium.

The squashfs, UKI, and ISO are each optional and driven by their spec sections; a minimal build stops at the composed root plus the initramfs cpio.

## See also

- [The build spec](/peios/peiso/reference/the-build-spec.md) — every `peiso.toml` field the command reads.
- [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md) — what each stage of the run does.
- [Composing a root](/peios/using-peios/package-management/composing-a-root.md) — the manifest the spec points at.

---

# The build spec

_Peios / Peiso / Reference_

> Field-by-field reference for peiso.toml: every table and key with types, defaults and path handling, plus the exact validation rules peiso enforces.

**`peiso.toml`** is the declarative TOML spec that drives [`peiso`](/peios/peiso/building-images/overview.md). A single file describes the whole bootable image: the [peipkg-compose manifest](/peios/using-peios/package-management/composing-a-root.md) 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](/peios/peiso/building-images/the-build-pipeline.md) 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.
- **`schema` is checked first.** The spec must declare `schema = 1`; any other value (or a missing `schema`, which reads as `0`) fails with `unsupported 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](#validation)).

## 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:

```toml
schema      = 1
source_date = "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.
bypass_path_restrictions = 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.
[root]
manifest = "manifest.toml"
out      = "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.
[initramfs]
dir     = "boot/initramfs"
cpio    = "system/boot/initramfs.cpio.gz"
exclude = ["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.
[squashfs]
out         = "rootfs.squashfs"
compression = "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.
[uki]
cmdline_file = "root/usr/share/live-boot/cmdline"
out          = "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).
[iso]
source   = "root/boot/efi"
efi_boot = "EFI/BOOT/BOOTX64.EFI"
out      = "peios.iso"
label    = "PEIOS"

# Features this image enables on first boot (feat add <name>).
[features]
enable = ["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`](/peios/using-peios/package-management/composing-a-root.md). |
| `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](/peios/using-peios/package-management/composing-a-root.md) 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](/peios/using-peios/package-management/overview.md) 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](#registry--registry-seeds-auto-applied-on-first-boot-optional), 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.

> [!CAUTION]
> An injected file is only as removable as the spec that placed it. That is an advantage for a bring-up affordance — deleting the `[[root.inject]]` entry deletes the file, with nothing to uninstall — and a hazard for anything else, because no package manifest records that it was ever there.

## `[initramfs]` — packing the initramfs (required)

Describes how peiso packs the initramfs root into a cpio archive using the composed root's own [`mkirf`](/peios/security-fundamentals/boot-and-trust-establishment/mkirf.md) 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`](/peios/security-fundamentals/boot-and-trust-establishment/mkirf.md), 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](/peios/security-fundamentals/boot-and-trust-establishment/mkuki.md) — 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.

```toml
[[iso.include]]
src  = "repo"
dest = "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.

> [!IMPORTANT]
> A booted system can only read the data area if something mounts the medium. On a live image that is [live-boot](/peios/security-fundamentals/boot-and-trust-establishment/initramfs-stage.md)'s root-mount hook, which carries the medium to `/media/peios` after the pivot — prelude moves only `/proc`, `/sys` and `/dev` into the new root and then chroots, so a mount nobody relocates is unreachable afterwards.

> [!NOTE]
> Symlinks are refused rather than followed. ISO9660 without Rock Ridge cannot represent one, and silently following it would place a copy where the image author wrote a reference.

## `[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](#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.** `schema` must equal `1`, or the build fails with `unsupported schema`.
- **Required fields.** `root.manifest`, `root.out`, `initramfs.dir` and `initramfs.cpio` must 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_boot` and `iso.out`; and both `src` and `dest` on every `inject` entry, in `[[root.inject]]` and `[[initramfs.inject]]` alike.
- **No-escape path checks.** The root-relative paths `initramfs.dir`, `initramfs.cpio`, `uki.out`, `iso.efi_boot` and every `inject.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** of `cmdline` or `cmdline_file` must be set. Supplying both, or neither, is an error.
- **Registry seeds must be bare filenames.** Each `registry.autoapply` entry 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.enable` entry 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 generated `feat add` line.
- **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](/peios/peiso/building-images/the-build-pipeline.md) runs.

## See also

- [Building a Peios image](/peios/peiso/building-images/overview.md) — what peiso is and where it sits above compose.
- [The build pipeline](/peios/peiso/building-images/the-build-pipeline.md) — the stage each table drives, in run order.
- [Running a build](/peios/peiso/building-images/running-a-build.md) — the command, exit codes, and required tools.
