Build Linux From Scratch using Bazel! This hybrid build system combines the dependency management and caching power of Bazel with the traditional shell/make-based LFS build process.
What makes this special? Instead of forcing LFS into pure Bazel semantics, we use Bazel as a smart orchestrator that respects the LFS book's traditional build patterns while adding dependency tracking, caching, and runnable validation targets.
This project implements the entire Linux From Scratch 13.0-systemd build process using Bazel build rules. The recipes contain x86_64 and aarch64 branches; the complete build and boot path is currently verified only for native arm64 (aarch64) on Apple Silicon using a rootless Podman worker.
- Three-stage bootstrap from your host system to a fully independent OS
- Container-isolated builds with explicit dependency tracking
- Incremental compilation using Bazel's caching
- A repeatable, inspectable LFS build pipeline
Perfect for learning about Linux internals, build systems, or just building your own custom Linux distribution!
lfs-bzl/
βββ src/ # Bazel workspace root
β βββ packages/ # LFS chapter implementations
β β βββ chapter_05/ # Cross-toolchain (5 packages)
β β βββ chapter_06/ # Temporary tools (17 packages)
β β βββ chapter_07/ # Chroot preparation (6 packages)
β β βββ chapter_08/ # Final system (79 packages)
β β βββ chapter_09/ # System configuration
β β βββ chapter_10/ # Linux kernel + GRUB config
β β βββ chapter_11/ # Release files (The End) π
β β βββ hello_world/ # Toolchain validation tests
β βββ tools/ # Custom Bazel rules (lfs_package.bzl, lfs_macros.bzl, etc.)
β βββ sysroot/ # π― Build artifacts (your LFS system!)
β βββ MODULE.bazel # Source package definitions
βββ docs/ # Documentation and design notes
Before you begin, you'll need:
- Bazel/Bazelisk with bzlmod support. The repository does not yet pin a tested Bazel release, so use of Bazelisk's moving default is a known gap.
- Podman 3.0+ configured for rootless containers. All build phases execute through the Podman worker.
- Host tools: Bash, Podman, and the normal Bazel prerequisites. Run
bazel test //packages/chapter_02:podman_check_testand thenbazel build //packages/chapter_02:version_checkto validate the environment. - Disk space: allow at least 20GB. A completed local sysroot is currently about 13GB before Bazel's download and action caches.
- Optional boot validation: QEMU 10+, matching aarch64 UEFI firmware, and
Expect (used by
//packages/chapter_11:boot_test). - No sudo required! Entire build runs as regular user with rootless Podman
# IMPORTANT: Bazel workspace root is `src/` (commands won't work from repo root)
cd src
# 1οΈβ£ Verify Podman and the bootstrap container meet LFS requirements
bazel test //packages/chapter_02:podman_check_test
bazel build //packages/chapter_02:version_check
# 2οΈβ£ Build the cross-toolchain (Chapter 5)
bazel build //packages/chapter_05
# 3οΈβ£ Build all temporary tools (Chapter 6)
bazel build //packages/chapter_06
# 4οΈβ£ Build Chapter 7 chroot base system (rootless Podman worker - no sudo!)
bazel build //packages/chapter_07
# 5οΈβ£ Build Chapter 8 final system (79 packages - rootless Podman worker)
bazel build //packages/chapter_08
# 6οΈβ£ Configure System (Chapter 9)
bazel build //packages/chapter_09
# 7οΈβ£ Make System Bootable (Chapter 10) - builds Linux kernel
bazel build //packages/chapter_10
# 8οΈβ£ Finalize System (Chapter 11) - creates release files
bazel build //packages/chapter_11
# 9οΈβ£ Create the self-booting UEFI disk image -> sysroot/lfs-uefi.img (optional)
bazel build //packages/chapter_11:bootable
# (fast-iteration raw image instead: //packages/chapter_11:bootable_quick)
# π§ͺ Validate each toolchain stage:
bazel build //packages/hello_world:hello_cross # Cross Toolchain (Ch 5) β
bazel build //packages/hello_world:hello_chroot # Chroot Tools (Ch 7) β
bazel build //packages/hello_world:hello_final # Final System (Ch 8) β
Build Artifacts Location: src/sysroot/
- Chapter 5 cross-toolchain:
src/sysroot/tools/bin/ - Chapter 6 temporary tools:
src/sysroot/usr/bin/,src/sysroot/usr/lib/ - Chapter 7+ final system:
src/sysroot/(root filesystem)
This project builds four distinct toolchain stages in sequence, each more capable than the last. This mirrors the traditional LFS bootstrap process:
- Location: The Podman worker container (
/usr/bin/gcc, etc.) - Purpose: Bootstrap the cross-toolchain (Chapter 5)
- Verified by:
bazel build //packages/chapter_02:version_check - Limitation: This first stage comes from the container image rather than the LFS build
- Bazel Target:
//packages/chapter_05:cross_toolchain - Location:
$LFS/tools/bin(e.g.,aarch64-lfs-linux-gnu-gcc; the triplet is$(uname -m)-lfs-linux-gnu, derived from the build container's architecture) - Purpose: Build temporary tools (Chapter 6) for the LFS target
- Key Components:
- Binutils Pass 1 (assembler, linker)
- GCC Pass 1 (C/C++ compiler, minimal libc)
- Linux API headers
- Glibc (C library)
- Libstdc++ (C++ standard library)
- Validation:
bazel run //packages/hello_world:hello_cross - Why? Isolates from host system contamination
- Bazel Target:
//packages/chapter_06:temp_tools_toolchain - Location:
$LFS/usr/bin(rebuiltgcc,binutils, etc.) - Purpose: Full-featured temporary toolchain with POSIX threads, ready for chroot
- Key Components:
- Binutils Pass 2 (rebuilt with complete utilities)
- GCC Pass 2 (full compiler with threading support)
- 17 core utilities (bash, coreutils, make, grep, etc.)
- IMPORTANT: π This toolchain is cross-compiled to run ON the LFS target, not the host!
- Cannot run directly on host system (binaries are linked against LFS glibc)
- Requires chroot environment to execute
- Validation happens in Chapter 7 when building inside chroot
- Bazel Target:
//packages/chapter_08:toolchain - Location:
$LFS/usr/bin(native GCC, built inside chroot) - Purpose: The complete, self-hosting toolchain for the final system
- Key Components:
- Native GCC 15.2.0 (built inside chroot, no host dependencies)
- Native Binutils 2.46.0
- Glibc 2.43
- 79 total packages (compression, security, python, systemd, etc.)
- Validation:
bazel build //packages/hello_world:hello_final - Result: A fully independent, bootable Linux system!
Container GCC β builds β Cross Toolchain (Ch 5)
β
Cross Toolchain β builds β Temp Tools (Ch 6)
β
Temp Tools β builds β Chroot Base (Ch 7)
β
Chroot Base β builds β Final System (Ch 8)
β
Bootable Linux! π§
Each stage removes dependency on the previous, creating a fully independent system.
Validation targets at each stage:
//packages/hello_world:hello_cross- Cross toolchain (runs on host)//packages/hello_world:hello_chroot- Chroot tools (runs in container)//packages/hello_world:hello_final- Final system GCC (builds deps if needed, cached after)
This project uses a unique hybrid approach across different LFS chapters:
- Run directly in the rootless Podman worker container (without chroot)
- Write to staging sysroot (
src/sysroot/) - No host sudo required
- Fast, simple, cached by Bazel
- Persistent Bazel JSON worker running in rootless Podman container
- Container mounts staging sysroot at
/lfs - All builds run as regular user (no sudo required!)
- Fast: container stays alive across builds, amortizing startup cost
- Isolated:
--network=noneenforces offline builds - Mounts virtual filesystems (
/dev,/proc,/sys,/run) inside container - Single worker instance to avoid race conditions (configured in
.bazelrc)
The Result: Modern container-based workflow with zero sudo requirements for the entire build process.
Current implementation status:
- β Chapter 5: Cross-toolchain (5 packages) - Podman worker container
- β Chapter 6: Temporary tools (17 packages) - Podman worker container
- β Chapter 7: Chroot base system (6 packages) - Rootless Podman worker
- β Chapter 8: Final system (79 packages) - Rootless Podman worker
- β Chapter 9: System Configuration (Systemd, Network, Shells)
- β Chapter 10: Linux Kernel + GRUB bootloader config
- β Chapter 11: Release files (lfs-release, os-release, lsb-release)
π LFS 13.0-systemd BUILD COMPLETE! The sysroot contains a bootable Linux system (native aarch64: glibc 2.43, gcc 15.2.0, binutils 2.46.0, kernel 6.18.10).
- β
BLFS extras (
//packages/blfs): sudo, openssh, curl, git, CA certificates, plus login/network config (root passwordlfs, sshd + systemd-resolved enabled) β the booted VM has working DHCP, DNS and ssh.
Warning
The generated image is a development image with the public root password
lfs and password-based root SSH enabled. Do not expose it to an untrusted
network; change the credentials and SSH policy before any non-local use.
The image now carries everything needed to boot on its own: the kernel, an
initramfs (initramfs-6.18.10-lfs-13.0-systemd.img, built in-chroot by
mkinitramfs + LFS-built cpio), and a real UEFI GRUB binary embedded in
the image's EFI System Partition β no QEMU -kernel injection required. The
root filesystem is found by LABEL=lfs-root, which the initramfs resolves.
Boot-verified 2026-07-07 on Apple Silicon (QEMU 10 + hvf + edk2-aarch64): UEFI β GRUB 2.14 β Linux 6.18.10-lfs-13.0-systemd β systemd β login prompt.
# Build the real self-booting UEFI disk image -> sysroot/lfs-uefi.img
# (Bazel builds kernel + initramfs + GRUB EFI in-chroot; the container only
# packages the GPT + FAT ESP via mtools/sgdisk/dd β no loop devices, no sudo)
bazel build //packages/chapter_11:bootable
# Boot in QEMU with aarch64 UEFI firmware (Homebrew qemu ships
# edk2-aarch64-code.fd). Firmware auto-boots the removable-media fallback
# \EFI\BOOT\BOOTAA64.EFI β no bootloader install, no NVRAM entry, no efibootmgr.
qemu-system-aarch64 -M virt -cpu host -accel hvf -m 4G \
-drive if=pflash,format=raw,readonly=on,file="$(brew --prefix)/share/qemu/edk2-aarch64-code.fd" \
-drive file=sysroot/lfs-uefi.img,format=raw,if=virtio \
-netdev user,id=n0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=n0 \
-nographic
# Log in as root (password: lfs), or from another terminal:
# ssh -p 2222 root@localhost
# Exit QEMU: Ctrl-a x
# Automated boot gate (boots a -snapshot copy, checks login + DHCP):
# bazel test //packages/chapter_11:boot_test
# Note: the embedded GRUB config does not pin console=ttyAMA0 on the kernel
# command line; if no output appears after GRUB with -nographic, drop
# -nographic and add -device virtio-gpu-pci for a graphical console.The original raw-image path is kept for quick iteration β it skips the bootloader/ESP assembly and injects the kernel directly:
# Build the raw ext4 image (aliases: :bootable_quick or :create_disk_image)
bazel build //packages/chapter_11:bootable_quick
# Boot with QEMU (-kernel injection; no in-image bootloader)
qemu-system-aarch64 -M virt -cpu host -accel hvf -m 4G \
-kernel sysroot/boot/vmlinuz-6.18.10-lfs-13.0-systemd \
-initrd sysroot/boot/initramfs-6.18.10-lfs-13.0-systemd.img \
-append "root=LABEL=lfs-root rw console=ttyAMA0" \
-drive file=sysroot/lfs.img,format=raw,if=virtio \
-nographic
# Exit QEMU: Ctrl-a xDesign Decisions:
- Init System: systemd (not SysVinit)
- Strip Command: Skipped (optional per LFS book)
- Expected Test Failures: Some tests fail in chroot - this is documented and expected per LFS book
See docs/status.md for detailed progress tracking.
The rootless Podman worker (used for Chapter 7-8+) requires initial setup:
Symptom: Podman commands fail or container can't start
Solution: Ensure Podman is installed and configured for rootless mode:
# Check Podman version
podman --version # Should be 3.0+
# Test rootless container
podman run --rm hello-worldBest practice: Build container image before starting chroot builds:
cd src
bazel run //tools/podman:container_imageSee docs/troubleshooting.md for detailed setup and troubleshooting.
To completely restart the build from scratch:
cd src
# 1οΈβ£ Remove the entire sysroot directory
rm -rf sysroot/
# 2οΈβ£ Clean Bazel's cache
bazel clean --expunge
# 3οΈβ£ Rebuild the complete system and bootable image
# Build cross-toolchain (Chapter 5) - ~5-10 minutes
bazel build //packages/chapter_05:cross_toolchain
# Build temporary tools (Chapter 6) - ~30-45 minutes
bazel build //packages/chapter_06
# Build Chapter 7 chroot base system - ~5-10 minutes
bazel build //packages/chapter_07
# Build Chapters 8-11, BLFS extras, and the UEFI image
bazel build //packages/chapter_11:bootableBuild time varies substantially by hardware, cache state, and package test load. A clean build is a multi-hour operation.
Important Notes:
- No sudo required! The Podman worker handles all containerization internally.
- Always clean both sysroot AND Bazel cache together. The sysroot and Bazel cache must stay in sync. If you clean one without the other, install scripts may fail when encountering existing files.
You don't need to start from scratch if you want to iterate on a later chapter:
Restart Chapter 6 only:
# Remove Chapter 6 artifacts
rm -rf sysroot/usr/
# Rebuild Chapter 6
bazel clean # Clear Bazel's action cache
bazel build //packages/chapter_06Restart Chapter 7 only:
# Remove Chapter 7 artifacts
rm -rf sysroot/{bin,sbin,lib,lib64,etc,var}
rm -rf sysroot/usr/bin/{bison,perl,python3,makeinfo}
# Rebuild Chapter 7
bazel clean
bazel build //packages/chapter_07:chroot_cleanupClean build (start from scratch):
bazel run //tools/podman:cleanup_orphaned # release any stuck containers/mounts
rm -rf sysroot/
bazel clean --expunge
bazel build //packages/chapter_06Incremental build (preserve sysroot):
# Just rebuild a specific target
bazel build //packages/chapter_06:m4
# Or clean Bazel's cache but keep sysroot
bazel clean
bazel build //packages/chapter_06Best practice: Use incremental builds during development. Only do clean builds when troubleshooting or starting fresh.
- Unsandboxed execution: Builds run outside Bazel's sandbox to write into
src/sysroot/ - Build logs: Written to
bazel-out/lfs-logs/<target>.login the Bazel execroot - Dependency tracking: Each package creates a
.donemarker file for Bazel
- Chapter mapping: Each LFS chapter maps to a package directory (
src/packages/chapter_XX/) - Custom rules: Build logic lives in
src/tools/lfs_package.bzlandlfs_macros.bzl - Source definitions: Package URLs and checksums in
src/MODULE.bazel
- DESIGN.md - Architecture and "Managed Chaos" philosophy
- docs/status.md - Build progress tracker
- docs/tools.md - Bazel rules reference
- docs/troubleshooting.md - Common issues and solutions
- docs/chroot.md - Chapter 7: Entering Chroot (detailed guide)
This is a personal learning project, but feedback and suggestions are welcome! File issues or PRs on GitHub.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
This repository includes the Linux From Scratch book as a Git submodule in docs/lfs-book/ for reference purposes. That submodule is currently pinned to the 12.2 branch while the build recipes follow 13.0-systemd; use the linked online 13.0 book when checking recipe details. The LFS book has its own separate licensing:
- Book text: Creative Commons Attribution-NonCommercial-ShareAlike 2.0
- Code/instructions: MIT License
See docs/lfs-book/appendices/license.xml for the full LFS license details.
- Linux From Scratch 13.0-systemd Book - The source material
- Bazel Documentation - Build system reference
- LFS Mailing Lists - Get help from LFS community
- /r/linuxfromscratch - Reddit community