rpi-image-gen is a tool used to create custom software images for Raspberry Pi devices. rpi-image-gen runs best on a Raspberry Pi Host running up-to-date 64-bit Raspberry Pi OS.
For the tool used to create Raspberry Pi OS, please go to https://github.com/RPi-Distro/pi-gen
NOTE rpi-image-gen is under active development. Please report issues at https://github.com/raspberrypi/rpi-image-gen. Feature suggestions are welcome.
The following terms are used in this document:
Host The machine on which rpi-image-gen is executing. Analogous to GNU Build.
Device The machine on which the image generated by rpi-image-gen will execute. Analogous to GNU Host.
Class The device hardware family that the software image is built for.
Variant Enables differentiation between devices of the same class.
Package A collection of files and software resources, encapsulated in a known format, that constitute part of a larger software system.
Config A file that defines the device rootfs profile, disk image configuration identifier and top-level attributes.
Profile A file that contains the YAML layers for the device rootfs.
Image Identifies the configuration used to create the device disk image file.
Layer A single file of YAML metadata.
Meta A directory containing YAML layers in a defined hierarchy.
Hook A shell script that may be optional and which if found, will be run at a defined point in a particular flow of execution.
SBOM Software Bill Of Materials
CVE Common Vulnerabilities and Exposures
git clone https://github.com/raspberrypi/rpi-image-gen.git cd rpi-image-gen sudo ./install_deps.sh ./build.sh
The device image will be in work/deb12-arm64-min/artefacts/deb12-arm64-min.img
Install it onto an SD card using Raspberry Pi Imager (https://www.raspberrypi.com/software/).
Select "Use Custom" if using the GUI. If using the command line and
is where you want to install to:/dev/mmcblk0
sudo rpi-imager --cli work/deb12-arm64-min/artefacts/deb12-arm64-min.img /dev/mmcblk0
Note: The default image intentionally has login passwords disabled. Please look in the examples directory to help understand how to customise the installation of packages and make your own modifications.
-
Quick to build. You don’t have to build the whole world from source.
-
By using the same binaries as millions of people are using in production worldwide, you know you’ve not accidentally inserted vulnerabilities with your choice of tool chain, optimisations, build flags, etc.
-
Use the same version set of libraries and applications as you do on Raspberry Pi OS. You won’t have to install a bleeding edge version of a library to build your application.
-
Configure your filesystem exactly how you want it and use rpi-sb-provisioner (https://github.com/raspberrypi/rpi-sb-provisioner) to automatically setup and enable signed boot and encrypted filesystems.
-
Output your software bill of materials (SBOM) to list the exact set of packages that were used to create the image.
-
Generate a list of CVEs identified from the SBOM and give consumers of your image confidence that your image does not contain any known vulnerabilities.
To install the required Host dependencies for rpi-image-gen you should run:
cd rpi-image-gen sudo ./install_deps.sh
The file depends
contains a list of the dependencies required. The format of this file is <tool>[:<debian-package>]
.
rpi-image-gen is a root file system and image creation tool designed with a high degree of customisation, flexibility and control in mind. rpi-image-gen uses bdebstrap (https://github.com/bdrung/bdebstrap) and mmdebstrap (https://gitlab.mister-muffin.de/josch/mmdebstrap) for rootfs construction and genimage (https://github.com/pengutronix/genimage) for image creation. rpi-image-gen is a Bash orientated scripting engine capable of producing software images with different on-disk partition layouts, file systems and profiles using collections of metadata and a defined flow of execution. It provides the means to create a highly customised software image for your Raspberry Pi device. rpi-image-gen is human readable, auditable and easy to use.
A device system is created using the concept of 'collections' where a collection is some YAML metadata in a defined format that describes a list of packages to be installed in, and operations to be performed on, the chroot (i.e. the device rootfs). Multiple YAML layers can be grouped into customisable profiles with all layers being aggregated and merged by bdebstrap. Device and image layout hooks are supported which enable a user to do things like apply a static rootfs overlay directory hierarchy specific to their device, perform custom post-build operations, integrate with third-party build systems, and have complete control over the input genimage configuration file(s) used to produce the device disk image(s).
rpi-image-gen heavily favours a Debian-ish installation process where a temporary apt configuration is assembled and used to seed the chroot. However, unlike debootstrap, mmdebstrap provides many more configuration options which can be defined in the YAML layers read by bdebstrap. This make it possible not only to describe different variants of the base rootfs in a modular way (e.g. ranging from a 'standard' Debian install with Essential packages to a rootfs that consists of nothing more than busybox, libc and a few basic utils), but also to use multiple mirrors in which to retrieve packages, and be able to perform customisable operations at defined stages of chroot creation, e.g. at 'setup', 'extract', 'essential', 'customize' and 'cleanup' points.
rpi-image-gen runs as a regular user and does not require root privileges.
The rpi-image-gen tree structure contains individual sub-component directories, each of which has a specific purpose in the execution flow. Certain directories may be of particular interest when integrating with rpi-image-gen because it’s possible to set their location to within an externally provided directory. Doing so would, for example, enable a user to:
-
Use one of the in-tree image layouts, but use their own device directory (e.g to apply a custom product specific roofs overlay, execute hooks to perform bespoke operations).
-
Augment the YAML layer search with a directory of their choice (e.g. where they own all layers or to integrate with a third-party).
-
Define their own image partition layout, file system types, mount points, etc.
-
Use a custom profile which contains a combination of in-tree layers and/or their own (e.g. customise the packages to install, perform specific chroot operations, etc).
If rpi-image-gen is provided with a path to an external directory on the command line it will use this directory when resolving sub-components paths for
, config/
, profile/
and image/
and it will include the device/
sub-directory of this path in the list when searching for YAML meta layers defined by the Profile. This allows a user to provide external directories for these sub-components and for them to take precedence over the meta/
in-tree
equivalents. The external directory does not need to contain all sub-components; it may just contain custom hooks. If no external directory is provided, variable
defines the root directory of all sub-components.IGTOP
The config file is provided as a command line argument and is loaded from the external directory or in-tree. The config directory contains rpi-image-gen config files. Once the config file is loaded and parsed, sub-components for device, profile and image are searched for and their paths resolved, either from the external directory or in-tree. rpi-image-gen will always prioritise the external directory when searching. If not specified, a default config file is used. See
for further information../build.sh -help
A device directory contains assets which are specific to a class of hardware device. Additionally, rpi-image-gen adopts a sub-directory named
in various locations to denote the contents are related to run-time software on the device (e.g. rootfs-overlay).device
The image directory contains the necessary assets with which rpi-image-gen will use to create the output disk image(s) with a particular layout.
The meta directory contains YAML layers which are individual 'recipes' used for rootfs creation. The search path for additional meta layers can be augmented by usage of
and optional namespace.EXT_DIR
The profile is a plain text file which supports comments via
and where each line contains a YAML layer. The profile directory contains rpi-image-gen profiles. For example, if a Profile contained:#
# My device layer my/fantastic/layer
…
would be searched for.my/fantastic/layer.yaml
The SBOM directory contains assets specific to generation of the SBOM (see below).
Other sub-component directories exist for particular purposes and the path to some of those are propagated to all layers via dedicated variables. These are mainly to assist with scripting and template generation, e.g. when creating a systemd config fragment for a network interface, creation of RPi boot firmware config files, etc.
rpi-image-gen settings are aggregated as part of the input parameter assembly stage (see below) before commencing the build, and are translated into fully qualified variables with prefix
. Settings specific to a sub-component are denoted by a particular namespace prefix such as IGconf
or device
. Default settings specific to a sub-component are loaded from files named 'defaults' in the appropriate directory (e.g. image
). rpi-image-gen uses the appropriate sub-component namespace when reading these files which simplifies variables declared within. All of these settings are classed as 'options' or 'configuration variables' depending on the scope. A variable can be set by:build.defaults
The Environment
The config file (
)-c
The user options file (
)-o
Default settings
Rather than listing all available options here, please refer to the following files in the tree for further details:
Device configuration build-time settings, e.g. class, profile, locale
Auto-prefix: device
Default build options for all image layouts, e.g. version, compression
Auto-prefix: image
Default options for the build configuration, e.g. APT key directory, output artefacts directory
Auto-prefix: sys
The three main stages of execution in rpi-image-gen are described below.
Before commencing creation of the rootfs, the configuration of the system is assembled and translated into a set of environment variables established from different sources in the following order:
-
User option file settings (scope: explicit set and unset [see Note1 below])
-
Config file settings (scope: aggregate, set and unset)
-
System wide default settings (scope: aggregate, set and unset)
-
Selected Device and Image settings (scope: aggregate, set and unset)
-
User option file settings (scope: explicit set and unset)
The config file uses .ini file syntax (https://en.wikipedia.org/wiki/INI_file) to set
pairs inside a section.key=value
The user options file, and files containing default settings, are shell fragments containing declarative
pairs.key=value
Both file types support comments, and empty lines are ignored.
The loading of defaults settings or the config file will only set a variable if it is unset, i.e. their assignments are 'aggregated'. The user options file can override any variable. For example, if variable
was set in the Environment it would not be set by the loading of image defaults or the config file. It would, however, be set if the user options file assigned IGconf_image_name
to a non-empty string. If the user options file assigned it as empty it would be unset. When the declarative syntax is parsed, it does not support setting a variable to be empty. If a variable is assigned as empty it will be treated as being unset. Because of this, the user options file is re-read at the end of parsing to ensure it is able to override any previously set variables to be unset if necessary.image_name
As mentioned above, rpi-image-gen automatically applies a global prefix for all variables it parses from files and it applies a scope specific prefix to variables declared in config files and defaults. This 'translation' of variable parsing ensures variables automatically adhere to their defined scope. Unsupported config file sections are ignored. Variables set from the options file will be
prefixed and translated as-is. Translation includes the variable name being converted to lower case. A variable is said to be 'fully qualified' after parsing and when prefixed with IGconf
. All fully qualified variables are propagated and made available to YAML layers and hooks via their execution environment.IGconf
Note1: rpi-image-gen variables set from the Environment must be fully qualified otherwise they will be ignored. For example, to set the Device Class from the Environment, set variable
accordingly. To set it from the user options file, assign variable IGconf_device_class
.device_class
Note2: Exceptions to this are the individual
layer default files which are loaded at the same time as their corresponding YAML layer.meta
Understanding how to set and create variables is an important part of using rpi-image-gen because it forms the foundation of user customisation.
The following sections are supported when parsing a config file:
-
device
-
image
-
sys
-
sbom
-
meta
Variables declared outside the scope of these sections will not be read.
These are equivalent:
Default
IGconf_device_class=pi5
Config
[device] class=pi5
Options
DEVICE_CLASS=pi5
Processing the input sources and aggregating the variables this way allows the setting of a variable to override a previous setting. This may be particularly useful where customisation may only require the modification of a small number of variables compared with what was set previously. Furthermore, rpi-image-gen evaluates each variable after translation which means that variables set previously can be used to set other variables.
Default
IGconf_device_class=pi5 IGconf_image_suffix=img
Options
image_suffix=${IGconf_device_class}.bin
Yields:
IGconf_image_suffix=pi5.bin
Tip
|
Via the Options file or a supported Config file section, it is possible to define new custom variables. rpi-image-gen does not 'filter' variables or perform any sort of manipulation of their values/contents. The propagation of all variables, including user defined custom variables, may be beneficial to YAML layers and hooks. |
Environment
IGconf_device_user1=pi
Config
[device] user1=rasp [sys] flavour=debug debugger=autoattach debug_port=8080
Options
sys_debug_port= sys_debug_user=$IGconf_user1 sys_debugger=disabled
Yields
IGconf_sys_flavour=debug IGconf_sys_debugger=disabled IGconf_sys_debug_user=pi
Tip
|
It’s possible to include a Config file from another Config file provided both exist in the same directory. This may be useful in cases where there is a common configuration shared across variants. Rather than duplicating parts of a Config file in others, a common configuration can be included and any overrides or additional settings applied afterwards. |
Common configuration fragment base.cfg
[device] class=edge01 profile=v1 [image] layout=ROTA default_slot=A [sys] safe_boot=y boot_scheme=2
A developer may wish to use customisations provided by the layout
include base.cfg [image] resize=false appdata_size=1G
Note: Files must be included outside a config section. Including files from the user options file is not supported.
After assembling the environment variables from the input sources, rpi-image-gen reads the Profile and validates the path of each YAML layer before reading its default settings (if present) and assembling each layer as an argument to bdebstrap. A layer must adhere to bdebstrap
YAML syntax. Please refer to the bdebstrap man page for further details. It’s also worth pointing out that if authoring shell expressions in YAML, it may be necessary to adopt usage of particular block scalar styles to achieve newlines inside a block. For example:
- |- chroot $1 bash -- <<- EOCHROOT source /opt/device/setup.sh run_provisioning EOCHROOT
In addition to each YAML layer, all
variables are also passed to bdebstrap as arguments. This enables access to these variables from all layers.IGconf
A number of 'core hooks' are installed via bdebstrap command line arguments. These are run in addition to shell operations specified via by the YAML and provide the means for rpi-image-gen to execute common hooks at particular points in the creation of the chroot. They also serve the purpose of providing functionality at defined points that a custom layer(s) may need. For example, it may be desirable for all initramfs kernel images installed in the chroot to be rebuilt before the device file system images are generated. It’s unlikely that this operation needs to be done more than once during chroot creation, so there is a hook that runs
. This removes the need for a layer to invoke this command specifically and it helps to reduce potential complications that could arise depending on the aggregation order of layers. Because bdebstrap prioritises hooks which are provided as command line arguments, their execution point in the flow is deterministic.update-initramfs
rpi-image-gen runs bdebstrap in a new Linux namespace via
. This is required so that bdebstrap creates files with the correct ownership information, which is particularly important when creating a chroot that contains a user account. See podman-unshare(1) and user_namespaces(7) for further details.podman unshare
Before bdebstrap invokes mmdebstrap to begin creation of the chroot, it writes the fully aggregated and merged YAML to
as $IGconf_sys_outputdir
. This is an incredibly useful file because it’s essentially the 'recipe' for generating the chroot. It also creates a file called config.yaml
in the same directory which lists all the packages that were installed in the chroot.manifest
The mmdebstrap execution is not regarded as in the scope of this document. mmdebstrap follows the rules governed by its design and by the configuration provided to it by bdebstrap. In turn, the vast majority of bdebstrap configuration is derived from the YAML layers provided to it by rpi-image-gen. From this point of view, rpi-image-gen could be regarded as a thin toolkit wrapper with which to design a system purely from YAML constructs and a set of environment variables. The application of both these things, once understood, is very powerful and provides the means to create a completely customised rootfs.
For example, it is possible to create a usable, minimal chroot with only the following YAML layer:
--- name: bookworm-arm64-svelte mmdebstrap: architectures: - arm64 mode: auto variant: custom suite: bookworm mirrors: - deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware - deb http://deb.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware - deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware packages: - dpkg - busybox - libc-bin - base-files - base-passwd - debianutils
A SBOM should be a reproducible and immutable artefact of the build. Generation of it takes place at the end of the post-build stage after all other hooks have run, and immediately before
invocation. It is not advised to perform any operation on the rootfs that could affect the data encapsulated by the SBOM after it has been generated. The SBOM is generated using genimage
(https://github.com/anchore/syft) which will be downloaded if not installed on the host. The SBOM is written to the image output directory (syft
) and its output format is configurable via the IGconf_sys_outputdir
variable which can be set via the Config or Options file. The output format is currently the only option available to the user.SBOM_OUTPUT_FORMAT
For example, these are equivalent:
Config
[sbom] output_format=cyclonedx-json
Options
SBOM_OUTPUT_FORMAT=cyclonedx-json
The value of
is passed directly to syft. Please refer to the syft documentation for the supported output formats. Nothing in the rootfs is excluded from the SBOM scan. syft is invoked via IGconf_sbom_output_format
so it can execute with the same privileges as bdebstrap did when the rootfs was created.podman unshare
Utilisation of the SBOM is beyond the scope of this document. However, the following is offered as an example of how the SBOM could be used to generate a report of known CVEs using grype (https://github.com/anchore/grype):
$ grype --by-cve sbom:./work/deb12-arm64-min/artefacts/deb12-arm64-min.sbom -o json > vulnerabilities.json
After the chroot is successfully created (i.e. 'post-build'), file system overlays are applied (if present) before a number of hooks are run. These hooks provide additional integration points and provide a powerful way to perform custom operations on the chroot before image generation commences. One of hooks that runs at this point is the
hook. Similar to bdebstrap invocation, every hook is invoked in an environment where all pre-image
variables are available. Hooks are also invoked in the directory in which they exist, which means they can use relative paths to assets and sub-directories specific to their function.IGconf
The
hook is called by the core engine with defined arguments such as the path to the chroot, and the path to the genimage input directory. It is the responsibility of this hook to perform the tasks required to create the file(s) (i.e. the templates) in the genimage input directory so that genimage can process them to create the device image(s). Failure to generate a file(s) of the expected naming convention (pre-image
), or to use syntax that renders the file(s) unable to be read by genimage, will result in an error and no device image will be generated. Please refer to the genimage documentation for usage information and examples of creation templates, parameters, etc.genimage*.cfg
When invoking genimage, rpi-image-gen sets the value of
and --inputpath
to the same location so that it’s possible to reference one image from another. For example, image1 may be a squashfs image containing various assets that needs to be present on a file system in image2 so it can be mounted at boot.--outputpath
rpi-image-gen is again somewhat of a thin toolkit wrapper, this time leveraging genimage functionality with its documented arguments and parameters. Like bdebstrap, genimage is run via
which is necessary to enable file system creation utilities to incorporate correct and valid file system metadata from the chroot when creating the partition images, before assembling them into the final device image(s).podman unshare
An 'overlay' is a statically defined directory tree hierarchy that is copied into the chroot after bdebstrap completes execution. An overlay is identified by a directory named 'rootfs-overlay' and can reside in two independent locations:
-
<image dir>/device/rootfs-overlay
(Usage: optional) (Provided-by: image)Image layout specific root file system contents.
-
<device dir>/device/rootfs-overlay
(Usage: optional) (Provided-by: device)Device specific root file system contents.
Overlays are applied in the order they are listed above.
The hooks available for user customisation are documented below. If a hook is to be executed, it must have executable permissions for the user performing the build.
rpi-image-gen extends the support of bdebstrap hooks to image, device and external (
) directories. Hooks with filenames beginning with EXT_DIR
, setup
, essential
and customize
are supported and must exist in a sub-directory namedcleanup
within the directory in order for them to be run at the respective stage of chroot creation. Their file extension is ignored. Sub-directories are not traversed.bdebstrap
If
is installed in the chroot, rpi-image-gen extends the support of initramfs scripts and hooks to image and device directories via their sub-directory initramfs-tools(7)
. If present, the entire contents of this directory is recursively copied into the chroot. Mode and ownership attributes are preserved. Destination files will not be overwritten. rpi-image-gen performs this operation during the device/initramfs-tools
stage of chroot creation and guarantees it will take place after invocation of all image and device bdebstrap customize
hooks.customize
-
<image dir>/pre-build.sh
(Usage: optional) (Provided-by: image)Image layout specific pre-build operations.
-
<device dir>/pre-build.sh
(Usage: optional) (Provided-by: device)Device specific pre-build operations.
Both hooks are executed in the order they are listed above.
-
<image dir>/post-build.sh
(Usage: optional) (Provided-by: image)Image layout specific post-build operations.
-
<device dir>/post-build.sh
(Usage: optional) (Provided-by: device)Device specific post-build operations.
Both hooks are executed in the order they are listed above.
-
<device dir>/pre-image.sh
(Usage: Mandatory - see notes below) (Provided-by: device)Device owned pre-image generation operations.
-
<image dir>/pre-image.sh
(Usage: Mandatory - see notes below) (Provided-by: image)Image layout owned pre-image generation operations.
Only one of these hooks is executed. The device pre-image hook has priority. If it exists, it will be executed, else the image layout pre-image hook will be executed.
-
<device dir>/post-image.sh
(Usage: Optional - see notes below) (Provided-by: device)Device owned post-image operations.
-
<image dir>/post-image.sh
(Usage: Optional - see notes below) (Provided-by: image)Image layout owned post-image operations.
Only one of these hooks is executed. The device post-image hook has priority. If it exists, it will be executed. If it doesn’t and the image layout post-image hook exists, that will be executed. If neither exist, a default post-image hook will be executed. It is the responsibility of the post-image hook to perform all operations required to deploy artefacts to
, for example compressing image and SBOM.IGconf_sys_deploydir