Table of Contents

Overview

The rpi-image-gen build process consists of multiple stages executed sequentially:

  1. Parameter Assembly - Input validation and configuration parsing

  2. Layer Processing - Layer collection, validation, and dependency resolution

  3. Build Preparation - Environment setup and bdebstrap configuration

  4. Filesystem Generation - Filesystem creation followed by hooks and overlays

  5. Image Generation - Disk image creation

  6. Deployment - Installation of build assets

Philosophy

When instructed to use a source directory via the CLI, rpi-image-gen prioritises that directory for config files and layers, while still allowing references to its built-in resources. Similar to how a C compiler searches local directories before system directories (-I. -I/usr/include), rpi-image-gen uses a hierarchical search path. This allows users to have complete project encapsulation for their integration.

Practical Example

# Local config can include built-in base
# File: ./my-project/config/kiosk.yaml
include:
  file: bookworm-minbase.cfg  # Resolves to built-in config

layer:
  app: my-kiosk               # Resolves to ./my-project/layer/my-kiosk.yaml

# Build with mixed local/built-in resources
$ rpi-image-gen build -S ./my-project/ -c kiosk.cfg

This philosophy enables a user to have self-contained projects with external dependencies.

Stage 1: Parameter Assembly

Purpose

Validate input arguments, parse configuration files, and establish the initial environment for subsequent stages.

Activities

  • Input Validation and Override Processing: CLI processing

  • Config Parsing: Configuration file parsing

  • Environment Injection: Path setup, initial internal setup

  • Sanity Checks: Validate input sources

Stage 2: Layer Processing

Purpose

Discover, validate, and order all layers specified in the configuration, and expand all configuration variables to their final values.

Activities

  • Layer Collection: Extract layer references from config

  • Layer Validation: Generate layer configuration variables

  • Dependency Resolution: Resolve layer build order to determine the build sequence

  • Variable Expansion: Resolves configuration variables with defined policy handling

Stage 3: Build Preparation

Purpose

Set up the build environment and assemble bdebstrap parameters with the necessary configuration.

Activities

  • Path Configuration: Paths updated to allow location of host binaries and tools

  • APT Configuration: Configure apt settings including:

    • Key directories

    • Cache settings

    • Proxy settings

    • Package options

  • Command Assembly: Constructs the initial bdebstrap command including:

    • Environment variables

    • Target settings

    • All compatible YAML layer files

    • Installing core hook runners for setup, extract, essential, customize, and cleanup phases

Note

Stages 4-6 below name several hook points (eg prebuild). Each is a point where any number of custom scripts can run, not a single fixed filename - see Hooks for the full mechanism, and Overlays for how filesystem overlays fit alongside them.

Stage 4: Filesystem Generation

Purpose

Create the filesystem and generate the Software Bill of Materials.

Activities

  • Hook point prebuild

    • Example Use - Custom validation of pre-build settings

  • Filesystem Generation: Execute bdebstrap, including its own setup/extract/essential/customize/cleanup hook points and overlays

  • Hook point postbuild

    • Example Use - Custom installation of image or device specific assets, eg boot configuration files.

  • SBOM: Execute the Software Bill of Materials provider to create the SBOM file.

Stage 5: Image Generation

Purpose

Create disk images from the prepared filesystem using the provider.

Activities

  • Hook point preimage

    • Example Use - Creating genimage templates, setting up image creation resources.

  • Image Generation: Execute the image provider to create images.

  • Hook point postimage

    • Example Use - Custom packaging, signing with device keys, etc.

Stage 6: Deployment

Purpose

Install output assets from the build to a defined location for distribution.

Activities

Install and compress:

  • Build: Raw disk and sparse images, filesystem archives

  • Audit: SBOM, manifests

Hooks

Overview

Two distinct kinds of hook exist. A layer may declare hooks natively inline in its own YAML (eg mmdebstrap: customize-hooks:) - these run as bdebstrap merges and executes each phase’s hook list, and are documented alongside the rest of a layer’s YAML, not here.

rpi-image-gen runs the other kind - standalone hook files - in phases via script bin/runner. Runner is the single entry point every standalone hook runs through. bdebstrap invokes it for each lifecycle phase (setup, customize, etc.), wired in as the last hook bdebstrap runs for that phase - so every standalone hook runner finds (per-layer, source tree, or built-in) always runs after every layer’s own inline YAML hooks for the same phase. The main script calls runner directly for image, SBOM, and deploy phases, which have no YAML-native hook equivalent since they run outside bdebstrap entirely. Runner:

  • Resolves hook locations from tagged specs (SRCROOT:*, IGROOT:*) so the same path resolves regardless of environment (e.g. host vs container).

  • Executes phase-specific hooks in a deterministic order.

  • Applies filesystem overlays named after the phase that applies them (see Overlays).

  • Orchestrates deterministic hook execution with the correct environment and arguments, regardless of where the build runs.

The phase name is the only key runner works from - it is the prefix every hook and overlay is discovered by, and every phase takes the same path through runner. Within a phase, work happens in a fixed order, and any step with nothing to do is simply skipped:

  1. Per-layer, in build plan order - each layer’s overlay for this phase, then that layer’s own <phase>* hooks

  2. Source tree overlays for this phase

  3. Source tree and built-in hooks for this phase

Consequently a phase needs no special-casing to gain a hook or an overlay: shipping a file whose name starts with the phase, in the right place, is the whole of it.

Important

Hooks are optional but if a hook is to be executed, it must have executable permissions for the user performing the build.

A hook must not read from standard input. Runner passes the build’s stdin through to hooks unchanged, so a hook that reads it will block waiting for terminal input on an interactive build. Where a hook needs data, it should take it from the environment runner provides or a file the layer ships.

Phases

Phase Context Execution

prebuild

main

Before bdebstrap runs

setup

bdebstrap

After build output directory creation, before any packages are downloaded or installed

extract

bdebstrap

After Essential:yes packages have been extracted, before installing them

essential

bdebstrap

After Essential:yes packages have been installed, before installing remaining packages

customize

bdebstrap

After all packages are installed, before cleanup commences

cleanup

bdebstrap

After customize

postbuild

main

After bdebstrap, before sbom

sbom

main

After postbuild

preimage

main

After sbom, before image generation

postimage

main

After image generation

finalize

main

After sbom and image generation, before deploy

deploy

main

Final stage

Hook Discovery

For every phase, runner scans two kinds of location, always in this order:

  1. Per-layer - each layer’s own <layer-stem>.d/hooks/, if present, in build-plan order. Where the layer also ships an overlay for this phase, that overlay is applied immediately before the layer’s own hooks for it.

  2. Tree/built-in, unconditionally after every layer’s per-layer hooks for this phase:

    1. Source tree (SRCROOT/hooks)

    2. Built-in (IGROOT/builtin/hooks)

Runner scans for <phase>* hooks (eg customize*, preimage05-do-something) inside hooks/ subdirectories at each location, executing matching hooks (alphanumeric basename) in lexicographic order - hence the numbered-prefix convention used throughout (customize01-, essential01, cleanup50-apt-cache, etc), which controls run order for multiple hooks at the same location and phase. Subdirectories aren’t traversed; the file extension is ignored.

A per-layer hook runs from its own directory, so anything else the layer ships must be reached through the environment runner provides it:

Variable Value

LAYER_NAME

The layer’s declared X-Env-Layer-Name

LAYER_VERSION

The layer’s declared X-Env-Layer-Version

LAYER_VERSION_MAJOR

Major component of the version

LAYER_VERSION_MINOR

Minor component of the version

LAYER_VERSION_PATCH

Patch component of the version

LAYER_DIR

Directory containing the layer file, ie the parent of <layer-stem>.d

LAYER_DOTD

The layer’s companion directory <layer-stem>.d. Only set when the layer ships one

LAYER_FILE

The resolved layer’s filename (full path)

LAYER_WORKDIR

The layer’s work directory (created automatically)

LAYER_UUID

UUID derived from LAYER_NAME and LAYER_VERSION. Identical on any host for a given pair.

A hook reading a template the layer ships alongside its YAML therefore writes "$LAYER_DIR/genimage.cfg.in" rather than relying on the working directory.

Overlays

Two independent overlay mechanisms exist, applied at different times for different reasons.

Per-layer overlays are named after the phase that applies them. A layer ships <layer-stem>.d/<phase>.overlay (eg my-layer.yaml has my-layer.d/customize.overlay/) and it is applied when that phase runs, immediately before that layer’s own hooks for the same phase. Any phase will do - no table lists them - though in practice three are useful:

Overlay Applied

customize.overlay

During bdebstrap’s customize phase, immediately ahead of that layer’s own customize-hooks, so a layer’s own hooks (eg an enable-units call) can depend on its own overlay content

postbuild.overlay

By bin/runner during postbuild, so the content is present in the tree when the SBOM is generated

preimage.overlay

By bin/runner during preimage, after the SBOM and before image generation

No metadata declares the timing - the directory name is the whole declaration, so deferring content until after filesystem construction is a matter of naming the directory preimage.overlay rather than customize.overlay.

customize.overlay is the one runner does not apply itself; it is applied by the layer’s synthesised pre-config, which is what buys it the position ahead of that layer’s own customize-hooks.

Caution

Prefer customize.overlay for anything landing in the filesystem. An overlay applied during essential writes files before the packages owning them are installed, which breaks dpkg conffile handling.

Overlays are applied with rsync and include --chmod=go-w, so a file keeps whatever mode it declares except that the transfer cannot make it group or world writable. Anything needing to be widely writable in the built filesystem must be made so by a hook rather than by shipping it that way.

<layer-stem>.d/rootfs-overlay and <layer-stem>.d/overlay are both accepted as compat spellings of customize.overlay, offering perhaps a more intuitive or human-readable name for an overlay directory. It’s not recommended to mix more than one of these.

A layer needs no mmdebstrap: mapping of its own to ship an overlay - every layer in the plan is offered pre/post synthesis.

Source-tree overlays are applied during the customize phase by bin/runner:

  1. Source tree overlay - SRCROOT:rootfs-overlay

These apply once, after every layer’s own customize-hooks have already run.

Core

rpi-image-gen ships its own hooks to perform generic tasks. Depending on requirements, there may not be a need to add custom hooks at these stages as the generic hooks execute after SRCROOT hooks.

Device and Image Asset Directory

IGconf_device_assetdir and IGconf_image_assetdir no longer resolve hooks or initramfs content on their own - a device or image layer’s hooks belong in its own <layer-stem>.d/hooks/, found through the build plan like any other layer’s, and initramfs content follows the same pattern, described below. The variables themselves are still ordinary config values a layer’s own hooks can read directly for whatever they need - eg installing a file that lives in the asset directory, or locating a template by a naming convention of the layer’s own choosing.

initramfs

dracut and initramfs-tools are mutually exclusive initramfs generators, so a layer contributing to either ships its content under its own <layer-stem>.d/device/, in the directory matching the generator it targets:

Directory Copied to

device/dracut.conf.d/

/etc/dracut.conf.d/

device/dracut.modules.d/

/usr/lib/dracut/modules.d/

device/initramfs-tools/

/etc/initramfs-tools/, following that tool’s own layout (eg hooks/, scripts/)

Every layer that ships any of these has its content copied in, in build plan order, during customize, via a built-in hook - one for whichever of dracut/initramfs-tools is actually installed in the chroot, immediately before it (re)generates the initramfs.

Interactive Mode

A CLI option allows execution to pause between major operations for user confirmation. This may be useful for inspecting log output prior to building.