Skip to content

Instantly share code, notes, and snippets.

@elazarl
Created June 7, 2026 20:17
Show Gist options
  • Select an option

  • Save elazarl/b023c230428ee16f4ac1ffb0d84b736b to your computer and use it in GitHub Desktop.

Select an option

Save elazarl/b023c230428ee16f4ac1ffb0d84b736b to your computer and use it in GitHub Desktop.
Minimal bootable Linux kernel on Apple Silicon Mac (arm64) via Podman + QEMU/HVF

Minimal Bootable Linux Kernel on a Mac (Apple Silicon, arm64)

Build a tiny custom Linux kernel inside a Podman container and boot it against an Ubuntu cloud image with QEMU — entirely on an Apple Silicon Mac, no sudo required.

The whole point is a fast edit‑compile‑run cycle: boot to a userspace shell in well under a second thanks to hardware acceleration (HVF).

This is the arm64 variant. Because the Mac is itself arm64, the kernel is built natively (no cross‑compiler) and QEMU runs the guest with the hvf accelerator at near‑native speed. (The original write‑up targeted x86_64; everything below is adapted for aarch64.)

What you get

  • A ~4 MB arch/arm64/boot/Image built from a tinyconfig base + just enough drivers.
  • Boots a stock Ubuntu Noble cloud image straight into /bin/bash as init.
  • Verified boot log: virtio_blk ... [vda]EXT4-fs (vda1): mountedRun /bin/bash as init process.

Prerequisites (on the Mac)

brew install qemu podman
podman machine init   # if you don't already have a machine
podman machine start

You also need a Linux kernel source tree. This guide assumes it lives at ~/linux:

git clone --depth=1 https://github.com/torvalds/linux.git ~/linux

1. Spin up a build container (Ubuntu 24.04) with the toolchain

The kernel must be built on Linux, so we use a container. The host ~/linux is mounted in, so the build artifacts land in your real tree.

podman run -dit --name kbuild -v "$HOME/linux:/linux" -w /linux ubuntu:24.04 bash

podman exec kbuild bash -c 'apt-get update && \
  DEBIAN_FRONTEND=noninteractive apt-get install -y \
  build-essential flex bison libssl-dev libelf-dev bc libncurses-dev cpio kmod'

Because the container is arm64 (matching the Mac), the native gcc builds an arm64 kernel directly — no CROSS_COMPILE needed.

2. Configure a minimal-but-bootable arm64 kernel

Start from tinyconfig and enable only what's needed to (a) print to the serial console, (b) find the virtio disk over the QEMU virt machine's PCIe host bridge, and (c) mount an ext4 rootfs and exec userspace.

podman exec kbuild bash -c '
cd /linux
make ARCH=arm64 tinyconfig

# console + printk
./scripts/config -e PRINTK -e PRINTK_TIME -e TTY

# block layer + virtio disk
./scripts/config -e BLOCK -e BLK_DEV
./scripts/config -e PCI -e VIRTIO_MENU -e VIRTIO_PCI -e VIRTIO_BLK -e VIRTIO_MMIO

# *** the part tinyconfig misses: the arm64 "virt" PCIe ECAM host bridge ***
# without this the PCI bus is never enumerated and the disk never appears
./scripts/config -e PCI_HOST_GENERIC -e PCI_HOST_COMMON -e PCI_ECAM

# root filesystem
./scripts/config -e EXT4_FS

# run userspace binaries + scripts
./scripts/config -e BINFMT_ELF -e BINFMT_SCRIPT -e BINFMT_MISC

# almost nothing works without these
./scripts/config -e PROC_FS -e SYSFS -e DEVTMPFS -e DEVTMPFS_MOUNT
./scripts/config -e FUTEX -e MULTIUSER

# arm64 "virt" platform essentials (replaces the x86 8250 UART path):
#   PL011 serial = ttyAMA0, GICv3 interrupt controller, arch timer, PSCI
./scripts/config -e SERIAL_AMBA_PL011 -e SERIAL_AMBA_PL011_CONSOLE
./scripts/config -e ARM_GIC -e ARM_GIC_V3
./scripts/config -e ARM_ARCH_TIMER
./scripts/config -e ARM_PSCI_FW
./scripts/config -e POWER_RESET -e POWER_RESET_SYSCON
./scripts/config -e OF -e OF_FLATTREE

# debug symbols (so you can attach with qemu -s and gdb)
./scripts/config -e DEBUG_INFO_DWARF5

make ARCH=arm64 olddefconfig
'

Key differences from the x86_64 recipe

x86_64 (original) arm64 (this guide)
-e 64BIT n/a (arm64 is always 64‑bit)
SERIAL_8250*ttyS0 SERIAL_AMBA_PL011*ttyAMA0
(implicit i440fx/PCI) PCI_HOST_GENERIC required for virt ECAM
ARM_GIC_V3, ARM_ARCH_TIMER, ARM_PSCI_FW
arch/x86_64/bzImage arch/arm64/boot/Image
qemu-system-x86_64 qemu-system-aarch64 -machine virt -cpu host -accel hvf

3. Build

podman exec kbuild bash -c 'cd /linux && make ARCH=arm64 -j"$(nproc)" Image'
# -> arch/arm64/boot/Image   (~4 MB)

4. Get an Ubuntu cloud image (arm64)

mkdir -p ~/kernel-qemu && cd ~/kernel-qemu
curl -LO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img

5. Boot it with QEMU (on the Mac, no sudo)

cd ~/kernel-qemu
qemu-system-aarch64 \
    -nographic \
    -machine virt,gic-version=3 \
    -cpu host -accel hvf \
    -m 2048 -smp 4 \
    -kernel "$HOME/linux/arch/arm64/boot/Image" \
    -drive file=noble-server-cloudimg-arm64.img,if=virtio,format=qcow2 \
    -append "console=ttyAMA0 root=/dev/vda1 init=/bin/bash" \
    -snapshot
  • -accel hvf -cpu host → hardware‑accelerated, near‑native arm64 guest.
  • console=ttyAMA0 → the PL011 UART (arm64), not ttyS0.
  • root=/dev/vda1 init=/bin/bash → mount the cloud image and drop straight into a shell.
  • -snapshot → discards writes on exit, so the image stays pristine and reusable.

You'll land in root@(none):/# with all the usual userspace tools (lspci, ls, …). To quit QEMU from -nographic: Ctrl-a x.

Save it as a script

cat > ~/kernel-qemu/run.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
exec qemu-system-aarch64 -nographic -machine virt,gic-version=3 \
    -cpu host -accel hvf -m 2048 -smp 4 \
    -kernel "$HOME/linux/arch/arm64/boot/Image" \
    -drive file=noble-server-cloudimg-arm64.img,if=virtio,format=qcow2 \
    -append "console=ttyAMA0 root=/dev/vda1 init=/bin/bash" -snapshot
EOF
chmod +x ~/kernel-qemu/run.sh

Edit‑compile‑run loop

  1. Hack on ~/linux.
  2. podman exec kbuild bash -c 'cd /linux && make ARCH=arm64 -j"$(nproc)" Image'
  3. ~/kernel-qemu/run.sh

Rebuilds are incremental and the boot is sub‑second — the fast cycle the original write‑up is all about.

@elazarl

elazarl commented Jun 7, 2026

Copy link
Copy Markdown
Author

Prompt for Claude Code

Paste this into Claude Code (running on an Apple Silicon Mac with podman + qemu installed) to do the whole thing end‑to‑end:

Follow this gist to build and boot a minimal arm64 Linux kernel on my Mac:
https://gist.github.com/elazarl/b023c230428ee16f4ac1ffb0d84b736b

Specifically:

  1. Make sure ~/linux is a kernel source tree (shallow‑clone torvalds/linux if it's missing) and that podman machine is running and qemu is installed (brew install qemu).
  2. Create a Podman container kbuild from ubuntu:24.04 with ~/linux mounted at /linux, and install the build toolchain (build-essential flex bison libssl-dev libelf-dev bc libncurses-dev cpio kmod).
  3. Configure a minimal arm64 kernel: make ARCH=arm64 tinyconfig, then enable exactly the options in the gist — including the easy‑to‑miss PCI_HOST_GENERIC/PCI_HOST_COMMON/PCI_ECAM (needed for the virt machine's PCIe bus), the PL011 serial console, GICv3, arch timer and PSCI — then make ARCH=arm64 olddefconfig.
  4. Build it natively: make ARCH=arm64 -j$(nproc) Image.
  5. Download the Ubuntu Noble arm64 cloud image into ~/kernel-qemu.
  6. Boot it on the host with qemu-system-aarch64 -machine virt,gic-version=3 -cpu host -accel hvf ... -kernel arch/arm64/boot/Image -append "console=ttyAMA0 root=/dev/vda1 init=/bin/bash" -snapshot (no sudo).
  7. Verify success by capturing the boot log and confirming virtio_blk ... [vda], EXT4-fs (vda1): mounted, and Run /bin/bash as init process all appear. Don't claim success until you see those lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment