diff options
Diffstat (limited to 'docs/handbook/images4docker/architecture.md')
| -rw-r--r-- | docs/handbook/images4docker/architecture.md | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/docs/handbook/images4docker/architecture.md b/docs/handbook/images4docker/architecture.md new file mode 100644 index 0000000000..8a1501f6fc --- /dev/null +++ b/docs/handbook/images4docker/architecture.md @@ -0,0 +1,504 @@ +# images4docker — Architecture + +## Repository Structure + +images4docker follows a deliberately flat, single-purpose architecture. There +are no build scripts, no Makefiles, no helper utilities. Every file in the +repository serves exactly one role. + +``` +images4docker/ +├── .gitattributes # Line-ending enforcement (LF everywhere) +├── .gitignore # Ignore *.log, *.tmp, .env +├── LICENSE # GPL-3.0-or-later (full text) +├── LICENSES/ +│ └── GPL-3.0-or-later.txt # REUSE-compliant license copy +├── README.md # Project overview and notes +└── dockerfiles/ + ├── alma-9.Dockerfile + ├── alma-10.Dockerfile + ├── alpine-319.Dockerfile + ├── alpine-320.Dockerfile + ├── alpine-321.Dockerfile + ├── alpine-322.Dockerfile + ├── alpine-latest.Dockerfile + ├── amazonlinux-2.Dockerfile + ├── amazonlinux-2023.Dockerfile + ├── arch-latest.Dockerfile + ├── centos-stream9.Dockerfile + ├── centos-stream10.Dockerfile + ├── debian-bookworm.Dockerfile + ├── debian-bookworm-slim.Dockerfile + ├── debian-bullseye.Dockerfile + ├── debian-bullseye-slim.Dockerfile + ├── debian-stable-slim.Dockerfile + ├── debian-trixie-slim.Dockerfile + ├── devuan-chimaera.Dockerfile + ├── devuan-daedalus.Dockerfile + ├── fedora-40.Dockerfile + ├── fedora-41.Dockerfile + ├── fedora-42.Dockerfile + ├── fedora-latest.Dockerfile + ├── gentoo-stage3.Dockerfile + ├── kali-rolling.Dockerfile + ├── nix-latest.Dockerfile + ├── opensuse-leap-155.Dockerfile + ├── opensuse-leap-156.Dockerfile + ├── opensuse-tumbleweed.Dockerfile + ├── oraclelinux-8.Dockerfile + ├── oraclelinux-9.Dockerfile + ├── oraclelinux-10.Dockerfile + ├── rocky-9.Dockerfile + ├── rocky-10.Dockerfile + ├── ubuntu-2004.Dockerfile + ├── ubuntu-2204.Dockerfile + ├── ubuntu-2404.Dockerfile + ├── ubuntu-latest.Dockerfile + └── void-latest.Dockerfile +``` + +Total: **40 Dockerfiles**, **1 README**, **1 LICENSE pair**, **2 git config files**. + +--- + +## Dockerfile Naming Convention + +Every Dockerfile follows a strict naming pattern: + +``` +<distro>-<version_or_tag>.Dockerfile +``` + +### Rules + +1. **Distro name** is the short, lower-case distribution identifier: + `alma`, `alpine`, `amazonlinux`, `arch`, `centos-stream`, `debian`, + `devuan`, `fedora`, `gentoo`, `kali`, `nix`, `opensuse-leap`, + `opensuse-tumbleweed`, `oraclelinux`, `rocky`, `ubuntu`, `void`. + +2. **Version** is the numeric version with dots stripped, or a keyword: + - Numeric: `9`, `10`, `319` (for 3.19), `2004` (for 20.04), `155` (for 15.5) + - Keywords: `latest`, `rolling`, `stage3`, `stream9`, `stream10` + - Variants: `bookworm-slim`, `bullseye-slim`, `stable-slim`, `trixie-slim` + +3. **Extension** is always `.Dockerfile` (capital D), not `.dockerfile`. + +### Examples + +| File name | Distribution | Version / Tag | +|----------------------------------|-----------------|----------------------| +| `alma-9.Dockerfile` | AlmaLinux | 9 | +| `alpine-322.Dockerfile` | Alpine Linux | 3.22 | +| `debian-bookworm-slim.Dockerfile` | Debian | Bookworm (12), slim | +| `centos-stream10.Dockerfile` | CentOS Stream | 10 | +| `opensuse-leap-156.Dockerfile` | openSUSE Leap | 15.6 | +| `ubuntu-2404.Dockerfile` | Ubuntu | 24.04 | +| `void-latest.Dockerfile` | Void Linux | latest | + +--- + +## The Universal Dockerfile Template + +Every single Dockerfile in the repository shares the same structural template. +The only differences between files are: + +1. The `FROM` base image reference. +2. The default package-manager command used when `CUSTOM_INSTALL` is not set. +3. The default cleanup command. +4. Minor variations in the Qt6 binary search path. + +### Template Anatomy + +```dockerfile +# syntax=docker/dockerfile:1.7 +FROM <base_image>:<tag> + +ARG PACKAGES= +ARG CUSTOM_INSTALL= +ARG UPDATE_CMD= +ARG CLEAN_CMD= + +SHELL ["/bin/sh", "-lc"] + +RUN set -eux; \ + if [ -n "${UPDATE_CMD}" ]; then \ + sh -lc "${UPDATE_CMD}"; \ + fi; \ + if [ -n "${CUSTOM_INSTALL}" ]; then \ + sh -lc "${CUSTOM_INSTALL}"; \ + elif [ -n "${PACKAGES}" ]; then \ + <package_manager> install <flags> ${PACKAGES}; \ + fi; \ + if [ -n "${CLEAN_CMD}" ]; then \ + sh -lc "${CLEAN_CMD}"; \ + else \ + <default_cleanup>; \ + fi; \ + export PATH="$PATH:/usr/lib/qt6/bin:/usr/lib64/qt6/bin:/opt/qt6/bin:/root/.nix-profile/bin"; \ + <qt6_verification_gate> + +CMD ["/bin/sh"] +``` + +### Template Sections Explained + +#### 1. Syntax Directive + +```dockerfile +# syntax=docker/dockerfile:1.7 +``` + +Every file begins with this BuildKit syntax directive. This enables: +- Heredoc support (`<<EOF`) +- Improved caching behaviour +- `RUN --mount` options (though not currently used) +- Better error messages during builds + +#### 2. FROM Statement + +```dockerfile +FROM <base_image>:<tag> +``` + +Each Dockerfile has a single, non-parameterised `FROM`. The image reference is +hardcoded — there is no `ARG`-based base image selection. This is intentional: +every Dockerfile builds exactly one image from exactly one base. + +The `FROM` references use public registries: + +| Registry | Used by | +|-------------------------|--------------------------------------------| +| Docker Hub (implicit) | AlmaLinux, Alpine, Amazon Linux, Arch, | +| | Debian, Fedora, Gentoo, Kali, NixOS, | +| | openSUSE, Oracle Linux, Ubuntu, Void | +| `quay.io` | CentOS Stream (`quay.io/centos/centos`) | +| Docker Hub (explicit) | Devuan (`devuan/devuan`), Rocky | +| | (`rockylinux/rockylinux`), | +| | Void (`voidlinux/voidlinux`) | + +#### 3. Build Arguments + +```dockerfile +ARG PACKAGES= +ARG CUSTOM_INSTALL= +ARG UPDATE_CMD= +ARG CLEAN_CMD= +``` + +All four arguments default to empty strings. They are the injection points +through which the CI workflow customises each build: + +| Argument | Purpose | Example value | +|-------------------|-------------------------------------------------------------|-----------------------------------------| +| `PACKAGES` | Space-separated list of packages to install | `qt6-base-dev cmake gcc g++` | +| `CUSTOM_INSTALL` | Arbitrary shell command that replaces the default install | `dnf config-manager --enable crb && dnf install -y qt6-qtbase-devel` | +| `UPDATE_CMD` | Shell command run before package installation | `apt-get update` | +| `CLEAN_CMD` | Shell command run after installation to reduce image size | `rm -rf /var/lib/apt/lists/*` | + +Priority logic: +1. If `CUSTOM_INSTALL` is non-empty, it is executed instead of the package manager. +2. Otherwise, if `PACKAGES` is non-empty, the native package manager installs them. +3. If neither is set, nothing is installed (but the Qt6 check still runs and will fail). + +#### 4. Shell Override + +```dockerfile +SHELL ["/bin/sh", "-lc"] +``` + +The default Docker shell is `["/bin/sh", "-c"]`. The `-l` flag forces a login +shell, which ensures: +- `/etc/profile` and `/etc/profile.d/*.sh` are sourced. +- `PATH` extensions from the distribution's login scripts are available. +- NixOS profile paths (`/root/.nix-profile/bin`) are activated. + +#### 5. The RUN Block + +The entire build logic is a single `RUN` instruction. This is deliberate — it +creates a single Docker layer, minimising image size and avoiding intermediate +layers that would persist deleted files. + +The `RUN` block executes in this order: + +``` +┌──────────────────────┐ +│ set -eux │ Fail on errors, undefined vars, print commands +├──────────────────────┤ +│ UPDATE_CMD? │ Optional: pre-install update (apt-get update, etc.) +├──────────────────────┤ +│ CUSTOM_INSTALL? │ If set: run arbitrary install command +│ or PACKAGES? │ Else if set: run native pkg manager with PACKAGES +├──────────────────────┤ +│ CLEAN_CMD? │ If set: run custom cleanup +│ or default clean │ Else: run distro-appropriate cleanup +├──────────────────────┤ +│ export PATH=... │ Extend PATH with Qt6 binary locations +├──────────────────────┤ +│ Qt6 verification │ Check for qmake6/qmake-qt6/qtpaths6 binaries +│ gate │ FAILS BUILD if not found +└──────────────────────┘ +``` + +#### 6. CMD + +```dockerfile +CMD ["/bin/sh"] +``` + +Every image defaults to a shell. CI jobs override this with their own +`entrypoint` or `command` specifications, so the `CMD` is effectively a +debug/interactive fallback. + +--- + +## Package Manager Dispatch + +The package-manager command in the `RUN` block varies per distribution family. +Here is the exact command used by each group: + +### apt-based (Debian, Ubuntu, Devuan, Kali) + +```sh +apt-get update; apt-get install -y --no-install-recommends ${PACKAGES} +``` + +- `--no-install-recommends` keeps images lean by skipping suggested packages. +- Default cleanup: `rm -rf /var/lib/apt/lists/*` + +### dnf-based (Fedora, AlmaLinux, CentOS Stream, Rocky, Oracle Linux, Amazon Linux 2023) + +```sh +dnf install -y ${PACKAGES} +``` + +- Default cleanup: `dnf clean all || true` + +### yum-based (Amazon Linux 2) + +```sh +yum install -y ${PACKAGES} +``` + +- Default cleanup: `yum clean all || true` + +### apk-based (Alpine Linux) + +```sh +apk add --no-cache ${PACKAGES} +``` + +- `--no-cache` means no index files are persisted. +- Default cleanup: `true` (no-op, since apk --no-cache handles it). + +### zypper-based (openSUSE Leap, Tumbleweed) + +```sh +zypper --non-interactive refresh; zypper --non-interactive install --no-recommends ${PACKAGES} +``` + +- `--non-interactive` prevents prompts. +- `--no-recommends` skips recommended (but not required) packages. +- Default cleanup: `zypper clean --all || true` + +### pacman-based (Arch Linux) + +```sh +pacman -Syu --noconfirm --needed ${PACKAGES} +``` + +- `-Syu` does a full system upgrade before installing. +- `--needed` skips already-installed packages. +- Default cleanup: `pacman -Scc --noconfirm || true` + +### emerge-based (Gentoo) + +```sh +emerge --sync; emerge ${PACKAGES} +``` + +- `--sync` refreshes the Portage tree before installing. +- Default cleanup: `true` (no-op). + +### nix-env-based (NixOS/Nix) + +```sh +nix-env -iA ${PACKAGES} +``` + +- `-iA` installs by attribute path from nixpkgs. +- Default cleanup: `nix-collect-garbage -d || true` + +### xbps-based (Void Linux) + +```sh +xbps-install -Sy ${PACKAGES} +``` + +- `-S` syncs the repository index. +- `-y` assumes yes to prompts. +- Default cleanup: `xbps-remove -O || true` + +--- + +## Qt6 Binary Search Paths + +After package installation, every Dockerfile extends `PATH` to include +distribution-specific Qt6 binary directories. There are two variants: + +### Standard Path Extension (most distros) + +```sh +export PATH="$PATH:/usr/lib/qt6/bin:/usr/lib64/qt6/bin:/opt/qt6/bin:/root/.nix-profile/bin" +``` + +Used by: AlmaLinux, Alpine, Arch, CentOS Stream, Debian, Devuan, Fedora, +Kali, openSUSE, Rocky, Ubuntu. + +### Extended Path (distros with /usr/libexec/qt6) + +```sh +export PATH="$PATH:/usr/lib/qt6/bin:/usr/lib64/qt6/bin:/usr/libexec/qt6:/opt/qt6/bin:/root/.nix-profile/bin" +``` + +Used by: Amazon Linux 2023, Gentoo, NixOS, Oracle Linux, Void Linux. + +The `/usr/libexec/qt6` path is added for distributions where Qt6 installs its +binaries under `libexec` rather than `lib/qt6/bin`. + +--- + +## Base Image Selection Strategy + +The choice of base images follows these principles: + +### Version Pinning + +- **LTS releases** are pinned to specific versions: `ubuntu:20.04`, `ubuntu:22.04`, + `ubuntu:24.04`, `debian:bookworm`, `alpine:3.19`, etc. +- **Rolling releases** use `latest` tags: `archlinux:latest`, `fedora:latest`, + `alpine:latest`, `opensuse/tumbleweed:latest`. +- **Dual coverage**: where possible, both a pinned version and a `latest` tag + are maintained (e.g., `alpine-321.Dockerfile` + `alpine-latest.Dockerfile`). + +### Registry Selection + +- **Docker Hub** is the primary registry for most images. +- **Quay.io** is used for CentOS Stream because the official CentOS images + are hosted there: `quay.io/centos/centos:stream9`. +- **Namespaced images** are used where distributions publish under their own + Docker Hub organisation: `devuan/devuan`, `rockylinux/rockylinux`, + `voidlinux/voidlinux`, `kalilinux/kali-rolling`, `gentoo/stage3`. + +### Slim vs Full Variants + +For Debian, both full and slim variants are maintained: +- `debian:bookworm` — full image with documentation, man pages, extra utilities. +- `debian:bookworm-slim` — minimal image, roughly half the size. + +The slim variants are preferred for CI because they download faster, but full +variants are kept for cases where build scripts expect standard utilities. + +--- + +## Configuration Files + +### .gitattributes + +``` +# images4docker +* text=auto eol=lf +*.Dockerfile text +``` + +- Forces LF line endings on all text files. +- Explicitly marks `*.Dockerfile` as text to ensure proper diff handling. +- Prevents CRLF corruption when contributors use Windows. + +### .gitignore + +``` +# images4docker +*.log +*.tmp +.env +``` + +- Ignores build logs and temporary files. +- Ignores `.env` files that might contain registry credentials. + +--- + +## Design Principles + +### Single-layer images + +Each Dockerfile has exactly one `RUN` instruction. This means: +- The final image has the base image's layers plus exactly one additional layer. +- No intermediate layers persist files that are later deleted (which would bloat + the image even though the files are not visible). + +### No COPY or ADD + +None of the Dockerfiles copy any files from the build context. All configuration +is done via `ARG` values injected at build time. This means: +- The Docker build context is effectively empty. +- Builds are fast because no files need to be sent to the Docker daemon. +- The Dockerfiles are entirely self-contained. + +### No ENTRYPOINT + +Images use `CMD ["/bin/sh"]` without an `ENTRYPOINT`. This allows CI jobs to +override the command freely without needing `--entrypoint`. + +### No EXPOSE or VOLUME + +These images are build environments, not services. There are no network ports +to expose and no data volumes to mount. + +### No USER directive + +All images run as `root`. CI builds typically need root to install packages +and access system directories. Security isolation is handled at the container +runtime level (Docker, Podman, etc.), not inside the image. + +### No HEALTHCHECK + +These are ephemeral CI images, not long-running services. Health checks would +add unnecessary complexity. + +--- + +## Image Lifecycle + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐ +│ Upstream base │────▶│ Dockerfile │────▶│ Built image │ +│ (Docker Hub / │ │ (in this repo) │ │ (GHCR) │ +│ Quay.io) │ │ │ │ │ +└─────────────────┘ └──────────────────┘ └─────────────────────┘ + │ │ │ + │ Daily pull │ Push to main / │ Used by CI + │ (cron 03:17 UTC) │ daily cron │ jobs in other + │ │ │ repositories + ▼ ▼ ▼ + New upstream ──▶ Rebuild triggered ──▶ New image pushed + tag available by workflow to ghcr.io +``` + +1. Upstream distributions publish new base images. +2. The daily cron or a push to `main` triggers the GitHub Actions workflow. +3. The workflow builds each Dockerfile with the appropriate `--build-arg` values. +4. The Qt6 verification gate passes or fails the build. +5. Successful images are pushed to `ghcr.io/project-tick-infra/images/`. +6. Other Project Tick CI jobs pull these images as their build containers. + +--- + +## Related Documentation + +- [Overview](overview.md) — project summary +- [Base Images](base-images.md) — per-image deep dive +- [Qt6 Verification](qt6-verification.md) — the verification gate +- [CI/CD Integration](ci-cd-integration.md) — workflow details +- [Creating New Images](creating-new-images.md) — adding distributions +- [Troubleshooting](troubleshooting.md) — debugging builds |
