Messing with your initramfs - Alpine edition

This is part of two of a series of posts about the Linux initramfs. You can find the first post here.

Before we get back to Hare programming, I’d like to share some more insights into how an initramfs works in general. In the last post, we were building on a distribution-provided image. To better understand what that image does, let’s quickly build our own, because why not?

SPOILER: the outcome will not be a suitable replacement for your distribution’s initramfs, for various reasons. But it boots, so it’s good enough to learn a bit from it.

As I briefly pointed at in the last post, the contents of the initramfs look suspiciously close to a proper Linux system. That is, because it mostly is. So let’s take a look at what it takes to make a “regular” system work as an initramfs - by building our own!

The base system

You can, in fact, build a working initramfs based on just about any Linux distribution. However, for reasons that will become apparent later on, you want something that has a small footprint, both in overall size and memory usage. A popular choice is busybox as the foundation. You can see where this is going: Alpine Linux is based on busybox, so it makes for a perfect base system!

So let’s start with a basic Alpine Linux system. The goal is to create one inside a directory, much like you would use e.g. for a chroot environment. You could build one whichever way you prefer (e.g. alpine-make-rootfs or just apk). I will be using my very own makeimg for demonstration, but I will try to point out the relevant details so that you should be able to follow along with any other method.

The makeimg declaration for the final system I’ll be using can be found here. It’s a basic system with the LTS kernel, so it should run just about anywhere. If you intend to play with VMs only you could use the linux-virt kernel instead. That would give you a much smaller image at the expense of hardware compatibility. Make sure all the standard services are enabled. We don’t need a boot-loader. Do make sure you can log in as root (i.e. set a password). I am also enabling a console on the serial port, which you may or may not need, depending on how you work with your VMs.

That one weird trick

Now, what we need to make this system work as initramfs is something that acts as the init process, similar to the init process of an actual system. Normally, it is the job of the (initramfs’) init process to mount the real system’s root file-system. This sounds simple, but as we’ve seen in the last post, when that init process gets started, it does not have much of a system to work with. Depending on the system to be booted, mounting the root file-system might for example require loading modules or discovering devices. The great thing about using Alpine for this is that it builds on busybox, which is essentially purpose-built exactly for this situation. Also, we’ll skip the dirty work: we’ll build an initramfs that boots and gives you a full system, but it will run purely from memory (much like a live system booted from a USB stick), but it will not actually mount the real system’s root file-system.

So what does it take? Well, thanks to busybox, all we need is a symlink. As discussed in the previous post, the kernel will execute /init in the initramfs. So we symlink /init to /bin/busybox - and that’s it!

I will stress again that this would not be a great way to build a traditional initramfs. Busybox will essentially perform the function of the init process of a real system (in this case, reading /etc/inittab and starting OpenRC). But this is fine, because it just so happens that we supply the entire real system in the initramfs.

Building the image

If you are using my makeimg example, you can build it with sudo makeimg. Whichever way you did it, I will now assume that you have a directory called alpine-initrd which contains an Alpine system (like a chroot).

For booting, we will need an initramfs and a matching kernel, so copy out the kernel (using sudo here because the makeimg output folder is owned by root):

sudo cat alpine-initrd/boot/vmlinuz-lts > vmlinuz-makeimg

Building the initramfs is essentially the inverse operation of the extraction shown in the previous post:

sudo bash -c “cd alpine-initrd; find . | cpio –quiet -H newc -o” |
zstd > initramfs-makeimg

You can also use other compressors, e.g. gzip, but zstd is pretty great.

Booting

We now have two files, initramfs-makeimg and the matching kernel vmlinuz-makeimg. There are various options to boot these.

NOTE: Because we are cramming an entire system into an initramfs you will need at least 4GB of RAM for it to boot properly. You can read more about the relation of the initramfs size and physical memory here.

By far the simplest choice - that is, if you already have a working VM setup - is to use e.g. libvirt’s “direct kernel boot”. You provide the two files and you’re done. You can also specify kernel command line parameters. I use console=tty0 console=ttyS0,115200 because I use the serial console on my VMs a lot. But in general, it should simply boot you into a basic Alpine system, giving you console access.

The “direct kernel boot” is really just a PXE boot process with some shortcuts, so the two files can also be used for that. I will, however, just assume that if you are considering that as an option, then you know how it works.

Another very interesting option to use these files is with kexec. However, that would - or in fact might soon be - a post of its own.

Last, but not least, you can of course boot your regular system with these files. Create an entry in your boot-loader for them (don’t make it the default, just in case!) and reboot.

Great success

So, what have we achieved here? I hope to have shown you that building a working initramfs image is not magic, even though what we have built here is clearly just a toy system (at least with regards to acting as a “real” initramfs). But if you have enough RAM you can now build images with whatever you can think of :)

But this whole exercise is not just for fun. In a future post, I will explore some ideas that such a system can actually be useful for.

Since the result does look a little bit like a live system I will point out that this is not how live systems work. They, too, have a “proper” initramfs, which does indeed mount the real root file-system. That just happens to also be a tmpfs. This approach also requires less RAM, because the memory used by the initramfs will be reclaimed.

As always, I’m looking forward to any feedback or questions - send them to my public inbox!