Skip to content
ALB Wiki

LFS: Preparing the Build

This is an old revision of this page — revision 8 (2026-10-26 12:54:40 UTC). It may differ from the current revision.

Linux From Scratch begins before a single package compiles — with a host system, a dedicated partition, ninety source tarballs verified by checksum, and a build user wearing a sterilized environment. This chapter is the setup the LFS book assumes and newcomers most often fumble — plus the traps nobody warns you about: the musl-libc host that can't work, the bash-hash cache that poisons toolchains, the /tools symlink that is the build's escape hatch from its own host. Get this right and the build is stamina; get it wrong and nothing after it works.

The chicken-and-egg problem

You cannot compile an operating system without an operating system. The Linux you're building needs a host — an already-running Linux with a working compiler, core utilities, and disk space — to compile against, and against which the new system's early pieces link. The host isn't cheating; it's how all software births. Cross-compilation's whole art is the careful severance of host influence — chapter 13 (the cross-toolchain) is where that severance is engineered. This chapter supplies the host.

Step 1 — The host system

Three valid hosts, in ascending realism:

  • A virtual machine — QEMU/KVM or VirtualBox, 2+ CPUs, 4 GB RAM, 30+ GB disk. The professional choice: snapshots let you rewind any mistake, and nothing you break is real.
  • A dedicated partition or spare machine — the fullest experience; real GRUB, real hardware detection, real consequences.
  • A live distribution on real hardware — possible but fragile; the live environment vanishes on reboot.

We use Slackware or Slint as the host throughout this handbook — its installer teaches partitioning by asking you to do it, its pkgtools leave the machinery visible, and it arrives with a complete development environment (gcc, make, binutils) already aboard. Any modern Linux works; the checks below tell you if yours qualifies.

The trap nobody warns about: your host's libc

The host must run glibc. LFS builds against GNU libc's headers and toolchain assumptions — an Alpine/musl or other non-glibc host will fail obscurely and early, in ways that look like compiler bugs but are actually libc mismatches. Check first:

ldd --version | head -1       # "ldd (GNU libc) 2.xx" = good; musl = wrong host

Docker images based on alpine, musl-based distros, and BusyBox-only environments are disqualified as hosts — fine as targets of curiosity, wrong as LFS mothers. WSL works for practicing tools but is a trap for the real build — no real partitions, no loop devices by default; the workaround (a loop-mounted disk image inside WSL) is documented below.

Verify the host qualifies

The LFS book carries a version_check.sh ritual — the minimum versions the build requires. On your host, prove each:

bash --version | head -1          # >= 3.2
binutils: ld --version | head -1  # >= 2.13.1
bison --version | head -1
bzip2 --version
coreutils: chown --version | head -1  # >= 6.9
diffutils: diff --version | head -1
findutils: find --version | head -1   # >= 4.2.31
gawk --version | head -1              # >= 4.0.1 — gawk specifically, NOT mawk
gcc --version | head -1               # >= 5.2
grep --version | head -1
gzip --version
uname -r                              # kernel >= 5.4
m4 --version | head -1
make --version | head -1              # >= 4.0
patch --version | head -1
perl -V:version                       # >= 5.8.8
python3 --version                     # >= 3.4
sed --version | head -1
tar --version | head -1               # >= 1.22
texinfo: makeinfo --version | head -1 # the one everyone forgets
xz --version

Every check must pass — the build's earliest packages lean on all of them. Slackware ships a clean pass out of the box; on Debian-family hosts apt install build-essential gawk bison texinfo closes the usual gaps. Two subtleties: gawk is required, not mawk (Debian defaults to mawk — install gawk explicitly), and texinfo's makeinfo is the classic missing package — several builds fail silently without it, weeks of confusion for a one-package fix.

Step 2 — The partition

The new system gets its own home — a dedicated partition it will fill. Before carving, one architectural decision: BIOS/MBR or UEFI/GPT. UEFI hosts want GPT (gdisk) and eventually an EFI System Partition; BIOS-era hosts take MBR (fdisk) and a simpler boot. For a VM you choose when creating the machine — this handbook covers both, with the GPT path primary.

# as root — find the target disk
fdisk -l
lsblk

# carve it — gdisk on GPT, fdisk on MBR
gdisk /dev/sda          # new partition ~30G, type 8300 (Linux filesystem)
# UEFI builders add a small ESP: ~300M, type EF00, FAT32

# format the LFS partition
mkfs.ext4 /dev/sda3
# and the ESP if UEFI: mkfs.vfat /dev/sda1

# the staging ground
mkdir -pv /mnt/lfs
mount /dev/sda3 /mnt/lfs

Why ext4 and not something exotic: the build writes hundreds of thousands of small files — ext4's journaling and tooling (e2fsprogs, which you build inside LFS) are the boring, correct choice. Btrfs/XFS work but add variables nobody needs on a first build.

A swap partition helps if your host is lean — an existing swap can be shared: swapon /dev/sda2. On a VM, a second virtual disk is cleanest — zero risk to the host's own disk.

Make it persistent — the build reboots:

echo '/dev/sda3  /mnt/lfs  ext4  defaults  1 1' >> /etc/fstab
# and if you made a swap: /dev/sda2  none  swap  pri=1  0 0

The no-partition path: a loop-mounted image file

No spare partition? Build inside a disk image file — the WSL workaround and the safest experiment:

dd if=/dev/zero of=lfs.img bs=1G count=32 status=progress   # 32 GB sparse-able image
mkfs.ext4 lfs.img
mkdir -pv /mnt/lfs
mount -o loop lfs.img /mnt/lfs

The whole LFS system lives in one file — backup it with cp, snapshot with cp --reflink on CoW filesystems, delete when done. Slightly slower than a real partition; infinitely safer.

The live-USB host — the trap of the ephemeral environment

Building LFS from a live USB (Slackware live, a rescue distro, any bootable image) is legal and occasionally the only option — but every live environment is ephemeral: reboot and your $LFS mount, your fstab edit, your lfs user, your dotfiles are all gone while the partition's contents survive. The fix is discipline: keep $LFS on the persistent partition, script the mount + fstab + useradd as one repeatable setup-host.sh, and re-run it after every boot. Treat the live environment as disposable scaffolding around a permanent partition — never the other way round.

Step 3 — $LFS and the sources tree

Everything hangs off one environment variable, $LFS — the mountpoint of the partition:

export LFS=/mnt/lfs
mkdir -pv $LFS/sources
chmod -v a+wt $LFS/sources        # world-writable + sticky, /tmp-style

Download the book's package manifest — wget-list (recent editions split it into wget-list-sysv / wget-list-systemd; ~90 tarballs) and md5sums (their checksums) — into $LFS/sources, then fetch:

cd $LFS/sources
wget --input-file=wget-list --continue --directory-prefix=$LFS/sources
# verify every tarball against its checksum BEFORE building
pushd $LFS/sources && md5sum -c md5sums && popd

Check every checksum before building. A corrupt tarball discovered three chapters in is a weekend lost; md5sum -c catches it now. Where upstream ships GPG signatures, gpg --verify them too — the sources become the system's DNA. --continue resumes partial downloads — the ~90-package fetch survives dropped connections. For bulk downloads aria2c -i wget-list parallelizes across mirrors; wget is the book's way and everywhere.

The offline angle nobody plans for

Download all tarballs before starting, and keep $LFS/sources — mid-build network failures are common, and an LFS build with a dead network stalls forever waiting on a tarball. rsync -a $LFS/sources/ backups/lfs-sources/ once at the start is a two-minute insurance policy.

Architecture honesty

This handbook builds x86_64 (LFS_TGT=x86_64-lfs-linux-gnu). LFS supports several arches (and CLFS — Cross LFS — targets them all), but a first build has no business cross-arching: the cross-toolchain already asks gcc to build for a foreign target; changing the machine class on top doubles the failure space. Build native, on x86_64, for x86_64. 32-bit builds exist in the book's lineage but are a museum path — modern LFS assumes 64-bit, and a 32-bit host cannot produce a 64-bit kernel without serious gymnastics. Check before you start:

uname -m        # must say x86_64 — the build assumes it everywhere

Step 4 — The toolchain staging area

mkdir -pv $LFS/tools      # where the cross-toolchain will live
ln -sv $LFS/tools /       # /tools symlink — the trick the whole build pivots on

That symlink is subtle and load-bearing — worth understanding before you make it. The cross-toolchain's early binaries are compiled with paths baked in as /tools/.... On the host, /tools resolves to $LFS/tools through this symlink. Later, inside the chroot — where $LFS is the root — /tools resolves to the same directory, same absolute path, no rewriting. It's how the build escapes its host's filesystem while still being built by it — a one-line filesystem judo move that makes the entire chroot transition seamless.

Step 5 — The lfs user

Build as a dedicated user, never root — one mis-typed make install as root overwrites your host's /usr and the host dies mid-build. This has happened to everyone who skipped the warning.

groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
passwd lfs

# hand the staging ground to lfs
chown -v lfs $LFS/{usr{,/*},lib,var,etc,bin,sbin,tools}
case $(uname -m) in
  x86_64) chown -v lfs $LFS/lib64 ;;
esac
chown -v lfs $LFS/sources

Two details nobody thinks of: -s /bin/bash must be explicit — on Debian-family hosts new users default to dash, and dash is not bash (the builds require bash); -k /dev/null gives lfs an empty skeleton directory so no host dotfiles leak into its environment. And the UID you create now will own files inside the finished system until the book's cleanup stages normalize ownership — a small oddity you'll see in ls -l output later.

The sterile environment

Login as su - lfs (the - matters — a full login shell) and write the two files that make every build correct:

cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF

env -i gives lfs a sterile environment — every login respawns bash with only HOME, TERM, and PS1: no inherited host variables, no leaked LD_LIBRARY_PATH, no distro-local CFLAGS quietly poisoning cross-compilation. TERM is kept so your terminal still renders correctly.

cat > ~/.bashrc << "EOF"
set +h                     # no hashing — always re-find binaries fresh
umask 022                  # sane default permissions
LFS=/mnt/lfs
LC_ALL=POSIX               # predictable locale for reproducible builds
LFS_TGT=x86_64-lfs-linux-gnu   # the target triplet — our system's name
PATH=$LFS/tools/bin:$PATH  # our toolchain first, host's second
MAKEFLAGS='-j$(nproc)'     # parallel builds across all cores
export LFS LC_ALL LFS_TGT PATH MAKEFLAGS
EOF
source ~/.bash_profile

Each line earns its place, and three of them defend against specific ghosts:

  • set +h — bash caches binary locations in a hash table (hash shows it). Mid-build, your fresh $LFS/tools/bin/gcc replaces the host's gcc — but a hashed bash would keep running the old one. set +h forces PATH re-resolution every call. This single line prevents a class of "my new toolchain isn't being used" bugs that look like curses.
  • LC_ALL=POSIX — builds that sort, grep, and awk differently under different locales produce different systems. POSIX locale makes every build byte-reproducible — the difference between a toolchain that works and one whose configure probes disagree with themselves.
  • LFS_TGT — the GNU target triplet (x86_64-lfs-linux-gnu) naming your new system's architecture-vendor-OS — every --target=$LFS_TGT in the cross-toolchain builds for it; it's how gcc learns it is building a foreign system's compiler.
  • MAKEFLAGS='-j$(nproc)' — parallel builds across every core. Hours become minutes — with one honest caveat: a handful of old packages have racy Makefiles; if a build fails weirdly with -j, retry serial before debugging anything else.

Step 6 — The final checks

su - lfs
echo $LFS $LFS_TGT          # must print /mnt/lfs and the triplet
which gcc                   # host's gcc — until tools/ exists
mount | grep lfs            # partition still mounted
df -h $LFS                  # space confirmed
id                          # you are lfs — NOT root

Then, and only then, the build begins.

The discipline nobody teaches: log everything

The single most useful LFS habit isn't in the book — record every command and its output. Builds fail silently, and the forensic trail is everything:

# as lfs — capture everything, forever
exec > >(tee -a $LFS/build.log) 2>&1

or simply run each package build inside script -a $LFS/logs/<pkg>.log. When a configure probe fails three packages deep, the log tells you which host assumption poisoned it. Disk space for logs is trivial; the information is irreplaceable. Keep a plain-text journal of which packages installed and in what order — when the finished system misbehaves, that journal is the difference between debugging and guessing.

Disk-space planning

30 GB is the floor; 40 GB is comfortable:

WhatSpace
$LFS/sources (~90 tarballs)~500 MB
Extracted + build trees~4–6 GB
/tools cross-toolchain~1 GB
The finished system~3–4 GB
Slack (logs, temp, rebuilds)the rest

Running out of space mid-build is the second-most-common silent killer after wrong-user builds — df -h $LFS between major packages.

Eyes-free LFS

Every step in this chapter works in a virtual console with no GUI — the book's entire toolchain is text. Speakup users can build LFS from a talking console exactly as sighted users do from xterm; makeinfo-free documentation paths and man/info pages are all readable. The accessibility chapter of the finished system (BRLTTY, Speakup inside the kernel you build) is its own handbook page — but the build itself needs nothing graphical. See Speakup.

The source manifest — what ~90 tarballs means

The wget-list is the build's bill of materials — ~90 packages at pinned versions, each with a checksum. Reading the list once teaches what a Linux actually is:

the toolchain:        binutils gcc glibc gmp mpfr mpc isl zstd
the build machinery:  m4 bison flex autoconf automake libtool pkgconf ninja meson
the userland:         bash coreutils diffutils findutils gawk grep sed tar gzip bzip2 xz
                      procps-ng psmisc util-linux e2fsprogs file less iproute2 kbd kmod
                      shadow attr acl libcap vim groff man-db man-pages inetutils
the libraries:        ncurses readline zlib libffi expat libxml2 libxcrypt openssl
                      elfutils libtasn1 p11-kit libpipeline libseccomp gettext texinfo
scripting:            perl python wheel setuptools markupsafe jinja2
system plumbing:      eudev sysklogd sysvinit iana-etc linux (the kernel itself)
misc:                 check gdbm libtool make patch sudo-adjacent bits

Every tarball's version is pinned in the book's edition — mixing versions is the classic way to break the build: a newer gcc's stricter parsing chokes on older packages' code, an older binutils can't assemble a newer gcc's output. The book's manifest is a tested combination — treat it as such, or understand why each deviation is safe.

The lfs environment — the complete anatomy

# ~/.bash_profile — sterile login
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash

# ~/.bashrc — the build environment
set +h
umask 022
LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=x86_64-lfs-linux-gnu
PATH=$LFS/tools/bin:$PATH
MAKEFLAGS='-j$(nproc)'
export LFS LC_ALL LFS_TGT PATH MAKEFLAGS

The env-var table — every variable's job:

VariableJob
LFSThe mountpoint — every path the build writes roots here
LFS_TGTThe target triplet — --target/--host for every cross-build
LC_ALL=POSIXLocale-independent builds — reproducible sorting/collation
MAKEFLAGS=-j$(nproc)Parallel builds — the hours-saver
PATH=$LFS/tools/bin:...The new toolchain found first
set +hNo command-path hashing — always re-resolve
umask 022Files 644/dirs 755 — sane defaults
env -iSterile env — no host contamination

The version-check roster — the complete minimums

The LFS book's host requirements — every tool, minimum version:

bash-3.2+        binutils-2.13.1+  bison-2.7+      bzip2-1.0.4+   coreutils-6.9+
diffutils-2.8.1+ findutils-4.2.31+ gawk-4.0.1+     gcc-5.2+       glibc-2.36+
grep-2.5.1a+     gzip-1.3.12+      linux-5.4+      m4-1.4.10+     make-4.0+
patch-2.5.4+     perl-5.8.8+       python-3.4+     sed-4.1.5+     tar-1.22+
texinfo-5.0+     xz-5.0.0+

The version_check.sh convention — a script that runs the roster — ships with the book; write it yourself from the table, run it before step 1 ends.

The disk layouts — the three valid schemes

Simple GPT/UEFI:                BIOS/MBR classic:            The VM-friendly:
sda1  ESP   300M vfat EF00      sda1  swap   4G   8200       vda    1 partition
sda2  swap  4G   swap 8200      sda2  /      36G  8300       (no swap needed —
sda3  /     36G  ext4 8300                                the VM has host swap)

The ESP sizing rule: ~300MB FAT32, type EF00, mounted /boot/efi — not the root partition. BIOS/MBR needs no ESP — the bootloader lives in the boot sector. VM builds can skip swap entirely.

What an SBU is

The book measures build costs in SBUs — Standard Build Units: the time the first package (binutils pass 1) takes on your machine, used as the unit for every other package's estimate. "This package takes 5 SBU" means five times your binutils-1 build. On a modern multi-core host an SBU is a few minutes; measure yours once and every estimate in the book becomes concrete.

The host's hardware — the inventory the build needs

lscpu                    # cores, threads, arch, flags (avx etc)
lsblk                    # disks
free -h                  # RAM + swap
df -h                    # filesystems
lsusb; lspci             # peripherals
uname -a                 # kernel + arch
cat /etc/os-release      # which distro

The sizing rules: RAM — 4GB workable, 8GB comfortable, glibc/gcc want parallel-headroom; disk — 30GB minimum, 40GB comfortable; CPU — more cores = lower wall-time, glibc+gcc are the long poles; swap — needed if RAM <4GB or -j is aggressive (MAKEFLAGS=-j$(nproc) with 2GB RAM swaps itself to death — halve the -j or add swap).

The QEMU recipe — the complete host

# create the disk
qemu-img create -f qcow2 lfs.qcow2 40G
# boot the installer
qemu-system-x86_64 -m 4096 -smp 4 -enable-kvm \
    -drive file=lfs.qcow2,if=virtio \
    -cdrom slint64-15.0.iso -boot d \
    -net nic -net user -display gtk
# snapshot before risky phases
qemu-img snapshot -c before-cross lfs.qcow2
qemu-img snapshot -l lfs.qcow2          # list
qemu-img snapshot -a before-cross lfs.qcow2  # revert
# UEFI variant: -bios /usr/share/edk2/ovmf/OVMF_CODE.fd
# serial console: -nographic -serial mon:stdio

The VM's virtues for LFS specifically: snapshots between build phases (cross-toolchain → temp-system → chroot → system packages → kernel), clones for parallel experiments, no hardware to break. virtio devices are the paravirtual drivers Linux supports natively — fast disk + net.

The download discipline — the whole 90-tarball ritual

cd $LFS/sources
# wget-list.txt + md5sums ship with the book's release
wget --input-file=wget-list.txt --continue --directory-prefix=$LFS/sources
# or parallel: aria2c -i wget-list.txt -d $LFS/sources -x4 -s4
pushd $LFS/sources && md5sum -c md5sums && popd
# where upstream signs: gpg --keyserver hkps://keys.openpgp.org --recv-keys <id>
# then: gpg --verify package.tar.xz.sig package.tar.xz

--continue resumes partial downloads; md5sum -c is the pre-build gate; GPG is the trust layer where upstream ships .sig files. Keep $LFS/sources after the build — it's the offline-rebuild insurance, and the manifest records exactly which versions produced the system.

The two books — choose now

LFS ships two editions: SysVinit and systemd. The choice is your new system's PID-1 — made now because later chapters fork on it. For this handbook we follow SysVinit — matching Slackware/Slint's BSD-style rc scripts, and because rc.M is a file you can read while a systemd unit graph is not — with systemd's parallel path flagged wherever the books diverge. Either teaches the same machine.

Where builds go wrong here

SymptomAlmost always
make install wrote to /usr on the hostYou were root — always build as lfs
Tools not found after installset +h missing — bash cached old paths
Weird locale-dependent failuresLC_ALL unset — export POSIX
Host leaked into a build.bash_profile lacking env -i — sterile env required
/tools empty inside chrootThe /tools symlink wasn't made on the host side
Build can't write $LFSPartition unmounted after reboot — fstab entry
Tarball fails to extractChecksum it before — corrupt download
Configure can't find libcHost is musl-based — wrong host entirely
lfs's shell behaves oddlyDefaulted to dash — -s /bin/bash required
A package fails only with -jRacy Makefile — retry make serial
makeinfo missing mid-buildtexinfo — the forgotten requirement

The honest time budget

PhaseWall time (modern host, -j$(nproc))
This chapter — host, partition, sources, envan evening
Cross-toolchain (ch. next)1–3 hours
Temporary system + chroot3–6 hours
Full system packages4–10 hours
Kernel + boot + first boot1–2 hours
Whole builda weekend to a week

The book says LFS is not a race — and it isn't. Slow is correct; every configure probe read properly is a bug you'll never meet.

Exercises

  1. Run the full version-check roster on your host; note every version that would disqualify it — and check ldd --version for the glibc trap.
  2. Partition a scratch VM disk by hand — gdisk, mkfs.ext4, mount, fstab, reboot, verify it returns.
  3. Build the loop-image variant (lfs.img) alongside the partition — understand both paths.
  4. Download five real upstream tarballs into $LFS/sources; md5sum -c them; then corrupt one deliberately and watch the check catch it.
  5. As lfs, deliberately break $PATH — then explain why set +h exists.
  6. Time a trivial ./configure && make and compute your SBU intuition.

Timeline

StepWhat you did
HostVerified a glibc-based Linux with dev tools
PartitionCarved, formatted, mounted /mnt/lfs, persisted in fstab
Sources~90 tarballs, checksum-verified, in $LFS/sources
Staging$LFS/tools + the /tools symlink
Userlfs with sterile env, LFS_TGT, MAKEFLAGS, set +h
NextLFS I — the cross-toolchain: binutils → gcc → glibc

References

  • linuxfromscratch.org — LFS book (SysV & systemd editions), chapters 2–4: host requirements, preparing a partition, packages & patches, final preparations
  • Hosted mirror: /dist/books/ — the official LFS book mirrored on this site (v10.0–13.1, SysV + systemd editions; CC BY-NC-SA 2.0 / MIT, hosted verbatim with attribution)
  • The version_check.sh convention — the book's own host-qualification script
  • wget-list / md5sums manifests shipped with each LFS release

See also

  • Linux — chapter 1: the landscape and the handbook map
  • Speakup — the eyes-free Linux path
This page was last edited 2026-10-26 12:54:40 UTC by devin. What links here