logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·26.06.26
← Back to blog

Building a bootable Linux System from Scratch

Jun 26, 2026|Kurashizu
linuxkernelbootinitramfsqemusystemd-boot

Building a bootable Linux System from Scratch: A Simplified 5-Step Guide to an Initramfs Emergency Shell

When I moved my custom kernel (7.1.1-krsz) from QEMU to my physical laptop using just a single FAT32 EFI partition, I hit a major roadblock: a completely black screen. My kernel was booting "blind" because the display pipeline wasn't initialized early enough.

Here is the quick, no-nonsense guide on how I configured, built, tested, and successfully deployed my minimal RAM-only emergency shell on bare metal.


Step 1: Kernel Configuration & Configuration Anatomy

Inside make menuconfig, my strategy was to compile essential early display drivers as Built-in ([*]) so the screen lights up immediately, while keeping everything else as Modules (<M>).

To fix the black screen, the kernel needs to use the motherboard's UEFI graphics canvas before the heavy GPU drivers load. I welded these options directly into the kernel core:

Device Drivers --->
  └─ Graphics support --->
      ├─ [*] Simple framebuffer support (CONFIG_X86_SYSFB=y)
      ├─ [*] Simple DRM driver (CONFIG_DRM_SIMPLEDRM=y)
      ├─ [*] Support for frame buffer devices --->
      │    └─ [*] EFI Based Framebuffer Support (CONFIG_FB_EFI=y)
      └─ [*] Console display driver support --->
           └─ [*] Framebuffer Console support (CONFIG_FRAMEBUFFER_CONSOLE=y)

Step 2: Compilation & Installing Modules

Once configured, I compiled the kernel and exported the hardware modules to my host system's module directory so they would be available for packaging:

# Compile the kernel
make -j$(nproc)

# Install the modules to /usr/lib/modules/7.1.1-krsz/
sudo make modules_install

Step 3: Building the Initramfs with mkinitcpio

Because my storage drivers were compiled as modules, I needed mkinitcpio to dynamically load them during boot. I kept my MODULES array empty and relied on automated discovery hooks to sweep up the required files.

My /etc/mkinitcpio.conf:

MODULES=()
HOOKS=(base udev block filesystems)

I then generated the final initramfs image:

# Generate the initramfs toolset
sudo mkinitcpio -c /etc/mkinitcpio.conf -k 7.1.1-krsz -g ./initramfs-7.1.1-krsz.img

Step 4: Testing Inside QEMU via ttyS0

Before doing anything on physical hardware, I used QEMU to verify that my kernel and initramfs worked together. I directed the output to my host terminal using the virtual serial port (ttyS0):

qemu-system-x86_64 \
    -kernel arch/x86/boot/bzImage \
    -initrd ./initramfs-7.1.1-krsz.img \
    -append "console=ttyS0" \
    -m 256M \
    -nographic \
    -no-reboot

The kernel rolled its logs inside my terminal and successfully stopped at the interactive sh emergency shell prompt. Stage one complete.


Step 5: Partitioning, Bootloader Installation, and Bare-Metal Launch

With validation complete, I moved to my physical target machine. I didn't want a full Linux distribution layout — just an isolated sandbox.

5.1 Partitioning and systemd-boot Setup

I booted into a live environment, used gdisk to wipe my NVMe drive, and created a single EFI System Partition (ESP) formatted to FAT32.

# Format the single EFI partition
sudo mkfs.vfat -F 32 /dev/nvme0n1p1

# Mount it and install systemd-boot
sudo mount /dev/nvme0n1p1 /boot
sudo bootctl --path=/boot install

I copied my custom binaries over to the ESP and created my boot entry configuration at /boot/loader/entries/emergency.conf:

sudo cp path/to/bzImage /boot/vmlinuz-7.1.1-krsz
sudo cp path/to/initramfs-7.1.1-krsz.img /boot/initramfs-7.1.1-krsz.img
# /boot/loader/entries/emergency.conf
title   Linux Emergency Shell (Custom Kernel 7.1.1-krsz)
linux   /vmlinuz-7.1.1-krsz
initrd  /initramfs-7.1.1-krsz.img
options rw console=tty0

Note: I purposefully omitted the root= parameter. Since there is no root partition, this forces the system to drop straight into the initramfs emergency shell.

5.2 The Bare-Metal Timeline

I rebooted my physical laptop and selected my custom entry from the menu:

  • 0.0s – 0.5s: The kernel boots. efifb immediately captures the UEFI screen canvas, and the text console (tty0) instantly lights up the screen with scrolling logs.
  • 0.5s – 1.5s: The block and filesystems hooks scan the PCI bus, automatically loading nvme.ko and setting up the hardware interface.
  • 1.5s – 2.0s: My built-in amdgpu driver wakes up, takes over from the temporary EFI canvas via a smooth handover, and sharpens the display resolution.
  • Conclusion: The initramfs realizes there is no root= target partition to mount. It safely drops execution control, presenting me with a fully operational, root-privileged interactive shell right on my laptop's physical display.

Key Takeaways

Keeping MODULES empty while welding efifb into the core kernel gives you an incredibly lightweight initramfs footprint with zero-frame visual feedback. Turn on your framebuffers, trust your hooks, and enjoy your custom shell!