/lfs-docker

Build LFS 11.1-systemd using Docker (x86_64 and aarch64 supported)

Primary LanguageDockerfile

lfs-docker

A Dockerfile to build Linux From Scratch 11.1-systemd, supporting x86_64 and aarch64/arm64.

Usage (for x86_64)

First, install Docker on your system. A Docker version >= 18.09 is required since some BuildKit features are used in this Dockerfile.

Now change the working directory to this repo, and then simply run

DOCKER_BUILDKIT=1 docker build -o . .

If the build succeeds, an ISO image lfs-x86_64.iso will be produced in the current directory

On a laptop with a 4-core (8-thread) low-power Intel CPU and 16 GB of memory, the entire build took roughly 2 hours and used about 15 GB of disk space.

Other architectures

By default, the Dockerfile is set to build for x86_64 (on an x86_64 host). To build for ARM 64-bit CPUs, change the following variables at the top of the Dockerfile:

ARG LFS_ARCH=aarch64
ARG DOCKER_ARCH=arm64v8
ARG BUSYBOX_ARCH=armv8l

The build process will change accordingly.

Moreover, if the current host architecture is not arm64, one needs to configure Docker and the kernel to support emulation of arm64 executables (with QEMU):

# Ubuntu example
sudo apt-get install qemu binfmt-support qemu-user-static
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

Then run the same build command:

DOCKER_BUILDKIT=1 docker build -o . .

Note that this is not cross-compilation (thus not quite the same as CLFS), since we are emulating an arm64 host and then building an arm64 system in it. This will have some performance issues due to the emulation.

For the ARM version, I used this ARM adaption of LFS for reference.

Booting the ISO image in QEMU

x86_64 BIOS:

qemu-system-x86_64 -m 1024M -cdrom lfs-x86_64.iso

x86_64 UEFI:

qemu-system-x86_64 -m 1024M -bios /usr/share/ovmf/OVMF.fd -cdrom lfs-x86_64.iso

ARM 64 UEFI:

cp /usr/share/qemu-efi-aarch64/QEMU_EFI.fd flash0.img
truncate -s 64M flash0.img

qemu-system-aarch64 \
    -M virt -cpu cortex-a57 -m 1024M -nographic \
    -pflash flash0.img \
    -cdrom lfs-aarch64.iso

NOTE: To boot on other ARM devices with different mediums, probably a lot more drivers need to be added to the kernel.

Running LFS 11.1-systemd on QEMU aarch64:

How does the Dockerfile work

This Dockerfile is self-contained and does not use any files from the context.

One crucial feature used in this Dockerfile is the multi-stage build, which essentially allows one to sequentially specify multiple images in a Dockerfile, with each image potentially referencing files from previous images.

Using multi-stage build, our Dockerfile is split into the following stages, each corresponding to a different part of the LFS manual:

# Stage 1: prepare the host
# Sections 2 - 7.3 are done in this stage
FROM alpine AS host
...

# Stage 2: chroot into the toolchain and build more packages
# Sections 7.4 - 7.14 are done in this stage
FROM scratch AS toolchain
COPY --from=host ${LFS} /
...

# Stage 3: build the packages in the final system
# Sections 8 - 10 are done in this stage
FROM toolchain AS system
...

# Stage 4: produce a bootable ISO image
FROM alpine:3.16 AS iso-builder
...

Notably, the stage 2 delegates the job of chroot (used in LFS manual section 7.4) to Docker, so we do not need any --privileged flag to perform this action.

More details on making a bootable ISO image (x86)

For booting the ISO image, I decided to use GRUB 2 with the goal to support booting in both legacy BIOS and UEFI modes.

The final file structure of the ISO image looks like this:

boot/
  - grub/
      - i386-pc/
          - eltorito.img # for BIOS booting
      - x86_64-efi/
          - efi.img      # for UEFI booting
      - grub.cfg         # GRUB configuration
  - vmlinuz              # Linux kernel
  - initramfs.cpio.gz    # initramfs
system.squashfs          # The actual LFS system packaged using squashfs

The boot process of the ISO image works in the following way.

When booting in BIOS mode, eltorito.img will be used; when booting in UEFI mode, efi.img will be used (as the EFI System Partition or ESP). Both boot images are produced using grub-mkimage and a few different commands (see Dockerfile for details), and they both contain a copy of the GRUB bootloader and will try to load the GRUB configuration boot/grub/grub.cfg.

The GRUB configuration boot/grub/grub.cfg specifies how the kernel should be booted with an initial, mini root file system initramfs.cpio.gz. The /init script in this mini file system will then try to find the actual root image system.squashfs and switch the root to it (using switch_root).

Finally, after switch_root, the actual systemd init script will be executed and the LFS system will start loading.

Some useful resources:

Related work