dracut and initramfs Generators

The kernel unpacks a cpio archive into rootfs and execs /init — but something in userspace has to build that archive with exactly the right drivers, libraries, and scripts to find your particular root filesystem. That “something” is an initramfs generator. The dominant cross-distribution tool is dracut, “a low-level tool for generating an initramfs/initrd image” (dracut.8) used by Fedora, RHEL, SUSE, and Gentoo; the two main alternatives are mkinitcpio (Arch, configured by a HOOKS array) and initramfs-tools (Debian/Ubuntu, driven by update-initramfs). All three solve the same hard problem — discover the chain of drivers from kernel module to root filesystem (storage controller → partition table → LVM → dm-crypt → filesystem) and pack precisely those into the image — and all three expose the same central tradeoff: a host-only image (tailored, tiny, but tied to this exact machine) versus a generic image (fat, portable, boots anything). Get the chain wrong — forget the NVMe driver, omit the dm-crypt module — and the result is an unbootable system dropping you to a dracut:/# emergency shell.

This note is the tooling companion to The initramfs and initrd (what the kernel does with the archive) and initramfs vs initrd History (why it’s a cpio archive at all). For what the resulting /init does at runtime, see The Early Userspace init Script and Mounting the Real Root Filesystem.

Mental Model

An initramfs generator is a dependency-closure computer plus a packer. Given “this machine’s root is on /dev/mapper/vg-root, which is an LVM logical volume, on a dm-crypt LUKS container, on an NVMe partition, formatted ext4,” it must transitively collect: the NVMe block driver (nvme.ko), the partition/GPT support, the device-mapper core, the LVM2 userspace tools and their shared libraries, the dm-crypt/cryptsetup tooling, the ext4 filesystem driver, plus the glue scripts and udev rules to assemble all of it in the right order at boot. Then it packs them into a newc-format cpio archive, compresses it, and writes it next to the kernel.

The crucial design axis is how much it assumes about the target machine:

flowchart TB
  ROOT["Goal: boot THIS root<br/>NVMe → LUKS → LVM → ext4"]
  ROOT --> HO["HOST-ONLY image<br/>(dracut -H / mkinitcpio autodetect /<br/>initramfs-tools MODULES=dep)"]
  ROOT --> GEN["GENERIC image<br/>(dracut -N / no autodetect /<br/>MODULES=most)"]
  HO --> HO1["Only the drivers + modules<br/>THIS box needs"]
  HO1 --> HO2["Tiny + fast<br/>BUT: tied to this hardware,<br/>this controller, this root fs"]
  GEN --> GEN1["Every plausible driver<br/>+ module"]
  GEN1 --> GEN2["Large + slower<br/>BUT: boots on new hardware,<br/>cloned disks, recovery media"]

The host-only vs generic decision. What it shows: both modes start from the same goal but resolve the driver set differently — host-only walks the current machine’s actual storage stack and includes only that; generic includes a broad superset. The insight to take: the choice is a bet about whether the hardware will change. Host-only is correct for a fixed appliance or a personal laptop you reinstall rarely; generic is correct for installation media, golden images cloned across fleets, or any disk that might be moved to a different controller. Distributions pick defaults accordingly: Fedora ships host-only by default, Debian’s installed system ships MODULES=most (generic) by default.

dracut — the Event-Driven, Modular Generator

dracut’s job, in its own words, is to create “an initial image used by the kernel for preloading the block device modules (such as IDE, SCSI or RAID) which are needed to access the root filesystem, mounting the root filesystem and booting into the real system” (dracut.8, Ubuntu). Its design philosophy distinguishes it from the older, distribution-specific mkinitrd scripts it replaced: dracut is modular and event-driven.

The module system

“dracut uses a modular system to build and extend the initramfs image. All modules are located in /usr/lib/dracut/modules.d” (dracut.modules.7). A dracut “module” is not a kernel module — it is a directory of shell scripts that knows how to install one feature into the initramfs (the lvm module, the crypt module, the nfs module, the systemd module, etc.). Each module carries a module-setup.sh with a small contract of functions:

  • check() — “called by dracut to evaluate the inclusion of a dracut module in the initramfs.” It returns 0 to request inclusion, 1 to decline, or 255 to mean “include me only if something explicitly asks for me” (conditional). In host-only mode, check() is where a module inspects the running system — e.g. the lvm module’s check() returns success only if LVM is actually in use.
  • depends() — prints the names of other dracut modules this one needs, so dracut can compute the transitive closure (the crypt module depends on dm, which depends on udev-rules, and so on).
  • install() — installs the userspace pieces: binaries, libraries, config, udev rules, and hook scripts, using helpers like inst_multiple, inst_simple, and inst_hook.
  • installkernel() — “all kernel related files should be installed” here, i.e. the .ko kernel modules the feature needs.

Event-driven boot via udev

The “event-driven” half is what dracut puts inside the image. Rather than a fixed sequential script that assumes devices appear in a known order, dracut’s /init relies on udev and a retrying work queue. Modules “can insert custom script at various points, to control the boot process. These hooks are plain directories containing shell scripts ending with ‘.sh’, which are sourced by init” (dracut.modules.7). The boot proceeds through named phases:

  • cmdline — parse the kernel command line, set the root= variable and friends.
  • pre-udev / pre-trigger — prepare before and configure udev.
  • initqueue — the central retry loop: as udev fires add/change events for newly-appeared block devices, jobs are pushed onto the initqueue; the loop keeps running settle/retry passes until the root device materializes (the LUKS container is unlocked, the LVM volume group is activated, the RAID array assembles).
  • pre-mount / mount — once the root device exists, mount it.
  • pre-pivot / cleanup — final fixups, then switch_root onto the real root.

This matters because real storage is asynchronous: an iSCSI target, a USB controller settling, a multipath device, or a LUKS prompt arrives whenever it arrives. The udev-driven initqueue copes with that ordering uncertainty far better than a linear script. As the man page summarizes the whole point: “All finding of the root device happens in this early userspace” (dracut.8, Ubuntu).

Host-only vs generic in dracut

dracut’s signature feature is the host-only mode, controlled by hostonly="yes" in config (the upstream default is yes) or -H/--hostonly on the command line, versus -N/--no-hostonly:

“If you want to create lighter, smaller initramfs images, you may want to specify the --hostonly or -H option. Using this option, the resulting image will contain only those dracut modules, kernel modules and filesystems, which are needed to boot this specific machine. This has the drawback, that you can’t put the disk on another controller or machine, and that you can’t switch to another root filesystem, without recreating the initramfs image.” (dracut.8)

Without host-only, dracut builds “a general purpose initramfs image, with all possible functionality resulting of the combination of the installed dracut modules and system tools.” There is even a middle setting: hostonly_mode="sloppy" (the default, more portable — includes a bit extra for safety) versus "strict" (minimal, least portable) (dracut.conf.5).

Configuration: dracut.conf

dracut reads /etc/dracut.conf plus drop-ins in /etc/dracut.conf.d/*.conf (also /usr/lib/dracut/dracut.conf.d/ for vendor defaults), processed in alphanumeric order with /etc/ overriding /usr/lib/ (dracut.conf.5). The key options, each line-commented:

# /etc/dracut.conf.d/90-myhost.conf
hostonly="yes"                      # build only what THIS machine needs
hostonly_mode="strict"             # minimal closure (vs "sloppy" default)
add_dracutmodules+=" crypt lvm "   # force-include the crypt and lvm features
omit_dracutmodules+=" plymouth "   # drop the boot splash to slim the image
add_drivers+=" nvme "              # belt-and-suspenders: force the NVMe .ko in
filesystems+=" ext4 "              # exclusively include this fs module
compress="zstd"                    # cpio compressor (cat|gzip|xz|lz4|zstd|...)
  • add_dracutmodules+= / omit_dracutmodules+= add or remove dracut feature modules; dracutmodules+= (no add_) forces dracut to include only the listed modules.
  • drivers+= exclusively includes named kernel modules (no .ko suffix); add_drivers+= adds to the auto-detected set. The distinction (drivers = exclusive, add_drivers = additive) is a frequent footgun.
  • filesystems+= controls which filesystem .kos are bundled; compress= selects the archive compressor (“cat|bzip2|lzma|xz|gzip|lzop|lz4|zstd”) (dracut.conf.5). Note the leading/trailing spaces inside the quotes — dracut config values are space-separated lists and += appends, so the spaces matter.

Building and naming:

dracut                                  # rebuild for the running kernel, default path
dracut --force                          # overwrite an existing image (-f)
dracut foobar.img 6.12.0-1.fc41         # explicit output name + --kver
dracut -H -f /boot/initramfs-6.12.img 6.12.0

--force/-f overwrites; --add/-a and --omit/-o adjust the module set per-invocation; --kver (or the positional version) picks the kernel (dracut.8).

Introspection: lsinitrd

Because the image is opaque (compressed cpio), dracut ships lsinitrd to inspect it: “To see the contents of the image created by dracut, you can use the lsinitrd tool” (dracut.8, Ubuntu). Running lsinitrd /boot/initramfs-6.12.img prints the image’s metadata header (the dracut modules and dracut version used, the kernel-command-line arguments embedded, the compression), the list of bundled kernel modules, and the full file tree of the cpio archive. lsinitrd -f /etc/cmdline.d/foo.conf <image> extracts a single file’s contents. It is the first tool to reach for when diagnosing “why didn’t my driver get included?” — you look for the .ko in the listing, and if it is absent you know the generator never packed it.

Uncertain

Verify: the exact field-by-field layout of lsinitrd default output (the precise header lines and ordering). Reason: the dedicated lsinitrd.8 man page returned HTTP 404 from man7.org during this task, so the output description above is reconstructed from the dracut.8 reference plus general knowledge, not quoted from the lsinitrd man page directly. To resolve: run lsinitrd --help / man lsinitrd on a dracut-based system, or read lsinitrd in the dracut source. The existence and purpose of lsinitrd is confirmed by dracut.8; only the precise output format is unpinned. uncertain

Alternatives and When to Choose Them

mkinitcpio (Arch Linux)

mkinitcpio “creates an initial ramdisk environment for booting Linux”; the initramfs is “a very small environment (early userspace) which loads various kernel modules and sets up necessary things before handing over control to init” (mkinitcpio man page summary). Its defining characteristic is the HOOKS array in /etc/mkinitcpio.conf (drop-ins in /etc/mkinitcpio.conf.d/). Hooks are “bash scripts sourced during runtime to add modules, binaries, and other files to the image,” and — crucially — order matters, because each hook’s runtime script executes in array order at boot. A representative encrypted-LVM configuration:

# /etc/mkinitcpio.conf
MODULES=(nvme)                       # force-load these kernel modules
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap \
       consolefont block encrypt lvm2 filesystems fsck)

Reading the hook chain (ArchWiki, mkinitcpio):

  • base — core utilities and the init framework (busybox-based by default).
  • udev — device-event management (the busybox/udev path; the systemd hook replaces both base and udev with a systemd-in-initramfs environment).
  • autodetect — the host-only mechanism: it scans the running system and trims the module set to only those drivers actually in use, dramatically shrinking the image. It must come early so later hooks see the reduced set.
  • modconf — applies /etc/modprobe.d config inside the image; kms pulls in graphics/DRM modules for early kernel mode-setting.
  • block — block device modules; filesystems — filesystem drivers; fsck — the fsck tools to check the root before mount.
  • keyboard / keymap / consolefont — input and console for typing a passphrase.
  • encrypt (or systemd’s sd-encrypt) — unlocks the LUKS/dm-crypt container; lvm2 activates LVM volume groups.

The MODULES=(...) array force-includes named kernel modules regardless of autodetect; BINARIES and FILES arrays force-add userspace binaries and arbitrary files. Images are built per preset (/etc/mkinitcpio.d/linux.preset) via mkinitcpio -p linux, or all presets with mkinitcpio -P.

The relationship to dracut: mkinitcpio’s autodetect is conceptually dracut’s hostonly, and omitting autodetect yields a generic, portable image (the Arch “fallback” preset deliberately drops autodetect for exactly this reason — it is the recovery image that boots when the trimmed default cannot). As of recent releases Arch also ships a dracut package as an officially supported alternative generator, so the two coexist on the same distribution.

initramfs-tools (Debian / Ubuntu)

initramfs-tools is the Debian/Ubuntu generator. Day-to-day you never call its builder directly; you run update-initramfs (“automatically regenerates initramfs images”; package maintainers invoke it via dpkg triggers) which wraps mkinitramfs, the actual builder (initramfs-tools.7). Configuration lives under /etc/initramfs-tools/ with conf.d/ snippets, modules (extra modules to force-include), hooks/ (build-time scripts), and scripts/ (boot-time scripts, organized into phase directories: init-top, init-premount, local-top, local-premount, local-bottom, init-bottom).

The host-only-vs-generic knob is the MODULES setting in initramfs.conf (initramfs.conf.5):

  • MODULES=most — “adds most file system, all ata, sata, scsi and usb drivers.” This is the generic option (the Debian default for installed systems): a broad superset that boots on essentially any common hardware, at the cost of size.
  • MODULES=dep — “tries to guess which modules are necessary for the running box and only adds those modules.” This is the host-only option: smaller, faster, but tied to the current hardware.

So Debian’s most ≈ dracut’s --no-hostonly, and dep ≈ dracut’s --hostonly/-H. The default difference (Debian generic, Fedora host-only) reflects each distribution’s priorities — Debian images frequently get cloned across machines, so generic is the safer default.

Newer entrants and the systemd direction

Beyond the big three there are minimalist generators like booster (Go, fast, declarative). More structurally, systemd has standardized the generator interface through kernel-install: when a new kernel is installed, kernel-install runs plugins in /usr/lib/kernel/install.d/*.install and /etc/kernel/install.d/, and selects the generator via the environment variables $KERNEL_INSTALL_INITRD_GENERATOR and $KERNEL_INSTALL_UKI_GENERATOR (settable as initrd_generator= and uki_generator= in install.conf) (kernel-install.8). Plugins drop their output (an initrd* file) into the $KERNEL_INSTALL_STAGING_AREA, and a later plugin installs it.

This dovetails with Unified Kernel Images (UKIs): with layout=uki in install.conf, the pipeline bundles the kernel, the generated initramfs, and the kernel command line into a single signed EFI binary placed under $BOOT/EFI/Linux (implemented by the 90-uki-copy.install plugin) (kernel-install.8). systemd’s own ukify “cannot generate an initramfs on its own,” so it delegates to dracut (or mkinitcpio/booster) to produce the initramfs, then embeds it — which is why dracut is increasingly positioned as systemd’s preferred initramfs generator: dracut both produces the cpio archive and, via its dracut --uefi mode and the dracut-ukify integration, can build the UKI directly. See Unified Kernel Images for the UKI format itself.

Failure Modes

“Forgot the storage driver → unbootable.” This is the canonical initramfs failure. If the generator did not include the kernel module for your root’s storage controller (the NVMe driver, a vendor RAID/megaraid module, the virtio-blk/virtio-scsi driver in a VM), the early-userspace /init never sees the root block device. The symptom is the boot stalling and dropping into an emergency shell — dracut:/#, or (initramfs) for initramfs-tools, or a Recovery prompt — typically after a message about waiting for the root device or Warning: /dev/mapper/... does not exist. The cause is almost always one of: (a) host-only mode built on hardware different from the boot hardware (disk moved to a new controller, image cloned to dissimilar hardware), (b) autodetect/MODULES=dep ran when the device was not attached, or (c) a kernel upgrade where the new kernel’s module set diverged and the image was not regenerated. The fix: boot a rescue image (the generic fallback), chroot, and regenerate — dracut -f, mkinitcpio -P, or update-initramfs -u -k all — ideally with the missing driver forced (add_drivers+=, MODULES=(...), or modules file).

The autodetect-too-early / hardware-not-present trap. mkinitcpio’s wiki documents a sharp edge: because autodetect trims to currently connected hardware, “for keyboards that are not connected to the PC at initramfs generation time, you need to place the keyboard hook before the autodetect hook,” or only the parts for the currently-connected hardware survive (ArchWiki, mkinitcpio). The same logic bites USB storage, USB keyboards needed for a LUKS passphrase, and any device absent at build time. The general rule: anything host-only detection might miss must be force-included (a module list, a hook before autodetect) or you must use a generic image.

Stale image after a config change. Editing dracut.conf/mkinitcpio.conf/initramfs.conf does nothing until you rebuild. Forgetting the rebuild — adding crypt to dracut but not running dracut -f — produces a config that looks right but an image that is unchanged. lsinitrd (dracut) or lsinitcpio (mkinitcpio) is how you confirm what is actually in the image versus what the config says should be.

Host-only image on a cloud golden image. Cloning a host-only image across a fleet of subtly different VM types (different virtio versions, different disk controllers) is a classic outage: the golden image boots on the build host and on identical instances, then fails on the one instance type with a different paravirtual driver. The lesson generic-by-default distributions internalized: for any image that will run on hardware other than the build host, build generic.

See Also