Time to roll up your sleeves. We've covered the theory — why LFS matters, how Linux works. Now comes the practical work: setting up an environment where we can actually build a complete Linux system from source code.
This isn't glamorous work. We're checking software versions, creating partitions, and setting up environment variables. But skip any of this preparation, and you'll hit mysterious failures hours into the build process.
Think of this as setting up a clean room for surgery. Everything needs to be exactly right before we start cutting.
Your Host System: What We're Starting With
We need a working Linux system to build our new Linux system. This is your "host" — the stable platform we'll use to bootstrap our target system.
For this series, we're using Debian 12 running on a virtual machine. But any modern Linux distribution will work: Ubuntu, Fedora, openSUSE, Arch, Gentoo. The LFS book supports them all.
Why a VM? Two reasons:
- Safety — If something goes wrong, we can restore a snapshot
- Isolation — We won't pollute our main system with LFS experiments
If you're feeling adventurous, you can build on bare metal. Just be prepared for the possibility of needing to reinstall your host system if you make a serious mistake.
Hardware Requirements: What You Actually Need
According to the official LFS requirements, you need:
- CPU — At least 4 cores recommended (more cores = faster compilation)
- Memory — 8 GB minimum, 16 GB comfortable
- Disk space — 20 GB for the LFS system plus workspace
- Time — Plan for several weekends if you're doing this carefully
Can you get away with less? Sure. An old laptop with 2 cores and 4 GB RAM will work — it'll just take longer. Much longer. GCC compilation on a single core is an exercise in patience.
The real requirement isn't hardware, it's attention to detail. One missed step or mistyped command can waste hours of compilation time.
Checking Your Host System
Before we start, we need to verify that our host system has all the required tools and versions. The LFS project provides a script for this. Let's create and run it:
cat > version-check.sh << "EOF"
#!/bin/bash
# A script to list version numbers of critical development tools
# If you have tools installed in other directories, adjust PATH here AND
# in ~lfs/.bashrc (section 4.4) as well.
LC_ALL=C
PATH=/usr/bin:/bin
bail() { echo "FATAL: $1"; exit 1; }
grep --version > /dev/null 2> /dev/null || bail "grep does not work"
sed '' /dev/null || bail "sed does not work"
sort /dev/null || bail "sort does not work"
ver_check()
{
if ! type -p $2 &>/dev/null
then
echo "ERROR: Cannot find $2 ($1)"; return 1;
fi
v=$($2 --version 2>&1 | grep -E -o '[0-9]+\.[0-9\.]+[a-z]*' | head -n1)
if printf '%s\n' $3 $v | sort --version-sort --check &>/dev/null
then
printf "OK: %-9s %-6s >= $3\n" "$1" "$v"; return 0;
else
printf "ERROR: %-9s is TOO OLD ($3 or later required)\n" "$1";
return 1;
fi
}
ver_kernel()
{
kver=$(uname -r | grep -E -o '^[0-9\.]+')
if printf '%s\n' $1 $kver | sort --version-sort --check &>/dev/null
then
printf "OK: Linux Kernel $kver >= $1\n"; return 0;
else
printf "ERROR: Linux Kernel ($kver) is TOO OLD ($1 or later required)\n" "$kver";
return 1;
fi
}
# Coreutils first because --version-sort needs Coreutils >= 7.0
ver_check Coreutils sort 8.1 || bail "Coreutils too old, stop"
ver_check Bash bash 3.2
ver_check Binutils ld 2.13.1
ver_check Bison bison 2.7
ver_check Diffutils diff 2.8.1
ver_check Findutils find 4.2.31
ver_check Gawk gawk 4.0.1
ver_check GCC gcc 5.4
ver_check "GCC (C++)" g++ 5.4
ver_check Grep grep 2.5.1a
ver_check Gzip gzip 1.3.12
ver_check M4 m4 1.4.10
ver_check Make make 4.0
ver_check Patch patch 2.5.4
ver_check Perl perl 5.8.8
ver_check Python python3 3.4
ver_check Sed sed 4.1.5
ver_check Tar tar 1.22
ver_check Texinfo texi2any 5.0
ver_check Xz xz 5.0.0
ver_kernel 5.4
if mount | grep -q 'devpts on /dev/pts' && [ -e /dev/ptmx ]
then echo "OK: Linux Kernel supports UNIX 98 PTY";
else echo "ERROR: Linux Kernel does NOT support UNIX 98 PTY"; fi
alias_check() {
if $1 --version 2>&1 | grep -qi $2
then printf "OK: %-4s is $2\n" "$1";
else printf "ERROR: %-4s is NOT $2\n" "$1"; fi
}
echo "Aliases:"
alias_check awk GNU
alias_check yacc Bison
alias_check sh Bash
echo "Compiler check:"
if printf "int main(){}" | g++ -x c++ -
then echo "OK: g++ works";
else echo "ERROR: g++ does NOT work"; fi
rm -f a.out
if [ "$(nproc)" = "" ]; then
echo "ERROR: nproc is not available or it produces empty output"
else
echo "OK: nproc reports $(nproc) logical cores are available"
fi
EOF
bash version-check.sh
Run this on your host system. If you see any errors, you'll need to install the missing packages before continuing. On Debian/Ubuntu:
sudo apt update sudo apt install build-essential gawk texinfo bison
The script checks for specific minimum versions because LFS depends on certain features and bug fixes. Don't try to work around version requirements — it'll bite you later.
Understanding the Requirements
Why does LFS need these specific tools and versions?
Bash 3.2+ — The configure scripts expect Bash-specific features. Some distributions use dash for /bin/sh, which can break builds.
Binutils 2.13.1+ — We need the GNU assembler and linker, not BSD variants. The version requirement ensures certain features are available.
GCC 5.4+ — Both C and C++ compilers are required because we'll be building both C and C++ programs. Modern C++ features are expected by recent software versions.
Linux Kernel 5.4+ — We specify this version when building glibc, so workarounds for older kernels aren't included. Keeps the glibc smaller and faster.
This isn't arbitrary pickiness. Each requirement exists because someone hit a build failure without it.
Creating the LFS Partition
Our new Linux system needs its own filesystem. We'll create a dedicated partition for it — separate from our host system's files.
First, let's see what storage is available:
lsblk fdisk -l
For this tutorial, we're assuming you have a spare disk or can create a new partition. We need at least 15 GB, but 20 GB gives us breathing room.
⚠️ Warning: The following commands will destroy data. Make sure you're working with the right device!
Create a new partition using fdisk:
sudo fdisk /dev/sdb # Adjust for your device! # In fdisk: # n (new partition) # p (primary partition) # 1 (partition number) # Enter (accept default start) # Enter (accept default end - uses whole disk) # w (write changes and exit)
Now create a filesystem on the new partition:
sudo mkfs.ext4 /dev/sdb1
We're using ext4 because it's well-tested, performs well, and is fully supported by the Linux kernel. You could use xfs or btrfs, but ext4 removes one variable from the equation.
Setting Up the Mount Point
We need a consistent place to mount our LFS partition. The LFS book uses /mnt/lfs:
sudo mkdir -pv /mnt/lfs
Mount the partition:
sudo mount -v -t ext4 /dev/sdb1 /mnt/lfs
Verify it worked:
df -h /mnt/lfs mount | grep lfs
You should see your new partition mounted with plenty of free space.
The $LFS Environment Variable
Throughout the LFS build, we'll be constantly referring to our LFS partition's mount point. Rather than type /mnt/lfs everywhere (and risk typos), we'll use an environment variable.
Set it in your shell:
export LFS=/mnt/lfs
Test it:
echo $LFS # Should output: /mnt/lfs
Add this to your ~/.bashrc so it persists across shell sessions:
echo 'export LFS=/mnt/lfs' >> ~/.bashrc
This variable will be used in almost every LFS command. $LFS/bin, $LFS/lib, $LFS/etc — everything goes under this prefix.
Creating the lfs User
We could do the entire build as root, but that's dangerous. One wrong command could damage our host system. Instead, we'll create a dedicated user for the LFS build.
Create the user and set a password:
sudo useradd -s /bin/bash -g users -m -k /dev/null lfs sudo passwd lfs
Give the lfs user ownership of the LFS directory:
sudo chown -v lfs $LFS
The lfs user will do most of the build work, switching to root only when necessary for system-level operations.
Setting Up the Build Environment
Now switch to the lfs user:
su - lfs
We need to set up a clean environment for building. Create a minimal .bash_profile:
cat > ~/.bash_profile << "EOF" exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash EOF
And a .bashrc with the exact environment LFS expects:
cat > ~/.bashrc << "EOF" set +h umask 022 LFS=/mnt/lfs LC_ALL=POSIX LFS_TGT=$(uname -m)-lfs-linux-gnu PATH=/usr/bin if [ ! -L /bin ]; then PATH=/bin:$PATH; fi PATH=$LFS/tools/bin:$PATH CONFIG_SITE=$LFS/usr/share/config.site export LFS LC_ALL LFS_TGT PATH CONFIG_SITE EOF
Let's understand what this environment does:
set +h — Disable hash table for command lookup. Forces the shell to search PATH every time, ensuring we use the right version of tools as we build them.
umask 022 — Sets default file permissions. Files created will be readable by everyone, writable only by owner.
LFS_TGT=$(uname -m)-lfs-linux-gnu — This is our target triplet. It tells the build system we're building for LFS, not the host system. Prevents contamination between host and target.
PATH=/usr/bin — Minimal PATH that prioritizes our LFS tools as we build them. Prevents picking up host system tools accidentally.
LC_ALL=POSIX — Forces POSIX-compliant behavior. Prevents locale-specific issues during compilation.
Source the new environment:
source ~/.bash_profile
Verify your environment:
echo $LFS echo $LFS_TGT echo $PATH
Creating the Directory Structure
Our LFS system needs the basic filesystem hierarchy. Create the essential directories:
mkdir -pv $LFS/{etc,var} $LFS/usr/{bin,lib,sbin}
for i in bin lib sbin; do
ln -sv usr/$i $LFS/$i
done
case $(uname -m) in
x86_64) mkdir -pv $LFS/lib64 ;;
esac
Create the tools directory where our cross-compilation toolchain will live:
mkdir -pv $LFS/tools
This structure follows the Filesystem Hierarchy Standard and modern conventions like the merged /usr directory structure.
Package Management Strategy
Unlike binary distributions, LFS doesn't have a package manager. Every piece of software is compiled and installed manually. This raises a question: how do you track what's installed?
For the basic LFS build, the answer is "you don't." Everything is installed to standard locations, and the build process is reproducible enough that you can rebuild if something goes wrong.
Later, in the Beyond LFS (BLFS) book, you can add package management. But for now, simplicity rules.
The Package List: What We're Going to Build
LFS 12.4 builds approximately 85 packages from source. Here's a sample of what we'll be compiling:
Core toolchain:
- Binutils 2.45 — Assembler, linker, and binary utilities
- GCC 15.2.0 — GNU Compiler Collection
- Glibc 2.42 — GNU C Library
- Linux 6.16.1 — The Linux kernel
Essential utilities:
- Bash 5.3 — The Bourne Again Shell
- Coreutils 9.7 — ls, cp, mv, and friends
- Findutils 4.10.0 — find, locate, xargs
- Gawk 5.3.2 — GNU Awk programming language
- Sed 4.9 — Stream editor
System libraries:
- Libffi 3.5.2 — Foreign Function Interface library
- OpenSSL 3.5.2 — Cryptographic library
- Ncurses 6.5 — Terminal handling library
- Readline 8.3 — Command-line editing library
System services:
- Systemd 257.8 — System and service manager
- E2fsprogs 1.47.3 — Ext2/3/4 filesystem utilities
- GRUB 2.12 — Grand Unified Bootloader
Each package serves a specific purpose, and the build order matters. You can't build GCC without binutils. You can't build most things without glibc. The dependency chain determines the sequence.
Download Strategy
We'll download packages as we need them, but you could download everything upfront. The complete source tarball collection is about 500 MB.
Create a sources directory:
mkdir -pv $LFS/sources chmod -v a+wt $LFS/sources
The sticky bit (+t) allows anyone to write files but only the owner can delete them. Good practice for shared build environments.
Compilation Time Estimates
How long will this take? Here are rough estimates for a 4-core system with SSD:
- Binutils — 30 minutes
- GCC (first pass) — 45 minutes
- Glibc — 20 minutes
- GCC (second pass) — 45 minutes
- Linux kernel — 15 minutes (depends on config)
- All other packages — 2-4 hours total
Total: 6-8 hours of compilation time, spread over several sessions as you work through the book.
Modern systems with more cores and NVMe storage can go faster. Older systems or traditional hard drives will take longer.
Backup Strategy
Before starting the build, take a snapshot of your VM or backup your partition. There will be points where a mistake forces you to start over, and you don't want to lose hours of work.
Good checkpoint moments:
- After completing the temporary toolchain
- After entering chroot successfully
- After building the final toolchain
- Before kernel configuration
Common Preparation Mistakes
Things that will cause problems later if you skip them now:
Wrong file permissions — The lfs user needs to own $LFS and its subdirectories.
Missing host tools — Don't skip the version check script. Missing or too-old tools cause cryptic build failures.
Inconsistent environment — Make sure $LFS is set correctly in every shell session.
Host contamination — The clean PATH in ~/.bashrc is crucial. Don't add extra directories that might confuse the build.
Filesystem full — Monitor disk usage during the build. Running out of space during a long compilation is frustrating.
Final Checks
Before we start building, verify everything is ready:
# Verify you're the lfs user whoami # Should output: lfs # Verify LFS is set and mounted echo $LFS ls -la $LFS # Should show the directories we created # Verify the target triplet echo $LFS_TGT # Should output something like: x86_64-lfs-linux-gnu # Check available disk space df -h $LFS # Should show plenty of free space
If any of these checks fail, fix them before continuing. The actual build process is unforgiving of preparation mistakes.
What's Next: The Bootstrap Challenge
We now have a clean environment and all the prerequisites in place. In the next post, we'll dive into the most conceptually challenging part of the entire LFS process: the bootstrap problem.
How do you build a compiler when you need a compiler to build anything? How do you create glibc when everything depends on glibc? We'll solve this with a carefully orchestrated cross-compilation dance.
The preparation work we've done might seem tedious, but it provides the stable foundation we need to tackle circular dependencies, cross-compilation, and the intricate choreography of building a self-hosting Linux system.
Everything's ready. Next post, we tackle the hardest concept in the entire build: the bootstrap problem.
Compiled by AI. Proofread by caffeine. ☕
← Previous: Part 2: How Linux Actually Works
→ Next: Part 4: Building the Cross-Toolchain