Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 109 additions & 101 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,186 +2,204 @@
# =============================================================================
# Multi-stage Dockerfile for Building Custom Linux Distribution
# =============================================================================
# This Dockerfile builds a minimal Linux distribution from scratch using:
# - Linux Kernel (from torvalds/linux)
# - BusyBox (minimal userspace utilities)
# - Syslinux (bootloader)
# =============================================================================

# -----------------------------------------------------------------------------
# Stage 1: Base builder with build dependencies
# -----------------------------------------------------------------------------
FROM debian:sid-slim AS builder-base

# Set build arguments for versioning and configuration
ARG LINUX_VERSION=master
ARG BUSYBOX_VERSION=master
ARG SYSLINUX_VERSION=6.04-pre1
ARG SYSLINUX_VERSION=6.03
ARG DEBIAN_FRONTEND=noninteractive
ARG BUILD_JOBS=auto
ARG BUILD_JOBS=

# Add metadata labels
LABEL maintainer="handbuilt-linux-project"
LABEL description="Custom Linux distribution builder"
LABEL version="1.0.0"

# Install build dependencies in a single layer with cleanup
# hadolint ignore=DL3008
# Install build dependencies
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -yq --no-install-recommends \
build-essential \
bzip2 \
git \
make \
gcc \
libncurses-dev \
flex \
bison \
bc \
cpio \
libelf-dev \
libssl-dev \
syslinux-common \
dosfstools \
genisoimage \
wget \
curl \
ca-certificates \
xz-utils && \
build-essential \
bzip2 \
git \
make \
gcc \
libncurses-dev \
flex \
bison \
bc \
cpio \
libelf-dev \
libssl-dev \
syslinux-common \
dosfstools \
genisoimage \
wget \
curl \
ca-certificates \
xz-utils && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /build

# -----------------------------------------------------------------------------
# Stage 2: Download and prepare sources
# Stage: source-downloader
# -----------------------------------------------------------------------------
FROM builder-base AS source-downloader

ARG LINUX_VERSION=master
ARG BUSYBOX_VERSION=master
ARG SYSLINUX_VERSION=6.03

# Create directory structure
RUN mkdir -p /build/initramfs && \
mkdir -p /build/myiso/isolinux && \
mkdir -p /build/sources
mkdir -p /build/sources && \
mkdir -p /build/cache

# Download Linux kernel source
WORKDIR /build/sources

# Clone Linux (shallow, branch/tag from ARG)
RUN --mount=type=cache,target=/build/cache \
if [ ! -f /build/cache/linux/.git/config ]; then \
git clone --depth 1 \
https://github.com/torvalds/linux.git linux && \
cp -r linux /build/cache/linux; \
if [ ! -d /build/cache/linux ]; then \
git clone --depth 1 --branch "${LINUX_VERSION}" https://github.com/torvalds/linux.git linux || \
git clone --depth 1 https://github.com/torvalds/linux.git linux; \
cp -r linux /build/cache/linux; \
else \
cp -r /build/cache/linux linux; \
cp -r /build/cache/linux linux; \
fi

# Download BusyBox source
# Clone BusyBox (shallow, branch/tag from ARG)
WORKDIR /build/sources
RUN --mount=type=cache,target=/build/cache \
if [ ! -f /build/cache/busybox/.git/config ]; then \
git clone --depth 1 \
https://git.busybox.net/busybox busybox && \
cp -r busybox /build/cache/busybox; \
if [ ! -d /build/cache/busybox ]; then \
git clone --depth 1 --branch "${BUSYBOX_VERSION}" https://git.busybox.net/busybox busybox || \
git clone --depth 1 https://git.busybox.net/busybox busybox; \
cp -r busybox /build/cache/busybox; \
else \
cp -r /build/cache/busybox busybox; \
cp -r /build/cache/busybox busybox; \
fi

# Download and extract Syslinux
# Note: Using alternative download locations due to mirror availability
# Download and extract Syslinux (tries multiple URLs)
WORKDIR /build/sources
RUN curl -fsSL -o syslinux.tar.gz \
"https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.gz" || \
curl -fsSL -o syslinux.tar.gz \
"https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.gz" && \
tar xzf syslinux.tar.gz && \
rm syslinux.tar.gz && \
mv syslinux-* syslinux
RUN set -eux; \
tried=0; \
for url in \
"https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/syslinux-${SYSLINUX_VERSION}.tar.gz" \
"https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-${SYSLINUX_VERSION}.tar.gz" \
"https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/Testing/${SYSLINUX_VERSION}/syslinux-${SYSLINUX_VERSION}.tar.gz"; do \
echo "Trying $url"; \
if curl -fsSL -o syslinux.tar.gz "$url"; then \
tried=1; \
break; \
else \
echo "Failed to download from $url"; \
fi; \
done; \
if [ "$tried" -ne 1 ]; then \
echo "ERROR: Unable to download syslinux ${SYSLINUX_VERSION}" >&2; \
exit 22; \
fi; \
tar xzf syslinux.tar.gz && rm syslinux.tar.gz && mv "syslinux-${SYSLINUX_VERSION}" syslinux

# -----------------------------------------------------------------------------
# Stage 3: Build Linux kernel
# Stage: kernel-builder
# -----------------------------------------------------------------------------
FROM builder-base AS kernel-builder

ARG BUILD_JOBS
ARG LINUX_VERSION=master

# Copy kernel source from downloader stage
COPY --from=source-downloader /build/sources/linux /build/linux

# Copy kernel configuration
# If you have a linux.config in repo, it will be copied by the build context
COPY linux.config /build/linux/.config

# Build kernel
WORKDIR /build/linux
RUN make olddefconfig && \
make -j"${BUILD_JOBS:-$(nproc)}" && \
(strip --strip-debug arch/x86/boot/bzImage 2>/dev/null || true)

# -----------------------------------------------------------------------------
# Stage 4: Build BusyBox
# Stage: busybox-builder
# -----------------------------------------------------------------------------
FROM builder-base AS busybox-builder

ARG BUILD_JOBS
ARG BUSYBOX_VERSION=master

# Copy BusyBox source from downloader stage
COPY --from=source-downloader /build/sources/busybox /build/busybox

# Copy BusyBox configuration
COPY busybox.config /build/busybox/.config

# Build and install BusyBox
WORKDIR /build/busybox
RUN make oldconfig && \
make -j"${BUILD_JOBS:-$(nproc)}" && \
make CONFIG_PREFIX=/build/initramfs install && \
strip /build/initramfs/bin/busybox

# Use a robust config, build, install; create an explicit tarball artifact and verify it
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -eux; \
mkdir -p /build/initramfs; \
# configure (prefer existing .config, accept defaults non-interactively)
if [ -f .config ]; then \
if ! yes "" | make oldconfig; then \
make defconfig; \
fi; \
else \
make defconfig; \
fi; \
# build
make -j"${BUILD_JOBS:-$(nproc)}"; \
# install into explicit path
make CONFIG_PREFIX=/build/initramfs install; \
# sanity checks — fail early with diagnostics if artifacts missing
if [ ! -x /build/initramfs/bin/busybox ]; then \
echo "ERROR: busybox install did not produce /build/initramfs/bin/busybox"; \
echo "workspace /build contents:"; ls -la /build || true; \
echo "busybox tree:"; ls -la /build/initramfs || true; \
exit 1; \
fi; \
# shrink binary if possible (non-fatal)
strip --strip-all /build/initramfs/bin/busybox || true; \
# create a single tarball artifact for reliable COPY by BuildKit
tar -C /build -czf /build/initramfs.tar.gz initramfs; \
ls -la /build/initramfs.tar.gz

# -----------------------------------------------------------------------------
# Stage 5: Create initramfs
# Stage: initramfs-builder
# -----------------------------------------------------------------------------
FROM builder-base AS initramfs-builder

# Copy BusyBox installation
COPY --from=busybox-builder /build/initramfs /build/initramfs
# Copy the tarball artifact and extract it, then add/overwrite init
COPY --from=busybox-builder /build/initramfs.tar.gz /build/initramfs.tar.gz

WORKDIR /build
RUN set -eux; \
mkdir -p /build; \
tar -C /build -xzf /build/initramfs.tar.gz && rm /build/initramfs.tar.gz; \
ls -la /build/initramfs

# Copy init script and make it executable
COPY init.sh /build/initramfs/init
RUN chmod +x /build/initramfs/init

# Create initramfs archive
WORKDIR /build/initramfs
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN find . -print0 | cpio --null --create --format=newc | gzip -9 > /build/initramfs.cpio.gz

# -----------------------------------------------------------------------------
# Stage 6: Create bootable ISO
# Stage: iso-builder
# -----------------------------------------------------------------------------
FROM builder-base AS iso-builder

# Copy syslinux files
COPY --from=source-downloader /build/sources/syslinux /build/syslinux
ARG SYSLINUX_VERSION=6.03

# Copy kernel
COPY --from=source-downloader /build/sources/syslinux /build/syslinux
COPY --from=kernel-builder /build/linux/arch/x86/boot/bzImage /build/myiso/bzImage

# Copy initramfs
COPY --from=initramfs-builder /build/initramfs.cpio.gz /build/myiso/initramfs

# Copy syslinux bootloader files
WORKDIR /build
RUN cp /build/syslinux/bios/core/isolinux.bin /build/myiso/isolinux/ && \
cp /build/syslinux/bios/com32/elflink/ldlinux/ldlinux.c32 /build/myiso/isolinux/

# Copy bootloader configuration
COPY syslinux.cfg /build/myiso/isolinux/isolinux.cfg

# Create bootable ISO
WORKDIR /build
RUN mkisofs \
-J \
Expand All @@ -195,56 +213,46 @@ RUN mkisofs \
myiso

# -----------------------------------------------------------------------------
# Stage 7: Final minimal image with artifacts
# Stage: export-stage
# -----------------------------------------------------------------------------
FROM scratch AS export-stage

# Copy build artifacts
COPY --from=iso-builder /build/output.iso /output.iso
COPY --from=kernel-builder /build/linux/arch/x86/boot/bzImage /bzImage
COPY --from=initramfs-builder /build/initramfs.cpio.gz /initramfs

# -----------------------------------------------------------------------------
# Stage 8: Runtime image (default)
# Stage: runtime
# -----------------------------------------------------------------------------
FROM debian:sid-slim AS runtime

# Install only runtime dependencies
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -yq --no-install-recommends \
qemu-system-x86 \
qemu-utils && \
qemu-system-x86 \
qemu-utils && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Create non-root user for better security
RUN groupadd -r distro && \
useradd -r -g distro -d /distro -s /bin/bash distro && \
mkdir -p /distro && \
chown -R distro:distro /distro

# Copy build artifacts from iso-builder stage
COPY --from=iso-builder /build/output.iso /distro/output.iso
COPY --from=kernel-builder /build/linux/arch/x86/boot/bzImage /distro/bzImage
COPY --from=initramfs-builder /build/initramfs.cpio.gz /distro/initramfs

# Copy scripts
COPY --chown=distro:distro build.sh /distro/build.sh
RUN chmod +x /distro/build.sh

# Set working directory and user
WORKDIR /distro
USER distro

# Set environment variables
ENV DISTRO_HOME=/distro
ENV PATH="${DISTRO_HOME}:${PATH}"

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD [ -f "/distro/output.iso" ] || exit 1

# Default command
CMD ["/bin/bash"]
CMD ["/bin/bash"]
6 changes: 4 additions & 2 deletions scripts/clean.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

set -euo pipefail

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
readonly SCRIPT_DIR
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_DIR
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"

readonly GREEN='\033[0;32m'
readonly BLUE='\033[0;34m'
Expand Down
6 changes: 4 additions & 2 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

set -euo pipefail

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"
readonly SCRIPT_DIR
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_DIR
PROJECT_DIR="$(dirname "${SCRIPT_DIR}")"

readonly GREEN='\033[0;32m'
readonly RED='\033[0;31m'
Expand Down
Loading