
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
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:
- 5.10 - LTS, widely used, excellent stability
- 6.1 - newer LTS, supported through September 2026
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:
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:
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:
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.
# 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:
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:
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:
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:
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 Option | Purpose |
|---|---|
CONFIG_SERIAL_8250_CONSOLE=y | Serial console output (ttyS0) |
CONFIG_PRINTK=y | Kernel log messages |
CONFIG_VIRTIO_MMIO=y | VirtIO transport over MMIO (no PCI) |
CONFIG_VIRTIO_BLK=y | VirtIO block device (rootfs drive) |
CONFIG_VIRTIO_NET=y | VirtIO network device |
CONFIG_EXT4_FS=y | ext4 filesystem support |
CONFIG_KVM_GUEST=y | KVM paravirtualization, KVM clock |
5.2 Options to Disable
Disabling unnecessary features directly reduces kernel size and boot time.
# 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)
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:
CONFIG_EXT4_FS=y
CONFIG_EXT4_USE_FOR_EXT2=y
If you want to strip out journal support for a slightly lighter FS:
# 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
# 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:
# 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:
sudo cp vmlinux /tmp/vmlinux.bin
Check the binary size:
ls -lh /tmp/vmlinux.bin
# A minimal build is typically 5–15 MB
Note for aarch64 hosts: Replace
make vmlinuxwithmake Imageand 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
# 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
mkfs.ext4 \
-O ^has_journal,^64bit \
-E lazy_itable_init=0,lazy_journal_init=0 \
-F \
/tmp/rootfs.ext4
Flag breakdown:
-O ^has_journal— disables journaling for lower overhead inside a VM-O ^64bit— uses 32-bit block addressing; sufficient for images under 16 TB-E lazy_itable_init=0,lazy_journal_init=0— forces full inode table initialization at format time, avoiding first-boot delay-F— forces formatting even if the target is not a block device
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
# 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
# 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:
# 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
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
{
"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:
console=ttyS0- directs kernel output to the serial consoleroot=/dev/vda- Firecracker maps the first VirtIO block device to/dev/vdaearlyprintk=serial- enables kernel messages before the full console is readyloglevel=8- maximum verbosity, useful during development; set to4in production
9.2 Set Up the TAP Network Interface
Before starting the VM, create a TAP device on the host:
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
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)
rm -f /tmp/fc.sock
firecracker --api-sock /tmp/fc.sock
Step 2: Configure the boot source
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
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
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
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
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:
# 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:
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:
echo 1 | sudo tee /sys/kernel/mm/ksm/run
Tune KSM scan rate (pages scanned per scan cycle):
# 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:
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:
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:
- Kernel build - clone Linux 5.10, apply the Firecracker reference config, compile
vmlinux - Rootfs creation - allocate an ext4 image, bootstrap a minimal Debian/Ubuntu userland with
debootstrap - VM configuration - define boot source, drives, machine config, and network via JSON or the REST API
- 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.