My experience building Linux From Scratch (LFS)

July 27, 2020 | ~23 Minute Read


I, probably as many others, have heard about the LFS book that is provided by the LFS project. This magical book provides the recipe to create your very own Linux system. I have been really excited to play around with this but have not had the time. I decided to make the time as this is something that I have looked forward to doing for a long time.

I didn’t really know how to record my progress with this though, at first I wanted to make sort of an ongoing series where I would update here with separate blog posts of my progress, however that seemed like a lot of work. I decided it’s best to simply record my experience as I go and make a single post once I’m done.

I only recorded the sections I found to be relevant, not the entire process package by package, so expect some parts of the book being skipped (Mainly compilation sections).

My setup

I have thought about doing this on a VM, but I had a laptop that I’m not currently using for anything else, so I thought it would be good to have actual hardware to run this on and not take up CPU from my main desktop while compiling the packages.

My current build system has the following specs:

I will be using version 9.1 of the LFS book.

Initial steps (Chapter 1)

I read through the initial sections of the LFS book and decided to build a 64-bit system. I will be installing the default packages that come in the book as I would like to avoid as many errors as possible.

I will try to track how long this takes me in actual time so that I can have a reference point to share.

Chapter 2 - Preparing the Host System

2.2. Host System Requirements

I made sure to update the host system (Slackware 14.2) to the latest stable version, once that was done I got to work.

I ran the host system requirements script, this was the result:

root@darkstar:~# bash 
bash, version 4.3.48(1)-release
/bin/sh -> /bin/bash
Binutils: version 2.26.20160125
bison (GNU Bison) 3.0.4
yacc is bison (GNU Bison) 3.0.4
bzip2,  Version 1.0.8, 13-Jul-2019.
Coreutils:  8.25
diff (GNU diffutils) 3.3
find (GNU findutils) 4.4.2
GNU Awk 4.1.3, API: 1.1 (GNU MPFR 3.1.4, GNU MP 6.1.1)
/usr/bin/awk -> /bin/gawk-4.1.3
gcc (GCC) 5.5.0
g++ (GCC) 5.5.0
(GNU libc) 2.23
grep (GNU grep) 2.25
gzip 1.8
Linux version 4.4.227 (root@z-mp.slackware.lan) (gcc version 5.5.0 (GCC) ) #1 SMP Thu Jun 11 15:06:42 CDT 2020
m4 (GNU M4) 1.4.17
GNU Make 4.1
GNU patch 2.7.6
Perl version='5.22.2';
Python 3.6.5
sed (GNU sed) 4.2.2
tar (GNU tar) 1.29
texi2any (GNU texinfo) 6.1
xz (XZ Utils) 5.2.2
g++ compilation OK

According to the documentation on this chapter I have two software packages that do not meet the minimum version requirements for this version of LFS:

gcc (GCC) 5.5.0
grep (GNU grep) 2.25

These versions should be:


I will continue on with these non-recommended versions keeping in mind that if I have issues while compiling down the road, this may be a factor, let’s keep our fingers crossed.

2.4. Creating a New Partition

I created a 20 GB partition for LFS via fdisk and made a ext4 file system on it, the end result looks like this:

root@darkstar:~# fdisk -l
Device     Boot     Start       End   Sectors  Size Id Type
/dev/sda1  *         2048 209717247 209715200  100G 83 Linux
/dev/sda2       209717248 419432447 209715200  100G 83 Linux
/dev/sda3       419432448 461375487  41943040   20G 83 Linux

root@darkstar:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        99G   13G   81G  14% /
devtmpfs        3.8G     0  3.8G   0% /dev
tmpfs           3.8G  1.1M  3.8G   1% /run
tmpfs           3.8G     0  3.8G   0% /dev/shm
cgroup_root     3.8G     0  3.8G   0% /sys/fs/cgroup
/dev/sda2        99G  4.4G   89G   5% /home
/dev/sda3        20G  3.9G   15G  21% /mnt/lfs
cgmfs           100K     0  100K   0% /run/cgmanager/fs

I followed the instructions for creating the ext4 file system and mounting the new partition. I also created the $LFS shell variable for my root user.

Chapter 3 - Packages and Patches

3.1. Introduction

I created the wget-list file and downloaded the sources as instructed. I did the checks with the md5sums file and everything turned out OK:

root@darkstar:/mnt/lfs/sources# md5sum -c md5sums
acl-2.2.53.tar.gz: OK
attr-2.4.48.tar.gz: OK
autoconf-2.69.tar.xz: OK
automake-1.16.1.tar.xz: OK
bash-5.0.tar.gz: OK
bc-2.5.3.tar.gz: OK
binutils-2.34.tar.xz: OK
bison-3.5.2.tar.xz: OK
bzip2-1.0.8.tar.gz: OK
check-0.14.0.tar.gz: OK
coreutils-8.31.tar.xz: OK
dejagnu-1.6.2.tar.gz: OK
diffutils-3.7.tar.xz: OK
e2fsprogs-1.45.5.tar.gz: OK
elfutils-0.178.tar.bz2: OK
eudev-3.2.9.tar.gz: OK
expat-2.2.9.tar.xz: OK
expect5.45.4.tar.gz: OK
file-5.38.tar.gz: OK
findutils-4.7.0.tar.xz: OK
flex-2.6.4.tar.gz: OK
gawk-5.0.1.tar.xz: OK
gcc-9.2.0.tar.xz: OK
gdbm-1.18.1.tar.gz: OK
gettext-0.20.1.tar.xz: OK
glibc-2.31.tar.xz: OK
gmp-6.2.0.tar.xz: OK
gperf-3.1.tar.gz: OK
grep-3.4.tar.xz: OK
groff-1.22.4.tar.gz: OK
grub-2.04.tar.xz: OK
gzip-1.10.tar.xz: OK
iana-etc-2.30.tar.bz2: OK
inetutils-1.9.4.tar.xz: OK
intltool-0.51.0.tar.gz: OK
iproute2-5.5.0.tar.xz: OK
kbd-2.2.0.tar.xz: OK
kmod-26.tar.xz: OK
less-551.tar.gz: OK
lfs-bootscripts-20191031.tar.xz: OK
libcap-2.31.tar.xz: OK
libffi-3.3.tar.gz: OK
libpipeline-1.5.2.tar.gz: OK
libtool-2.4.6.tar.xz: OK
linux-5.5.3.tar.xz: OK
m4-1.4.18.tar.xz: OK
make-4.3.tar.gz: OK
man-db-2.9.0.tar.xz: OK
man-pages-5.05.tar.xz: OK
meson-0.53.1.tar.gz: OK
mpc-1.1.0.tar.gz: OK
mpfr-4.0.2.tar.xz: OK
ninja-1.10.0.tar.gz: OK
ncurses-6.2.tar.gz: OK
openssl-1.1.1d.tar.gz: OK
patch-2.7.6.tar.xz: OK
perl-5.30.1.tar.xz: OK
pkg-config-0.29.2.tar.gz: OK
procps-ng-3.3.15.tar.xz: OK
psmisc-23.2.tar.xz: OK
Python-3.8.1.tar.xz: OK
python-3.8.1-docs-html.tar.bz2: OK
readline-8.0.tar.gz: OK
sed-4.8.tar.xz: OK
shadow-4.8.1.tar.xz: OK
sysklogd-1.5.1.tar.gz: OK
sysvinit-2.96.tar.xz: OK
tar-1.32.tar.xz: OK
tcl8.6.10-src.tar.gz: OK
texinfo-6.7.tar.xz: OK
tzdata2019c.tar.gz: OK
udev-lfs-20171102.tar.xz: OK
util-linux-2.35.1.tar.xz: OK
vim-8.2.0190.tar.gz: OK
XML-Parser-2.46.tar.gz: OK
xz-5.2.4.tar.xz: OK
zlib-1.2.11.tar.xz: OK
zstd-1.4.4.tar.gz: OK
bash-5.0-upstream_fixes-1.patch: OK
bzip2-1.0.8-install_docs-1.patch: OK
coreutils-8.31-i18n-1.patch: OK
glibc-2.31-fhs-1.patch: OK
kbd-2.2.0-backspace-1.patch: OK
sysvinit-2.96-consolidated-1.patch: OK

Chapter 4 - Final Preparations

I followed the instructions for the addition of the new lfs user and permission / ownership update of the $LFS/sources and $LFS/tools directories.

I also added a line to the lfs user’s .bashrc file in order to take full advantage of my CPU. The added line was $MAKEFLAGS='-j 4', the final file looks like the following:

lfs:~$ cat .bashrc
set +h
umask 022
LFS_TGT=$(uname -m)-lfs-linux-gnu

Chapter 5 - Constructing a Temporary System

By this point it had been around 60 - 90 minutes since I had started, I wanted to read slowly and carefully but I’m sure that after you have done this about two or three times, 10-15 minutes is a more realistic time frame to get up to this point in the book.

5.4. Binutils-2.34 - Pass 1

I followed the instructions on the book and as suggested, in order to track the time of an SBU for reference, I executed the following:

time { ../configure --prefix=/tools --with-sysroot=$LFS --with-lib-path=/tools/lib --target=$LFS_TGT --disable-nls --disable-werror && make; }

The result was the following:

real    0m52.610s
user    2m27.692s
sys     0m12.757s

So, nearly a minute, I think that was not that bad, we’ll see how it goes as we progress.

5.5. GCC-9.2.0 - Pass 1

This package is supposed to take 10 SBUs, so we’ll test and confirm if that estimation is correct now.

Just out of curiosity during the build process I took a look at the usage of the hardware and confirmed that all 4 threads of my processor were being used successfully:

GCC build CPU usage

The final output once built and installed was this:

real    8m17.817s
user    29m34.731s
sys     1m11.821s

So 10 minutes was a good estimation, it finished in just over 8 minutes.

5.7. Glibc-2.31

For this package the time was as follows:

real    4m52.316s
user    13m57.024s
sys     1m41.865s

The expected time for this package was 4.5 SBU, so I suppose we can take it that on my system the estimates are pretty much spot on.

I then ran the commands that are shown in order to test that the installation was successful and got the expected results:

lfs:/mnt/lfs/sources/glibc-2.31/build$ echo 'int main(){}' > dummy.c
lfs:/mnt/lfs/sources/glibc-2.31/build$ $LFS_TGT-gcc dummy.c
lfs:/mnt/lfs/sources/glibc-2.31/build$ readelf -l a.out | grep ': /tools'
      [Requesting program interpreter: /tools/lib64/]

5.10. GCC-9.2.0 - Pass 2

I’ve reached the point where we’re re-compiling GCC with the full headers so that we can have an independent toolchain for the new LFS system.

Everything was going smooth up until this section, it seems I made a mistake in the way that I built the second pass for GCC, so I took a step back and tried again making sure that I followed the instructions from the LFS book.

After that I was able to see the expected result from the commands below:

lfs:/mnt/lfs/sources/gcc-9.2.0/build$ echo 'int main(){}' > dummy.c
lfs:/mnt/lfs/sources/gcc-9.2.0/build$ cc dummy.c
lfs:/mnt/lfs/sources/gcc-9.2.0/build$ readelf -l a.out | grep ': /tools'
      [Requesting program interpreter: /tools/lib64/]

Lesson learned was just uncompress the packages from the tarball again, even if you think it doesn’t matter, this was the only thing I did differently. Treat every package as it’s own isolated build even if you reuse them.

5.11. Tcl-8.6.10

This was pretty straight forward, but I ran the test suite so I wanted to share the output.

Test suite results:

Tests ended at Sat Jul 25 16:36:37 UTC 2020
all.tcl:        Total   33499   Passed  30389   Skipped 3110    Failed  0
Sourced 150 Test Files.
Number of tests skipped for each constraint:
        9       !ieeeFloatingPoint
        3       asyncPipeChan
        76      bigEndian
        5       bug-3057639
        49      dde
        4       dontCopyLinks
        64      emptyTest
        5       fullutf
        2       hasIsoLocale
        1       knownBadTest
        42      knownBug
        100     localeRegexp
        9       localhost_v6
        52      longIs32bit
        14      macosxFileAttr
        82      memory
        46      nonPortable
        5       notNetworkFilesystem
        1       notValgrind
        9       nt
        4       readonlyAttr
        1996    serverNeeded
        1       testexprparser && !ieeeFloatingPoint
        1       testwinclock
        21      testwordend
        3       tip389
        2       unthreaded
        2       wideBiggerThanInt
        487     win
        4       winVista

Although the LFS book says that there might be errors, I was lucky and it seems there wasn’t any.

The rest of the installation for tcl was pretty uneventful.

5.36. Changing Ownership

Everything went fairly smoothly for the installation of all the rest of the packages from Chapter 5.

At this stage I made a backup as suggested by the LFS book:

tar -czvf /home/root/lfs-initial-toolchain.tar.gz $LFS

Just in case I’d like to build another system later from this same 9.1 version

And so we move on to the actual building of the new LFS system, I’m excited!

Chapter 6 - Installing Basic System Software

6.3. Package Management

I decided against choosing a package management scheme at this point. Since this is my first build, I’ll just take a look at this later as it becomes necessary.

6.10. Adjusting the Toolchain

This seems to be a very important section, as this is where we make the switch from the temp utilities to the final toolchain for the rest of the building process.

This is the output from the instructions in this section:

(lfs chroot) root:/sources/glibc-2.31/build# mv -v /tools/bin/{ld,ld-old}
renamed '/tools/bin/ld' -> '/tools/bin/ld-old'

(lfs chroot) root:/sources/glibc-2.31/build# mv -v /tools/$(uname -m)-pc-linux-gnu/bin/{ld,ld-old}
renamed '/tools/x86_64-pc-linux-gnu/bin/ld' -> '/tools/x86_64-pc-linux-gnu/bin/ld-old'

(lfs chroot) root:/sources/glibc-2.31/build# mv -v /tools/bin/{ld-new,ld}
renamed '/tools/bin/ld-new' -> '/tools/bin/ld'

(lfs chroot) root:/sources/glibc-2.31/build# ln -sv /tools/bin/ld /tools/$(uname -m)-pc-linux-gnu/bin/ld
'/tools/x86_64-pc-linux-gnu/bin/ld' -> '/tools/bin/ld'

(lfs chroot) root:/sources/glibc-2.31/build# gcc -dumpspecs | sed -e 's@/tools@@g'                   \
>     -e '/\*startfile_prefix_spec:/{n;s@.*@/usr/lib/ @}' \
>     -e '/\*cpp:/{n;s@$@ -isystem /usr/include@}' >      \
>     `dirname $(gcc --print-libgcc-file-name)`/specs

(lfs chroot) root:/sources/glibc-2.31/build# echo 'int main(){}' > dummy.c
(lfs chroot) root:/sources/glibc-2.31/build# cc dummy.c -v -Wl,--verbose &> dummy.log
(lfs chroot) root:/sources/glibc-2.31/build# readelf -l a.out | grep ': /lib'
      [Requesting program interpreter: /lib64/]

(lfs chroot) root:/sources/glibc-2.31/build# grep -o '/usr/lib.*/crt[1in].*succeeded' dummy.log
/usr/lib/../lib/crt1.o succeeded
/usr/lib/../lib/crti.o succeeded
/usr/lib/../lib/crtn.o succeeded

(lfs chroot) root:/sources/glibc-2.31/build# grep -B1 '^ /usr/include' dummy.log
#include <...> search starts here:

(lfs chroot) root:/sources/glibc-2.31/build# grep 'SEARCH.*/usr/lib' dummy.log |sed 's|; |\n|g'

(lfs chroot) root:/sources/glibc-2.31/build# grep "/lib.*/ " dummy.log
attempt to open /lib/ succeeded

(lfs chroot) root:/sources/glibc-2.31/build# grep found dummy.log
found at /lib/

So everything went as expected, now we start building the rest of the packages against the local toolchain.

6.25. GCC-9.2.0

This was a big one as well, I actually left this running overnight, so I’m not entirely sure how long it took for the testing, the compile time was about 15-20 minutes.

The results after the testing were the following on my setup:

(lfs chroot) root:/sources/gcc-9.2.0/build# ../contrib/test_summary | grep -A7 Summ
gawk: cmd. line:36: warning: regexp escape sequence `\=' is not a known regexp operator
                === g++ Summary ===

# of expected passes            134787
# of expected failures          527
# of unsupported tests          5921
/sources/gcc-9.2.0/build/gcc/xg++  version 9.2.0 (GCC) 

                === gcc tests ===
                === gcc Summary ===

# of expected passes            139439
# of unexpected failures        2
# of expected failures          527
# of unsupported tests          2151
/sources/gcc-9.2.0/build/gcc/xgcc  version 9.2.0 (GCC) 

                === libatomic Summary ===

# of expected passes            54
                === libgomp tests ===

Running target unix

                === libgomp Summary ===

# of expected passes            2316
# of expected failures          2
# of unsupported tests          210
                === libitm tests ===

                === libitm Summary ===

# of expected passes            42
# of expected failures          3
# of unsupported tests          1
                === libstdc++ tests ===

                === libstdc++ Summary ===

# of expected passes            12892
# of unexpected failures        8
# of expected failures          78
# of unsupported tests          380

Compiler version: 9.2.0 (GCC) 

I compared it to the output as suggested and saw a few failed tests as well on there, so I’m going to hope all is well.

The final compiling test for this the new toolchain was successful:

(lfs chroot) root:/sources/gcc-9.2.0/build# echo 'int main(){}' > dummy.c
(lfs chroot) root:/sources/gcc-9.2.0/build# cc dummy.c -v -Wl,--verbose &> dummy.log
(lfs chroot) root:/sources/gcc-9.2.0/build# readelf -l a.out | grep ': /lib'
      [Requesting program interpreter: /lib64/]

The rest of the tests also showed the expected results:

(lfs chroot) root:/sources/gcc-9.2.0/build# grep -o '/usr/lib.*/crt[1in].*succeeded' dummy.log
/usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../lib/crt1.o succeeded
/usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../lib/crti.o succeeded
/usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../lib/crtn.o succeeded

(lfs chroot) root:/sources/gcc-9.2.0/build# grep -B4 '^ /usr/include' dummy.log
#include <...> search starts here:

(lfs chroot) root:/sources/gcc-9.2.0/build# grep 'SEARCH.*/usr/lib' dummy.log |sed 's|; |\n|g'

(lfs chroot) root:/sources/gcc-9.2.0/build# grep "/lib.*/ " dummy.log
attempt to open /lib/ succeeded

(lfs chroot) root:/sources/gcc-9.2.0/build# grep found dummy.log
found at /lib/

For the rest of the packages in the chapter everything went pretty well, I ran the test suites for all of the packages that had one and got expected results for each one of them.

6.81. Cleaning Up

I followed most of the cleaning up process, I left the /tools directory present on the system.

Chapter 7 - System Configuration

We have finally reached a point where the system seems to have already been built. It looks from the index like the rest of the steps are more related to configuration of the already installed packages than more installation.

7.1. Introduction

This explains how System V works, which I’m familiar with from using Slackware, but I learned how it’s installed so that was great. I was actually surprised at how fast it was to compile and install the System V binary as well as how small it is.

In the rest of this section I set up the basic files that the new LFS system will use, pretty straight forward.

Chapter 8 - Making the LFS System Bootable

I can finally see the light at the end of the tunnel, we’re close to having a working system (I hope).

8.3. Linux-5.5.3

Something that is usually seen as the single hardest thing to set up on a Linux system is the Kernel. Usually most users are intimidated by the thought of compiling or re-compiling the Kernel for their systems (I include myself in that pool of users).

In LFS this is left for the end, which by this point doesn’t really seem all that involved.

I followed the recommendations and did not make any specific tweaks, the compilation went pretty smoothly.

This was my first time doing this, so it took me longer that it may have taken an experienced Kernel hacker to compile the LFS Kernel.

8.4. Using GRUB to Set Up the Boot Process

I use LILO on the Slackware host system as a boot manager, so I decided to just add a new entry in /etc/lilo.conf for the new LFS system.

I added the following OS block to /etc/lilo.conf:

# LFS bootable partition config begins
image= /boot/vmlinuz-5.5.3-lfs-9.1
  root = /dev/sda3
  label = LFS
# LFS bootable partition config end

The full /etc/lilo.conf looks like this now:

# LILO configuration file
# generated by 'liloconfig'
# Start LILO global section
# Append any additional kernel parameters:
append=" vt.default_utf8=0"
boot = /dev/sda

compact        # faster, but won't work on all systems.

# Boot BMP Image.
# Bitmap in BMP format: 640x480x8
  bitmap = /boot/slack.bmp
# Menu colors (foreground, background, shadow, highlighted
# foreground, highlighted background, highlighted shadow):
  bmp-colors = 255,0,255,0,255,0
# Location of the option table: location x, location y, number of
# columns, lines per column (max 15), "spill" (this is how many
# entries must be in the first column before the next begins to
# be used.  We don't specify it here, as there's just one column.
  bmp-table = 60,6,1,16
# Timer location x, timer location y, foreground color,
# background color, shadow color.
  bmp-timer = 65,27,0,255

# Standard menu.
# Or, you can comment out the bitmap menu above and 
# use a boot message with the standard menu:
#message = /boot/boot_message.txt

# Wait until the timeout to boot (if commented out, boot the
# first entry immediately):
# prompt
# Timeout before the first entry boots.
# This is given in tenths of a second, so 600 for every minute:
# timeout = 1200
# Override dangerous defaults that rewrite the partition table:
# Normal VGA console
vga = normal
# Ask for video mode at boot (time out to normal in 30s)
#vga = ask
# VESA framebuffer console @ 1024x768x64k
# VESA framebuffer console @ 1024x768x32k
# VESA framebuffer console @ 1024x768x256
# VESA framebuffer console @ 800x600x64k
# VESA framebuffer console @ 800x600x32k
# VESA framebuffer console @ 800x600x256
# VESA framebuffer console @ 640x480x64k
# VESA framebuffer console @ 640x480x32k
# VESA framebuffer console @ 640x480x256
# End LILO global section
# LFS bootable partition config begins
image= /boot/vmlinuz-5.5.3-lfs-9.1
  root = /dev/sda3
  label = LFS
# LFS bootable partition config end
# Linux bootable partition config begins
image = /boot/vmlinuz
  root = /dev/sda1
  label = Linux
# Linux bootable partition config ends

Chapter 9 - The End

It seems we have finished building LFS, I haven’t tried rebooting the system yet, so we’ll see.

This chapter still has some configuration steps that need to be done, mainly for system personalization.

9.2. Get Counted

I then registered as an LFS user:

LFS User Registration Screenshot

9.3. Rebooting the System

Part of this section suggests to add a few more packages before rebooting the system, but at the time it was late so I decided not to do this and do it once I got to the BLFS book.

After that I rebooted the system and..success!…sort of. My editing of the /etc/lilo.conf on Slackware seems to have made it so that LFS boots, but I’m not given a choice on whether I want to boot LFS or Slackware from LILO, I’m basically locked out of Slackware. Oops!

I booted a Slackware image from a USB drive as if I was going to install Slackware. I then followed this article from the Slackware Documentation Project to mount the already installed Slackware OS.

I executed the following:

mount /dev/sda3 /mnt

mount -o bind /dev /mnt/dev
mount -o bind /proc /mnt/proc
mount -o bind /sys /mnt/sys

chroot /mnt /bin/bash

vim /etc/lilo.conf

I noticed that I had commented out the lines in LILO that allowed the prompt and timeout settings to be applied, and I forgot to revert these changes so that I could choose from any of the two installed systems.

Here are the lines that were changed from the file that I shared earlier:

# prompt
# timeout = 1200

I simply uncommented these lines, ran /sbin/lilo and rebooted once again. With that everything worked as expected and I was able to choose whether I wanted to boot into the newly built LFS system or the existing Slackware system successfully!


We did it!

That was quite the journey, in total it took me around 18 hours of actual time to build my first LFS system. It went quite smoothly to be honest, I simply followed the steps on the book and everything worked great.

I really enjoyed learning what packages did what exactly and how those packages were installed and configured on the system, I even learned some new commands along the way. I would like to explore BLFS as well, I think it would be nice to install the i3 Window Manager on LFS for example.

I still have to apply the errata patches, which I will take a look at when the time to do BLFS comes.

A big thank you to the whole team that makes LFS possible, the quality of the project is truly amazing.

Additionally, I now have a new kind of respect for all of the people that maintain the different distributions of Linux out there, it is truly an involved process with many intricacies that we get to ignore as users due to their hard work.

The bottom line is if you have some experience with the command line, and around 20 hours of free time along with a desire to learn more about Linux, this is a great experience to have.