Linux Distro from Scratch

A Linux Distro from Scratch#

I think like many people, I use Linux on a daily basis. Personally, I use NixOS. But ultimately, I don’t really know how everything works under the hood.

I’ve stumbled upon videos by an American, Nir Lichtman I believe, who has fun creating different Linux distributions from scratch for various use cases.

Here is an example video

The concept is really cool and it makes the whole thing very accessible, so thanks to him for these videos.

IMPORTANT

The final distribution is not intended for production use, but just for educational purposes.

So in this post, I’m going to show you what I did to create a minimal Linux distribution from scratch.

Technical Prerequisites#

In my case, I’ll be doing this on an Ubuntu Docker container. And to run the VM, I use QEMU, which you can easily install.

On NixOS, I simply ran: nix-shell -p qemu

For other distributions, you can check the official QEMU documentation. The docker documentation. And by the way, the official Linux From Scratch documentation which is very well made.

Division#

We will need three blocks to make our distribution work:

  • kernel (the Linux kernel)
  • User Space (BusyBox)
  • bootloader (Syslinux)

The Linux Kernel#

As I told you at the beginning, I’m going to use an Ubuntu Docker container to do all of this. So my first step is to launch it.

docker

docker run —privileged -it ubuntu

docker

We find ourselves in the container’s shell, the first step is to update and install the necessary dependencies.

Update and dependency installation

apt update && apt upgrade -y apt install bzip2 git vim make gcc libncurses-dev flex bison bc cpio libelf-dev libssl-dev -y

update

We have our dependencies installed, now we can clone the Linux kernel repository.

NOTE

I prefer to clone it in the user’s directory but that’s up to you (/home/ubuntu).

Cloning the Linux Kernel

*—depth 1 - to avoid getting the full git history of the kernel, just the last commit

clone-linux

Since we are going for an x86_64 installation, we will configure the kernel for this architecture. We use a configuration already made by the kernel developers.

Kernel configuration

make x86_64_defconfig

x86_64_defconfig

Once done, we can start compiling the kernel. We can either use make or make -j N where N is the number of cores of your processor to speed up compilation.

I will use all available cores to go faster.

Kernel compilation

make -j $(nproc)

btop

make

Kernel: arch/x86/boot/bzImage is ready (#1) Once compilation is finished, we can see the kernel image in the arch/x86/boot/bzImage directory.

We create a boot-files directory at the root and copy the kernel image into it.

Copying the kernel

mkdir /boot-files mv arch/x86/boot/bzImage /boot-files

Done for the kernel, let’s move on to the user space.

User Space - BusyBox#

The official BusyBox documentation.

We return to the user space /home/ubuntu and clone the busybox repository.

Cloning BusyBox

cd /home/ubuntu git clone —depth 1 https://git.busybox.net/busybox cd busybox

It’s the same principle as for the kernel: we configure and compile.

To initialize the default configuration, we use make defconfig.

BusyBox configuration

make defconfig

We want the busybox version in static mode so that everything is in a single binary. So we modify the configuration in settings; we need to uncheck the Build static binary (no shared libs) section. To do this, run the command:

Modifying configuration

make menuconfig

Once in the menu, go to Settings (enter).

settings libs

Once toggled, it’s not over because with recent Ubuntu versions we must disable tc. To do this, go to Networking Utilities and uncheck tc (8.3 kb) (it might be another value Xkb).

tc

Once done, do exit and save the configuration. We then build busybox the same way as the kernel.

Compiling BusyBox

make -j $(nproc)

busybox

In the directory we created previously /boot-files, we create an initramfs directory and copy the busybox binary into it (mkdir /boot-files/initramfs).

We install busybox in this directory using the make install command with the CONFIG_PREFIX variable pointing to the installation directory.

Installing BusyBox

make CONFIG_PREFIX=/boot-files/initramfs install

busybox-install

Our system doesn’t have a shell yet, we will create an init script which will be the first process launched by the kernel. What’s funny about the init file is that we tell it to use itself as a shell.

NOTE

Make sure to create the init file in the /boot-files/initramfs directory and give it execution permissions with the command chmod +x init.

Before that, we create the important folders in the initramfs directory.

Creating folders

mkdir /boot-files/initramfs/{proc,sys,dev}

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
echo "Welcome to my distribution"
exec /bin/sh

Still in the /boot-files/initramfs directory, we create a compressed cpio archive which will be used by the kernel at startup.

Creating cpio archive

cd /boot-files/initramfs find . | cpio -o -H newc > ../init.cpio

That’s it for the user space, let’s move on to the bootloader.

Bootloader - Syslinux#

Here we will use Syslinux as the bootloader, the official doc is here.

We install it because it’s not installed by default.

Installing Syslinux

cd /boot-files # going back to the boot-files directory apt install syslinux -y

So once installed, we will create a 50MB file that will serve as a virtual hard drive for our distribution.

Creating virtual hard drive

dd if=/dev/zero of=boot bs=1M count=50

bootloader

We need dosfstools to format the file to FAT32.

Formatting virtual hard drive

apt install dosfstools -y mkfs -t fat boot syslinux boot

Here we mount the virtual hard drive, format it, and install syslinux on it.

Creating a random folder

mkdir m mount boot m

Copy necessary files#

cp bzImage m cp init.cpio m

Voila, we now have a boot file containing our minimal Linux distribution. The goal is to run it on a virtual machine.

Since we are on a Docker container, we must transfer the boot file to the host. To do this, we use the docker cp command.

In another terminal on the host:

Transferring boot file

docker cp <container_id>:/boot-files/boot .

cp

Now we must launch the virtual machine with QEMU using the boot file as the hard drive.

Launching VM

qemu-system-x86_64 boot

qemu

But we have an issue, it tells us No configuration file found. No problem, we will manually give it the configuration file we created previously.

So in boot we write:

WARNING

The keyboard is QWERTY

/bzImage -initrd=/init.cpio

boot

Press enter and there we go, we have our minimal Linux distribution.

shell-final

NOTE

We can clearly see our custom message “Welcome to my distribution”.

You can now play with your minimal Linux distribution. You can type commands like ls, cd, pwd, etc.

That’s roughly how it works. Of course, there are other bootloaders, other user spaces, and plenty of other things to add to make the distribution more complete, but for a start, it’s not bad.

Thanks to Nir Lichtman for the inspiration.

Thanks for reading! If you have any questions, don’t hesitate to ask me on X.

Linux Distro from Scratch
https://blog.ce-dev.eu/posts/en/linux-distro-from-scratch/
Author
Cedev
Published at
2026-01-28
License
CC BY-NC-SA 4.0