← Back to blog
How to Build a Minimal Linux Kernel for AWS Firecracker
LinuxTutorialAWSVirtualMachines

How to Build a Minimal Linux Kernel for AWS Firecracker

Learn how to build a minimal Linux kernel for AWS Firecracker from scratch. Step-by-step guide covering kernel config, compilation, and integration with Firecracker's microVM

April 10, 202612 min read

How to Build a Minimal Linux Kernel for AWS Firecracker VM

A complete, step-by-step guide to compiling Linux kernel 5.10, configuring kernel modules, creating a root filesystem, and launching microVMs with Firecracker's REST API.


What is Firecracker?

Firecracker is an open-source Virtual Machine Monitor (VMM) developed by Amazon Web Services, designed for serverless and container workloads. Unlike traditional hypervisors, Firecracker boots microVMs in under 125 milliseconds by stripping the guest environment down to the bare minimum. That minimalism starts with the kernel: you need a custom-compiled Linux kernel configured specifically for Firecracker's constraints.

This guide walks through every step - from cloning the Linux source tree to booting a fully networked Debian-based microVM - using kernel version 5.10.234, which is the current long-term support branch recommended by the Firecracker project.

1. Why Build a Custom Kernel for Firecracker?

Using a distribution kernel (from Ubuntu, Debian, or Fedora) inside a Firecracker microVM is technically possible, but it comes with real costs: longer boot times, wasted memory from unused drivers, and potential incompatibilities with Firecracker's stripped-down virtual hardware model.

Firecracker does not emulate a full PCI bus, has no BIOS, and exposes only a minimal set of VirtIO MMIO devices. A distribution kernel includes thousands of modules for hardware that simply does not exist inside a microVM. A minimal kernel configured for Firecracker's environment can be as small as a few megabytes, boots significantly faster, and has a smaller attack surface.

Firecracker's kernel policy officially supports two guest kernel versions:

This guide uses 5.10.234, the latest stable patch release in the 5.10 series at the time of writing.


2. Prerequisites and Host Environment

You need a Linux x86_64 host with KVM support enabled. All commands below are tested on Ubuntu 22.04 LTS.

Install build dependencies:

bash
sudo apt-get update
sudo apt-get install -y \
    build-essential \
    libncurses-dev \
    bison \
    flex \
    libssl-dev \
    libelf-dev \
    bc \
    dwarves \
    git \
    debootstrap \
    e2fsprogs \
    curl

Verify KVM access:

bash
ls -la /dev/kvm
# You should see: crw-rw---- 1 root kvm ...
# Add your user to the kvm group if needed:
sudo usermod -aG kvm $USER

Download the Firecracker binary:

bash
ARCH=$(uname -m)
FC_VERSION="v1.9.0"
curl -Lo /usr/local/bin/firecracker \
  "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${ARCH}.tgz" | \
  tar -xzOf - "release-${FC_VERSION}-${ARCH}/firecracker-${FC_VERSION}-${ARCH}" \
  > /usr/local/bin/firecracker
chmod +x /usr/local/bin/firecracker

3. Cloning the Linux Source Tree

The Linux kernel source is hosted on kernel.org. Because the full git history is large, use a shallow clone of the specific release tag to save time and disk space.

bash
# Shallow clone of the exact 5.10.234 tag
git clone \
  --depth 1 \
  --branch v5.10.234 \
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \
  linux-5.10.234

cd linux-5.10.234

Alternatively, if you already have a full clone of the Linus tree:

bash
git clone https://github.com/torvalds/linux.git linux.git
cd linux.git
git checkout v5.10.234

The source tree is approximately 1.2 GB. The shallow clone reduces this to around 300 MB.


4. Kernel Configuration for Firecracker

Firecracker's repository ships reference kernel configurations for both x86_64 and aarch64 in resources/guest_configs/. These configs are the ones used in Firecracker's own CI pipelines and represent the minimum viable configuration for booting a microVM.

Download the reference config:

bash
curl -Lo .config \
  "https://github.com/firecracker-microvm/firecracker/raw/main/resources/guest_configs/microvm-kernel-x86_64-5.10.config"

Sync the config against the actual kernel version:

bash
make olddefconfig

olddefconfig applies the downloaded config and sets any new options (that appeared between the config's base version and 5.10.234) to their default values. This is safer than oldconfig, which would interactively ask about every new option.

Optionally open the interactive menu:

bash
make menuconfig

5. Essential Kernel Modules and Config Options {#kernel-modules}

Understanding which config options Firecracker requires — and which you can disable - is the key to building a lean, fast kernel.

5.1 Mandatory Options

These must be compiled in (=y), not as modules. Firecracker does not support initramfs-based module loading by default in minimal setups.

Config OptionPurpose
CONFIG_SERIAL_8250_CONSOLE=ySerial console output (ttyS0)
CONFIG_PRINTK=yKernel log messages
CONFIG_VIRTIO_MMIO=yVirtIO transport over MMIO (no PCI)
CONFIG_VIRTIO_BLK=yVirtIO block device (rootfs drive)
CONFIG_VIRTIO_NET=yVirtIO network device
CONFIG_EXT4_FS=yext4 filesystem support
CONFIG_KVM_GUEST=yKVM paravirtualization, KVM clock

5.2 Options to Disable

Disabling unnecessary features directly reduces kernel size and boot time.

text
# No ACPI on minimal setups (unless you need it)
# CONFIG_ACPI is not set

# No PCI bus (Firecracker uses MMIO by default)
# CONFIG_PCI is not set

# No graphical framebuffer
# CONFIG_FB is not set
# CONFIG_DRM is not set

# No USB
# CONFIG_USB_SUPPORT is not set

# No sound
# CONFIG_SOUND is not set

# No wireless
# CONFIG_WIRELESS is not set

5.3 Networking Options (if you need networking)

text
CONFIG_NET=y
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_TCP_CONG_ADVANCED=y
CONFIG_PACKET=y
CONFIG_UNIX=y

5.4 Filesystem Options

For a rootfs image created with mkfs.ext4:

text
CONFIG_EXT4_FS=y
CONFIG_EXT4_USE_FOR_EXT2=y

If you want to strip out journal support for a slightly lighter FS:

text
# CONFIG_EXT4_FS_POSIX_ACL is not set
# CONFIG_EXT4_FS_SECURITY is not set

Note: the mkfs.ext4 example in this guide already uses -O ^has_journal to create a journal-free filesystem, which pairs well with a kernel that does not need journaling overhead.

5.5 Security and Boot Options

text
# Firecracker's own default kernel command line appends these:
# reboot=k panic=1 nomodule 8250.nr_uarts=0
# i8042.noaux i8042.nomux i8042.dumbkbd swiotlb=noforce

# If not using ACPI, Firecracker also adds: pci=off

The nomodule parameter means the kernel will not attempt to load kernel modules at runtime, so everything the microVM needs must be compiled in.


6. Compiling the Kernel

With the configuration in place, build the uncompressed ELF binary that Firecracker expects:

bash
# Use all available CPU cores
make vmlinux -j$(nproc)

On a modern 8-core machine, this takes approximately 5–10 minutes. The output binary is ./vmlinux.

Copy the kernel to a well-known path:

bash
sudo cp vmlinux /tmp/vmlinux.bin

Check the binary size:

bash
ls -lh /tmp/vmlinux.bin
# A minimal build is typically 5–15 MB

Note for aarch64 hosts: Replace make vmlinux with make Image and find the output at ./arch/arm64/boot/Image.


7. Creating the Root Filesystem

Firecracker expects a raw disk image in a standard block device format. The filesystem is mounted directly as a block device inside the microVM - there is no virtualized disk controller, just VirtIO block.

7.1 Allocate the Image File

bash
# Create a 2GB sparse image file
dd if=/dev/zero \
   of=/tmp/rootfs.ext4 \
   bs=1M \
   count=2000 \
   status=progress

For production use, tune the size to your actual workload. A minimal Debian Noble rootfs with a few packages occupies around 400-600 MB.

7.2 Format the Filesystem

bash
mkfs.ext4 \
  -O ^has_journal,^64bit \
  -E lazy_itable_init=0,lazy_journal_init=0 \
  -F \
  /tmp/rootfs.ext4

Flag breakdown:


8. Building a Debian-based Root Image with debootstrap

debootstrap installs a minimal Debian or Ubuntu userland directly into a directory. This approach gives you more control over the base image than using Docker.

8.1 Mount the Image and Bootstrap

bash
# Mount the ext4 image
sudo mkdir -p /mnt/rootfs
sudo mount /tmp/rootfs.ext4 /mnt/rootfs

# Bootstrap Ubuntu Noble (24.04) into the mounted image
sudo debootstrap \
  --arch=amd64 \
  noble \
  /mnt/rootfs \
  http://archive.ubuntu.com/ubuntu

This downloads and installs the base Ubuntu Noble userland. It takes a few minutes depending on your connection speed.

8.2 Chroot and Configure the System

bash
# Bind-mount pseudo-filesystems
sudo mount --bind /proc /mnt/rootfs/proc
sudo mount --bind /sys /mnt/rootfs/sys
sudo mount --bind /dev /mnt/rootfs/dev

# Enter the chroot
sudo chroot /mnt/rootfs /bin/bash

Inside the chroot:

bash
# Set root password
passwd root

# Set hostname
echo "firecracker-vm" > /etc/hostname

# Configure serial console for automatic login (ttyS0)
cat > /etc/systemd/system/serial-getty@ttyS0.service.d/override.conf << 'EOF'
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --keep-baud 115200,38400,9600 ttyS0 $TERM
EOF

# Enable the serial console service
systemctl enable serial-getty@ttyS0.service

# Configure basic networking
cat > /etc/systemd/network/eth0.network << 'EOF'
[Match]
Name=eth0

[Network]
Address=172.16.0.2/24
Gateway=172.16.0.1
DNS=8.8.8.8
EOF

systemctl enable systemd-networkd

# Set up /etc/fstab
echo "/dev/vda / ext4 defaults,noatime 0 1" > /etc/fstab

# Clean up apt cache to save space
apt-get clean
rm -rf /var/lib/apt/lists/*

exit

8.3 Unmount

bash
sudo umount /mnt/rootfs/proc
sudo umount /mnt/rootfs/sys
sudo umount /mnt/rootfs/dev
sudo umount /mnt/rootfs

9. Configuring and Launching Firecracker

Firecracker is configured via a JSON file or through its REST API over a Unix socket. The configuration below is a complete example for a 1 vCPU, 512 MB microVM with one network interface.

9.1 The Configuration File

json
{
  "boot-source": {
    "kernel_image_path": "/tmp/vmlinux.bin",
    "boot_args": "panic=1 earlyprintk=serial loglevel=8 console=ttyS0 root=/dev/vda rw"
  },
  "drives": [
    {
      "drive_id": "rootfs",
      "path_on_host": "/tmp/rootfs.ext4",
      "is_root_device": true,
      "is_read_only": false
    }
  ],
  "machine-config": {
    "vcpu_count": 1,
    "mem_size_mib": 512
  },
  "network-interfaces": [
    {
      "iface_id": "eth0",
      "host_dev_name": "tap0"
    }
  ]
}

Key boot arguments explained:

9.2 Set Up the TAP Network Interface

Before starting the VM, create a TAP device on the host:

bash
sudo ip tuntap add tap0 mode tap
sudo ip addr add 172.16.0.1/24 dev tap0
sudo ip link set tap0 up

# Enable IP forwarding if you want the VM to reach the internet
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i tap0 -j ACCEPT
sudo iptables -A FORWARD -o tap0 -j ACCEPT

9.3 Start Firecracker

bash
rm -f /tmp/fc.sock
firecracker --api-sock /tmp/fc.sock --config-file /path/to/config.json

Or use the API approach described in the next section.


10. API-Based VM Launch with curl

Firecracker's REST API is the most flexible way to configure and launch microVMs programmatically. Each resource is configured with a PUT request, and the VM is started with an InstanceStart action.

Step 1: Start Firecracker in API mode (in a separate terminal)

bash
rm -f /tmp/fc.sock
firecracker --api-sock /tmp/fc.sock

Step 2: Configure the boot source

bash
curl --unix-socket /tmp/fc.sock -i \
  -X PUT "http://localhost/boot-source" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "kernel_image_path": "/tmp/vmlinux.bin",
    "boot_args": "panic=1 earlyprintk=serial root=/dev/vda rw loglevel=8 console=ttyS0"
  }'

Step 3: Attach the root drive

bash
curl --unix-socket /tmp/fc.sock -i \
  -X PUT "http://localhost/drives/rootfs" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "drive_id": "rootfs",
    "path_on_host": "/tmp/rootfs.ext4",
    "is_root_device": true,
    "is_read_only": false
  }'

Step 4: Configure machine resources

bash
curl --unix-socket /tmp/fc.sock -i \
  -X PUT "http://localhost/machine-config" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "vcpu_count": 1,
    "mem_size_mib": 512
  }'

Step 5: Attach the network interface

bash
curl --unix-socket /tmp/fc.sock -i \
  -X PUT "http://localhost/network-interfaces/eth0" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "iface_id": "eth0",
    "host_dev_name": "tap0"
  }'

Step 6: Start the VM

bash
curl --unix-socket /tmp/fc.sock -i \
  -X PUT "http://localhost/actions" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "action_type": "InstanceStart"
  }'

After the InstanceStart call, Firecracker boots the microVM. You should see kernel log output in the terminal where Firecracker is running, ending with a login prompt on ttyS0.


11. Networking Inside the microVM

The microVM sees the TAP device on the host as an eth0 interface. The configuration added in the debootstrap step assigns a static IP. Verify connectivity from inside the VM:

bash
# Inside the microVM
ip addr show eth0
# Should show 172.16.0.2/24

ping -c 3 172.16.0.1
# Should reach the TAP interface on the host

For DNS resolution, add a resolver:

bash
echo "nameserver 8.8.8.8" > /etc/resolv.conf

For persistent DHCP-based networking (if you prefer dynamic addressing), you can configure systemd-networkd with a DHCP client and run a simple DHCP server on the host side of the TAP, but static addressing is simpler and faster for microVM workloads.


12. Memory Optimization with KSM

Kernel Samepage Merging (KSM) is a Linux kernel feature that allows the host to deduplicate identical memory pages across multiple processes or virtual machines. When running many Firecracker microVMs on the same host - especially when they share the same kernel and base rootfs - KSM can significantly reduce total memory consumption.

Enable KSM on the host:

bash
echo 1 | sudo tee /sys/kernel/mm/ksm/run

Tune KSM scan rate (pages scanned per scan cycle):

bash
# Default is 100; increase for faster deduplication at the cost of more CPU
echo 1000 | sudo tee /sys/kernel/mm/ksm/pages_to_scan

Check KSM status:

bash
cat /sys/kernel/mm/ksm/pages_shared    # pages currently shared
cat /sys/kernel/mm/ksm/pages_sharing   # additional virtual mappings saved
cat /sys/kernel/mm/ksm/full_scans      # number of completed scan cycles

The trade-off: KSM runs a background kernel thread (ksmd) that continuously scans memory pages and compares their contents. This consumes CPU proportional to the scan rate. On dense virtualization hosts, the memory savings (often 20-40% for homogeneous workloads) typically outweigh the CPU overhead, but benchmarking under your specific workload is recommended before enabling it in production.

Disable KSM:

bash
echo 0 | sudo tee /sys/kernel/mm/ksm/run

13. Troubleshooting Common Issues

VM fails to boot / no console output

Check that console=ttyS0 is present in boot_args and that CONFIG_SERIAL_8250_CONSOLE=y is set in the kernel config. Without these, all kernel output goes nowhere.

Kernel panic: VFS: Unable to mount root fs

The root device path in boot_args must be root=/dev/vda for VirtIO block. /dev/sda will not work. Also verify that CONFIG_VIRTIO_BLK=y and CONFIG_EXT4_FS=y are compiled in.

Network interface not appearing inside the VM

Ensure CONFIG_VIRTIO_NET=y is in the kernel config (not as a module - remember nomodule in Firecracker's default args). Also verify the TAP device exists on the host (ip link show tap0).

Firecracker exits immediately after InstanceStart

Check permissions on /dev/kvm and ensure the kernel image path exists and is readable. Firecracker logs errors to stderr.

mkfs.ext4 warns about journal

The -O ^has_journal flag is intentional. The warning is informational and can be ignored.

Boot is slow (> 500ms)

Review the kernel config for enabled drivers that aren't needed. Common culprits: CONFIG_ACPI, CONFIG_PCI, various USB/HID modules. Run make menuconfig and disable anything unrelated to VirtIO and serial console.


Summary

Building a custom Linux kernel for Firecracker is not optional if you care about boot performance and resource efficiency - it is the expected workflow. The process breaks down into four clear phases:

  1. Kernel build - clone Linux 5.10, apply the Firecracker reference config, compile vmlinux
  2. Rootfs creation - allocate an ext4 image, bootstrap a minimal Debian/Ubuntu userland with debootstrap
  3. VM configuration - define boot source, drives, machine config, and network via JSON or the REST API
  4. Host optimization - set up TAP networking, optionally enable KSM for multi-VM density

The result is a microVM that boots in well under a second, consumes a few hundred megabytes of RAM, and runs a full Linux userland. This is the foundation on which serverless platforms like AWS Lambda are built.


Subscribe for new tasks and project news

No spam. Only project information.