#linux
Tuesday, March 24, 2026

I like my kernel simple and lean, with just the configuration I need — kernel modules disabled, no initramfs, and all drivers and firmware built into the kernel image itself. Why, I would even forego sophisticated bootloaders like GRUB, relying instead on EFI stub to load my kernel directly from the UEFI firmware during the boot process. In my most recent setup, however, I decided to set up a LUKS2-encrypted btrfs filesystem on my main disk, and this forced me into having an initramfs.

So what is an initramfs? Basically, when the kernel starts up, it may be provided with an “initial RAM filesystem” in lieu of accessing the root filesystem from disk. This pseudo root filesystem is just a single archive with a script called “init”. The archive may include additional binaries and libraries that are needed by this init script early in the boot process. In my case, for instance, I needed to use the cryptsetup utility to ask for a password and unlock the disk, in order for the kernel to gain access to it. There is simply no other way for the kernel to access the decrypted contents of the disk and begin the service initialization process 1.

Tools like dracut can help you create an initramfs, but the results tend to be ugly and bloated as they cater to the common denominator. To avoid this, I decided to create my own initramfs. This turned out to be a rather simple process.

Conceptually, there are two parts to it. First, we need an init script that acts as the entry point and executes during the early boot process. Second, we need a build script to create an archive file with this init script, along with a filesystem hierarchy structure and required binaries and libraries.

In the init script, I used a busybox binary to provide the shell.

cryptsetup luksOpen "$ROOT_DEV" "$CRYPT_ROOT"`

The snippet above is at the heart of the script — it gets invoked up to 3 times in case the user enters the wrong password — with additional logic to mount the btrfs filesystem and resume correctly from hibernation. The final step is to switch to the full-fledged root filesystem and start the real init process from files on disk. One perk of this setup is that I can inject additional goodies into the script. Observe that I set the keyboard backlight and display brightness as part of the script. What’s more, I even managed to coax Claude into churning out a small binary called gamma to set the display to a ‘warm’ color from the very beginning.

The job of the build script is straightforward: bundle together all the binaries needed by the init script, along with the libraries they depend on (if dynamically linked). These binaries are busybox, gamma and cryptsetup. Of these, the first two are statically linked and require no additional files. For the latter, the script includes some logic to recursively scrape the system for the required libraries, placing them in the right directories and linking them just as they are on the live system. The final step is to convert the working directory into an archive using cpio and compressing it with zstd (which has support built into the kernel). No kernel modules are required to be loaded since drivers for display, disk and other essential components are built into the kernel itself.

1

Technically, I could use the GRUB bootloader to decrypt the disk for me before executing the kernel, but until the recent version 2.14, GRUB did not support the fast and modern Argon2 key derivation function.)