Adapting a shipped recipe
Every shipped recipe is a point across the build axes — device, kernel, u-boot series, suite, features, layout — and adapting one means naming a different point. Most of what people want from an image is reachable without writing a file at all, and the rest is a few lines of TOML in a directory of your own.
This tutorial works up the four levels of change, cheapest first. It assumes you have already built a shipped recipe once (Getting started).
| What you want to change | How | What it writes |
|---|---|---|
The artifact’s geometry or where it lands (--layout, --image-size, --out-dir) | flags on build | nothing — the lock is untouched |
| Which features are in the image | update <recipe> --feature …, then build the variant reference | a variant lock beside the recipe |
| Adding an app or capability of your own | a features/<name>.toml of your own — no code | your feature, selectable at once |
| Suite, kernel, u-boot series, board profile, locale, timezone, keymap | a recipe file of your own | your recipe plus its lock |
| Who can log in: SSH keys, the sudo policy, the generated password’s length | a recipe file of your own, or a base.toml in your overlay for every image | your recipe plus its lock |
| A hardware fact of the board | a device file, usually extends another | your device plus a recipe |
Where your files go
Put anything you author in a directory of your own and pass --overlay:
mkdir -p ~/my-boards/recipes/asus-c201
boot2deb --overlay ~/my-boards resolve asus-c201/forky
An overlay holds the same devices/ socs/ kernels/ features/ recipes/ structure as the
shipped root, wins over it name-for-name, and takes the locks that update writes — so
there is nothing to fork and nothing of yours to rebase when the shipped tree moves. See
Overlays. The rest of this page assumes --overlay ~/my-boards
wherever you author something; drop it if you are contributing the result back in-tree.
Level 1: change the artifact, not the build
build re-reads the lock for every pinned input, but the image’s shape is not a pinned
input, so two axes are overridable at build time:
# A bootloader-only image plus a separate rootfs image, for a two-medium install.
boot2deb build turing-rk1/forky --layout split
# A bigger artifact. The rootfs grows to fill its medium on first boot regardless, so
# this bounds the file you flash, not the installed system.
boot2deb build turing-rk1/forky --image-size 4G
# Keep the raw image beside the .xz, and put artifacts somewhere else.
boot2deb build turing-rk1/forky --keep-raw --out-dir /mnt/scratch
Artifacts are named for the whole build point — device and recipe
(turing-rk1/forky → turing-rk1-forky.img.xz, turing-rk1-forky-rootfs.tar,
turing-rk1-forky-idbloader.img) — so several recipes can share one --out-dir
without one build’s rootfs or bootloader being folded into another’s image.
Nothing here is recorded as a new point: the lock, the pins, and the support claim all
still describe the recipe you named. --stage narrows the run to one stage, which is how
you iterate on the image assembly without recompiling a kernel — see the
CLI reference.
Level 2: compose features
Features are the one axis you can select without authoring anything. update pins the
selection as a variant of the recipe, and the variant is then a build reference like any
other:
boot2deb list-features
boot2deb update turing-rk1/forky --feature media-accel-rockchip --feature jellyfin
boot2deb build turing-rk1/forky+media-accel-rockchip+jellyfin
The variant gets its own lock, its own solved package manifest, and its own work directory and image identity, so it never lands on the recipe’s artifacts. Three things worth knowing before you rely on it:
- The selection replaces the recipe’s
featureslist, it does not add to it. Name everything you want. - Order matters and is preserved — kconfig fragments and patch series compose in
selection order, so
a+bandb+aare two different builds and two different references. - A variant carries no support claim. The claim belongs to the recipe; a different
feature set is a different build. Variants appear in neither
list-recipesnor the support matrix.
Reference: A feature selection is a build point.
Authoring a feature of your own
A feature is entirely configuration. Drop a features/<name>.toml into your overlay
and it is selectable immediately — it appears in list-features, and
<recipe>+<name> resolves. There is nothing to register and no code to change.
# ~/my-boards/features/navidrome.toml
description = "Navidrome music server"
packages = ["navidrome"]
[[apt_sources]]
name = "navidrome"
uri = "https://apt.example.com/debian"
suite = "trixie"
components = ["main"]
signed_by = "navidrome.gpg"
boot2deb build turing-rk1/forky+navidrome --overlay ~/my-boards
Config files ride alongside in features/<name>/overlay/, laid into the rootfs after
every package so they win over what the packages shipped. Use
features/<name>/overlay-pre/ for the rarer case where a package’s own maintainer
scripts have to see the file while they run — see Overlays.
Four things a feature can declare beyond its packages:
[[apt_sources]]for an app that is not in the Debian mirror. Its signing key must be vendored asblobs/keyrings/<signed_by>in your overlay or the shipped tree; resolution refuses the build if it is missing rather than trusting the repo blindly. The source stays configured on the device, soapt upgradetracks the vendor’s releases.requires_soc/requires_archto gate a feature on hardware it needs. Empty means any, which is right for a portable application.conflictsfor features that cannot coexist. The check is symmetric, so declaring it on either side is enough.caveatsfor what the feature does not deliver. These follow the feature into every recipe that composes it and print at the end of a build, which is what a limitation caused by a capability should do.
The one thing you cannot do from config is teach the recipe schema a new block: the
[[data_volumes]] list is paired with the data-volume feature by name in the
builder, and it is the only feature the code knows by name. Everything else about the
axis is discovered from the directory.
Level 3: author your own recipe
Any axis that is not a feature — the suite, the kernel definition, the u-boot series, the depthcharge board profile, the locale, timezone, and keymap — is pinned by the recipe file, so changing one means a recipe of your own. That is deliberately cheap: a recipe states its deltas from the device’s defaults and nothing else.
This is also what resolve tells you to do. Preview a choice with the matching flag and
it prints the recipe to write, with the keys already filled in:
boot2deb resolve asus-c201/forky --suite trixie --keymap de
The worked example is the ASUS C201 on trixie, localized for Germany.
~/my-boards/recipes/asus-c201/trixie-de.toml:
device = "asus-c201"
suite = "trixie"
locale = "de_DE.UTF-8"
timezone = "Europe/Berlin"
keymap = "de"
Five lines is the whole file. Everything else — the kernel (Debian’s own linux-image-armmp
for this board), the boot method, the layout, the image size — comes from
devices/asus-c201.toml. Resolve it to see the point in full, including which values came
from where:
boot2deb --overlay ~/my-boards resolve asus-c201/trixie-de
device : asus-c201 — ASUS Chromebook C201 (RK3288, google,veyron-speedy)
kernel : debian-armmp (distro-package)
suite : trixie
locale : de_DE.UTF-8 (generated: de_DE.UTF-8, en_US.UTF-8, en_GB.UTF-8, fr_FR.UTF-8, ...)
timezone : Europe/Berlin
keymap : de [pc105]
board profile: speedy
...
resolve also runs the coherence preflight — image geometry, fragment existence, feature
compatibility, apt keyrings — so a green resolve means the point is buildable, not merely
parseable. Then pin it and build:
boot2deb --overlay ~/my-boards update asus-c201/trixie-de
boot2deb --overlay ~/my-boards build asus-c201/trixie-de
update writes trixie-de.lock next to your recipe, inside your overlay. It needs no
--kernel-ref here because this board installs Debian’s kernel: there is no git ref to
resolve, and the exact package version is pinned by the solved package manifest instead.
A board that compiles a kernel wants --kernel-ref <tag> on its first update, after which
update inherits the previous ref.
Which locale keys to set where is its own topic — the layered defaults, and what each key does to the running system, are on Locale, timezone, and keyboard.
Level 4: adapt the board itself
If what you need to change is a hardware fact — a different DTB, different DRAM timing, a
peripheral enabled for bring-up — that is the device layer, not the recipe. A board that is
another board with one difference uses extends:
# ~/my-boards/devices/my-rk1-variant.toml
extends = "turing-rk1"
description = "Turing RK1 with a different DDR fitting"
hostname = "rk1-variant"
[rkbin]
tpl = "rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.19.bin"
extends inherits the parent device’s keys and its overlay/ file tree, so the parent’s
driver tuning, units, and keymaps reach your image, and you override any single file by
shipping your own copy at the same path. Most arrays replace rather than append across
the merge, so restate any list you extend — the exceptions are the five that describe the
board (caveats, expect, nonfree_firmware_packages, packages, exclude), which
accumulate. Details:
A variant board extends another.
Reach for a variant device only when the difference needs a device-layer field — a device tree, a DTB name, the DRAM blob. A capability whose whole expression is packages, kernel config, and a patch series is a feature instead, and features compose a-la-carte where a variant device does not.
Beyond that — a board that is genuinely new, a SoC that is not here yet, a device tree that
is not upstream — is the bring-up track: Adding a board,
which starts with boot2deb new-device scaffolding the files for you.
When the change is worth a claim
A recipe is the unit that carries a [support] claim, and a claim is a statement about
hardware. If you have booted your adapted image and want to say so — in your own overlay or
in a contribution — see Authoring a recipe.