📖 LFS Series — Part 3 of 15 | Previously: Part 2: How Linux Actually Works

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:

  1. Safety — If something goes wrong, we can restore a snapshot
  2. 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:

Essential utilities:

System libraries:

System services:

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. ☕


📖 LFS Series Navigation
← Previous: Part 2: How Linux Actually Works
→ Next: Part 4: Building the Cross-Toolchain