This guide helps you to setup a minimal stateless and diskless Debian initramfs with debootstrap. It is suitable for running from QEMU or network booting with (e.g. with iPXE).
This guide is for amd64 architecture.
In my case I needed to run MikroTik EoIP driver on my system which is a bit sketchy but works. I didn't want to mix it up with my system since upgrading the kernel leads to recompiling because the driver is not building automatically with DKMS and it with certain commands I was able to crash the whole system. I could debug it but since I'm going to migrate away from using EoIP anyway in a year, it's not worth fixing.
This could be useful for folks wanting to run some VPN client which is too sketchy or suspicious to run on the host system.
Docker shares the same kernel with the host system which is good for performance, but it's not possible to load your own kernel modules or have a completely different version of it running.
First, we use debootstrap on
pre-existing system (such as the host) to set up a minimal base
system. You can use any directory, only restriction is file system
mount options. Root is mounted without nodev
so it's working for
sure.
mkdir /stable-chroot
debootstrap stable /stable-chroot http://deb.debian.org/debian/
Now, you can configure the system by chrooting to it:
chroot /stable-chroot /bin/bash
It is a good practice to mount /sys
, /proc
and copy some basic
files from the host system if you are doing something special. See
Debian wiki page for an
example.
This is important part. Pick you a kernel of your choice. I recommend cloud kernels if you are going to run it inside QEMU or not planning to use it on a special hardware. On chroot you can run:
apt install linux-image-YOUR_VERSION_HERE-cloud-amd64-unsigned
Installing a kernel creates an initrd file, but we are not going to use it. Instead, we create an initramfs of the WHOLE SYSTEM which is larger but can start normal system with SystemD and a proper userland.
The following commands create a compresses initramfs from the chroot, ignoring some directories which contain stuff we don't want there to save memory on the host system. Feel free to adjust them.
cd /stable-chroot
find . \( -path './root/*' -o -path './boot/*' -o -path './var/*/*' -o \
-path './usr/share/man' -o -path './usr/share/doc' \) \
-prune -o -print0 | sudo cpio --null --create --verbose --format=newc | \
gzip -9 >boot/initrd-root.img
Now, the symlink to the kernel is located at /stable-chroot/vmlinuz
and the initramfs is at /stable-chroot/boot/initrd-root.img
.
This is important since the kernel uses /init
instead of
/sbin/init
when booting a system fro initial ram drive. We just need
a symlink it there:
cd /stable-chroot
ln -s usr/bin/systemd init
Check that the link works. If you are using a legacy non-usrmerge
system, you might find your SystemD from /bin/systemd
instead.
The following command runs it on QEMU. On a production system you can
safely set -display none
to disable VGA and use -nic
options to
set up network for your system. This just starts it interactively:
qemu-system-x86_64 \
-machine accel=kvm \
-enable-kvm \
-runas kvm \
-m 2G \ # Must be twice the uncompressed size of initramfs by default
-kernel /stable-chroot/vmlinuz \
-initrd /stable-chroot/boot/initrd-root.img
There you go!
Drop the vmlinuz
and initrd
files to your TFTP root and load them
with a bootloader such as iPXE or GNU
GRUB.
Thanks to jpa for insights about Linux bootloading and clarifying the difference of initrd and initramfs.
In boot loaders, the term initrd is used for historical reasons and
actually for a boot loader it doesn't make a difference which is the
actual file format. It is just loaded to the memory and handed over to
the kernel. Linux "initrd files" created with cpio
do have initramfs
format. See more information.
Support me on GitHub to keep my hobbies going!