Configure a Custom Script in the Initrd (Debian 13, GRUB)#
Overview#
Goal: Run your own shell script on the device during early boot (inside the initramfs), for Debian 13 **imageType: raw** images that use GRUB.
This tutorial supports two routes for Debian 13 raw + GRUB images:
Route 1: initramfs-tools flow using
image-templates/debian13-x86_64-bb-raw.ymlRoute 2: dracut module flow using
image-templates/debian13-x86_64-bb-dracut-raw.yml
Both routes achieve the same goal (run your custom logic during early boot), but they use different initrd tooling and file layouts.
Start from your image template: add systemConfig.additionalFiles (and related entries) that point at files in the repo. This page shows the YAML first, then the file contents and boot stages.
Full working examples:
image-templates/debian13-x86_64-bb-raw.ymlwithimage-templates/additionalfiles/debian13-bb/(initramfs-tools flow)image-templates/debian13-x86_64-bb-dracut-raw.ymlwithimage-templates/additionalfiles/debian13-bb-dracut/(dracut module flow)
Choose your route first#
Use exactly one route for your template customization.
Route |
Pick this when |
Start from this template |
|---|---|---|
Route 1: initramfs-tools hook + script |
You want the classic Debian |
|
Route 2: dracut module |
You prefer dracut module structure under |
|
Package switch required for each route#
Route 1 (
bb-raw, initramfs-tools): addinitramfs-toolsandcloud-initramfs-growroot; removedracutanddracut-coreif present.Route 2 (
bb-dracut-raw, dracut): adddracutanddracut-core; removeinitramfs-toolsandcloud-initramfs-growrootif present.For both routes: keep your GRUB and kernel packages (for example
grub-cloud-amd64andlinux-image-amd64).
Remove the current initrd stack with configurations commands#
If your base package set already includes the wrong initrd tooling, remove it explicitly in systemConfig.configurations.
Use one of these snippets based on your route:
If you are switching to Route 1 (initramfs-tools)#
Remove dracut packages and dracut module config:
configurations:
- cmd: "apt-get purge -y dracut dracut-core || true"
- cmd: "rm -f /etc/dracut.conf.d/*.conf"
If you are switching to Route 2 (dracut)#
Remove initramfs-tools packages and old hook/script paths:
configurations:
- cmd: "apt-get purge -y initramfs-tools cloud-initramfs-growroot || true"
- cmd: "rm -f /etc/initramfs-tools/hooks/hello /etc/initramfs-tools/scripts/init-bottom/hello"
Notes:
Keep only one initrd framework in the final image to avoid mixed behavior.
Keep your selected framework in
packages(initramfs-toolsfor Route 1, ordracut+dracut-corefor Route 2).|| truemakes the command safe when a package is not installed.
Route 1: initramfs-tools hook + boot script (bb-raw)#
Use debian13-x86_64-bb-raw.yml as an example.
Paths in **local** are relative to the directory that contains your template YAML (image-templates/… → additionalfiles/debian13-bb/…).
...
bootloader:
bootType: efi
provider: grub
packages:
- initramfs-tools
# … keep your other packages (see bb example)
additionalFiles:
- local: additionalfiles/debian13-bb/hello.sh
final: /usr/local/sbin/hello.sh
- local: additionalfiles/debian13-bb/hooks/hello
final: /etc/initramfs-tools/hooks/hello
- local: additionalfiles/debian13-bb/scripts/init-bottom/hello
final: /etc/initramfs-tools/scripts/init-bottom/hello
configurations:
- cmd: "chmod 755 /usr/local/sbin/hello.sh /etc/initramfs-tools/hooks/hello /etc/initramfs-tools/scripts/init-bottom/hello"
What each additionalFiles entry does#
|
|
Role |
|---|---|---|
|
|
Your script on the rootfs; the hook copies it into the initramfs. |
|
|
Runs when |
|
|
Runs on the device during early boot (execute step). |
**local:** file on the build host (must exist before compose).**final:** path on the image; ICT copies files here before GRUB install runs**update-initramfs**.
Rename debian13-bb in local paths to match your folder name. Keep the **final** paths as shown.
Field summary#
Template field |
Purpose |
|---|---|
|
Required. Installs hook, boot script, and your |
|
Include |
|
|
Route 2: dracut module (bb-dracut-raw)#
If you want the same early-boot marker behavior using dracut modules instead of initramfs-tools hooks, use debian13-x86_64-bb-dracut-raw.yml.
This variant keeps the same Debian 13 + GRUB + raw image target but changes how content is added to initrd:
Area |
|
|
|---|---|---|
Package focus |
|
|
Files copied by template |
|
|
Destination inside image |
|
|
Enable step |
Hook/script are discovered by initramfs-tools layout |
Add |
dracut template snippet#
packages:
- dracut
- dracut-core
# ... keep your GRUB/kernel packages
additionalFiles:
- local: additionalfiles/debian13-bb-dracut/modules.d/91hello/module-setup.sh
final: /usr/lib/dracut/modules.d/91hello/module-setup.sh
- local: additionalfiles/debian13-bb-dracut/modules.d/91hello/hello.sh
final: /usr/lib/dracut/modules.d/91hello/hello.sh
- local: additionalfiles/debian13-bb-dracut/modules.d/91hello/initqueue-sample.sh
final: /usr/lib/dracut/modules.d/91hello/initqueue-sample.sh
configurations:
- cmd: 'mkdir -p /etc/dracut.conf.d && echo ''add_dracutmodules+=" hello "'' > /etc/dracut.conf.d/91hello.conf'
- cmd: "chmod 755 /usr/lib/dracut/modules.d/91hello/module-setup.sh /usr/lib/dracut/modules.d/91hello/hello.sh /usr/lib/dracut/modules.d/91hello/initqueue-sample.sh"
dracut module files#
Create the module under:
image-templates/
additionalfiles/debian13-bb-dracut/
modules.d/91hello/
module-setup.sh
hello.sh
initqueue-sample.sh
module-setup.sh declares install logic for the module; hello.sh is the script executed from initrd.
Use the provided example files in image-templates/additionalfiles/debian13-bb-dracut/modules.d/91hello/ as the
reference implementation.
dracut initqueue stage example (initqueue-sample.sh)#
Use this when you want a script to run in dracut’s initqueue phase while root-device discovery is still in progress.
In this example module:
module-setup.shinstallsinitqueue-sample.shinto initrd as/sbin/initqueue-sample.sh.The script is registered in two hook points:
inst_hook cmdline 5 ...to seed an initial initqueue job early.inst_hook initqueue 90 ...to run in initqueue rounds.
Key behavior of initqueue-sample.sh:
Logs markers such as
WAIT_ROOT_EXECUTED,WAIT_ROOT_REQUEUE, andWAIT_ROOT_MAX_ROUNDS_REACHED.Reads
root=from kernel cmdline and resolves common forms (/dev/...,UUID=...,LABEL=...,PARTUUID=...).Requeues itself with
initqueue --onetimefor up to 3 rounds if the root block device is not yet present.Adds a settled readiness check with
initqueue --settled /bin/sh -c "test -b \"$ROOTDEV\"".
Add this file in your template when using the example module:
additionalFiles:
- local: additionalfiles/debian13-bb-dracut/modules.d/91hello/initqueue-sample.sh
final: /usr/lib/dracut/modules.d/91hello/initqueue-sample.sh
How to verify on boot:
Check
dmesgforWAIT_ROOT_markers.If available in the initramfs runtime, inspect
/run/initramfs/wait-root.log.
Why this is different from the ad-hoc install_items initqueue approach#
You may have seen another pattern where files are copied into initramfs paths and then listed in dracut
install_items so they get packed. That method can work, but it is different from this tutorial’s approach.
This tutorial uses a dracut module + template-declared files approach, which gives you:
Reproducibility in ICT: all inputs are declared in the image template (
additionalFiles) and tracked as part of the image definition, instead of relying on one-off build-host actions.Cleaner dracut lifecycle integration:
module-setup.shdefines exactly which hooks are used (cmdline,initqueue,pre-mount) and in what order, so behavior is explicit and reviewable.Correct behavior for event-driven initqueue: dracut
initqueueis event/timing driven, so a sample script must be registered as a hook and be able to requeue/wait for readiness signals; only listing files withinstall_itemspackages content into initrd but does not model this runtime event flow by itself.Better portability across build environments: module files live in the repo and are copied into the target image layout; the flow does not depend on host-specific runtime state.
Easier maintenance: script logic (for example, requeue rounds and root-device checks in
initqueue-sample.sh) stays in one place, rather than being split across ad-hoc file install and pack lists.
In short: both methods can place a script in initrd, but the module-based method used here is preferred for declarative, version-controlled image builds in ICT.
Build and check#
Build the tool, install prerequisites, validate, and compose the image using the README.md (Quick Start and Compose an Image).
Pick one template based on your chosen route:
image-templates/debian13-x86_64-bb-raw.ymlimage-templates/debian13-x86_64-bb-dracut-raw.yml
Run validate if you use it (see Usage Guide). If validate warns about a missing local file, fix the path or add the file under Where to put files in the repo.
On the device:
Boot the flashed raw image.
Use serial console if your template sets
console=ttyS0,...on the kernel cmdline.Look for your message during early boot, or run:
dmesg | grep -i hello
Optional checks on a machine with the image mounted:
initramfs-tools flow:
lsinitramfs /boot/initrd.img-* | grep hellodracut flow:
lsinitrd /boot/initrd.img-* | grep 91hello
Where to put files in the repo#
Layout |
|
|---|---|
Next to templates (recommended) |
|
Debian OS defaults tree |
|
Example tree:
image-templates/
debian13-x86_64-bb-raw.yml
additionalfiles/debian13-bb/
hello.sh
hooks/hello
scripts/init-bottom/hello
Supporting files (content to create)#
hello.sh#
#!/bin/sh
echo "hello from initrd (debian13-bb)" >/dev/kmsg
Use /dev/kmsg or logger so output appears on serial or in dmesg.
hooks/hello#
Runs during **update-initramfs** on the build machine; copies hello.sh into the initramfs image.
#!/bin/sh
PREREQ=""
prereqs() { echo "$PREREQ"; exit 0; }
case "$1" in
prereqs) prereqs; exit 0 ;;
esac
. /usr/share/initramfs-tools/hook-functions
copy_exec /usr/local/sbin/hello.sh /usr/local/sbin/hello.sh
scripts/init-bottom/hello#
Runs on the device in the initrd (default: late in initramfs, before switch to the installed system).
#!/bin/sh
PREREQ=""
prereqs() { echo "$PREREQ"; exit 0; }
case "$1" in
prereqs) prereqs; exit 0 ;;
esac
if [ -x /usr/local/sbin/hello.sh ]; then
/usr/local/sbin/hello.sh
fi
Make all three executable (chmod 755) or use the configurations line in the template.
Every initramfs-tools hook and script must start with the **PREREQ / prereqs** block shown above.
Choosing an initramfs-tools boot stage#
The hook always runs at image build time when the initramfs is generated. To change when your script runs on the device, move the runner to a different directory under scripts/ (and update the template final: path).
|
When it runs (plain language) |
Good for |
|---|---|---|
|
Late initrd, after root handling, before switch_root. Default in the example. |
Logging, checks before the real OS starts. |
|
Early, before mounting root |
Very early setup |
|
Before local root mount |
Block device ready, root not mounted yet |
|
After local root mount steps |
Work that needs the root filesystem mounted in initrd |
There is no 99 prefix naming rule; the file name (hello) is arbitrary.
Troubleshooting#
Problem |
Check |
|---|---|
No output on boot |
All three |
Validate / build skips a file |
Wrong |
Script on disk but not in initrd |
Missing |
Script in initramfs but never runs |
Missing or wrong |
Wrong image type or bootloader |
This guide targets Debian 13 raw with |