Minimal Linux: 900KB kernel, no glibc

Mar 20, 2026

I wanted to see how small a bootable Linux system can be. Not Alpine-minimal. Actually minimal. Result: ~900KB kernel, a shell in x86-64 assembly using raw syscalls, under 9MB RAM, boots in under a second.

The kernel

make tinyconfig gets you a kernel smaller than allnoconfig. No networking, no loadable modules, no SMP. The bzImage came out around 900KB. A distro kernel is 10-15MB compressed.

I added serial console, initramfs, and CONFIG_PRINTK on top of tinyconfig. Debugging without printk is miserable. Each option I turned on, the binary grew. Good way to see what each config option costs in bytes.

Shell in assembly

BusyBox is the standard answer for minimal userspace. I skipped it initially and wrote a shell directly in x86-64 assembly. No glibc, no musl, no libc. Direct syscalls.

; write(1, msg, len)
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, msg_len
syscall

; read(0, buf, 256)
xor eax, eax
xor edi, edi
mov rsi, buf
mov edx, 256
syscall

It handles echo, ls (via getdents64), cat (via open/read/write), and exec (via execve). Not POSIX. Enough to poke around.

The annoying parts: getdents64 returns variable-length records, so you walk the buffer manually. execve needs a null-terminated argv array. Small things libc does for you that you don't think about until they're gone.

Adding real software

After the assembly shell worked, I added BusyBox (static, musl) and Lua. BusyBox is ~1MB for a real shell plus coreutils. Lua builds with no dependencies.

Full system — kernel, initramfs, BusyBox, Lua, assembly shell — under 9MB RAM. Boots in under a second on QEMU. No systemd, no init system. Kernel loads initramfs, runs /init, you get a prompt.

What I took away

The kernel itself is small and fast. The weight in a typical Linux system comes from everything above it. Useful to see that directly rather than just assume it.

← Back to main page