From 2f80cfe2881aaa964e1516bd39ee429edea3bdf8 Mon Sep 17 00:00:00 2001 From: Mukul Sharma Date: Sun, 13 Sep 2026 01:12:24 +0530 Subject: [PATCH] Fold build-tools into this repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build-tools is the image the pipeline's build pod runs its shell steps in. It was owned by devops-lib, which is an odd home: it is not library code, it is an artefact built by hand and pushed to Harbor, exactly like the mirrors here. devops-lib only ever referenced the result by tag, and it still will. Three deliberate changes from the homelab's version: - Pushed to base-images/build-tools:1, not homelab/. That project is public, so build pods pull it with no credentials — the same reason the language images live there. - FROM the mirrored docker:27-cli rather than Docker Hub, with that tag added to images.txt. Otherwise building the image that exists to remove a Docker Hub dependency would itself depend on Docker Hub. - yq is pinned instead of "releases/latest". An image that resolves a different yq on every build is not reproducible, and that is the kind of drift that surfaces months later as an unexplained pipeline failure. The README gains the build-and-push procedure: the same pod shape as the mirror, with the registry CA mounted into dind so the push is trusted, and DOCKER_BUILDKIT=0, since BuildKit wants to write state under /root/.docker where the push credentials get mounted read-only. Verified the three places that must agree do: images.txt, the README's inlined ConfigMap copy of it, and the Dockerfile's FROM tag. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N --- README.md | 110 +++++++++++++++++++++++++++++++++++++++-- build-tools.Dockerfile | 37 ++++++++++++++ images.txt | 5 ++ 3 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 build-tools.Dockerfile diff --git a/README.md b/README.md index d606070..b653253 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ # devops-base-images (GKE) -Mirrors minimized base images into Harbor's `base-images` project, so -language builds — `devops-lib`'s `buildDocker.groovy` fallback templates — -never depend on Docker Hub at build time, and the images that ship are the -leanest official variant for each language. +Owns every image the build pipeline pulls, in Harbor's `base-images` +project: + +- **Mirrored language base images** (`images.txt`), so builds — `devops-lib`'s + `buildDocker.groovy` fallback templates — never depend on Docker Hub at + build time, and what ships is the leanest official variant per language. +- **`build-tools`** (`build-tools.Dockerfile`), the image the build pod runs + its own shell steps in. + +Both are built or mirrored by hand from here, and `devops-lib` only ever +references the results by tag. `build-tools` lives here rather than in +devops-lib because it is the same kind of artefact as the mirrors, and +because a Jenkins job cannot build the image its own build pod needs. GKE counterpart of the homelab repo of the same name. The manifest and the script are identical; what differs is the registry host and, more @@ -73,6 +82,7 @@ data: maven:3-eclipse-temurin-21-alpine maven:3-eclipse-temurin-21-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine php:8.3-cli-alpine php:8.3-cli-alpine + docker:27-cli docker:27-cli mirror.sh: | #!/bin/sh set -eu @@ -176,6 +186,98 @@ If the push fails with a certificate error, the mount path is the usual cause: it must be `/etc/docker/certs.d/harbor.35.238.248.203.nip.io/ca.crt` exactly, hostname included, in the dind container. +## Building `build-tools` + +Do this after the mirror, since it builds `FROM` a mirrored image. + +The pod is the same shape as the mirror pod — privileged dind, the CA +mounted so pushes are trusted, a docker-cli sidecar — with the Dockerfile +supplied through a ConfigMap. Create it from the file in this repo so the +two cannot drift: + +``` +kubectl -n jenkins create configmap build-tools-src \ + --from-file=Dockerfile=build-tools.Dockerfile + +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +kind: Pod +metadata: + name: build-tools-build + namespace: jenkins +spec: + restartPolicy: Never + containers: + - name: docker + image: docker:27-dind + securityContext: + privileged: true + env: + - name: DOCKER_TLS_CERTDIR + value: "" + volumeMounts: + - name: docker-graph-storage + mountPath: /var/lib/docker + - name: registry-ca + mountPath: /etc/docker/certs.d/harbor.35.238.248.203.nip.io + readOnly: true + - name: docker-cli + image: docker:27-cli + command: ["cat"] + tty: true + env: + - name: DOCKER_HOST + value: tcp://localhost:2375 + # The classic builder. BuildKit wants to write its own state under + # /root/.docker, which is read-only wherever the push credentials are + # mounted as a file — the homelab hit exactly this. + - name: DOCKER_BUILDKIT + value: "0" + volumeMounts: + - name: src + mountPath: /src + volumes: + - name: docker-graph-storage + emptyDir: {} + - name: src + configMap: + name: build-tools-src + - name: registry-ca + configMap: + name: registry-ca +EOF +``` + +Then log in and build. The tag is deliberate: **bump it rather than +overwriting**, because `devops-lib-gcp`'s `dind-pod.yaml` pins this tag and +an overwritten tag rolls out silently on the next pod start. + +``` +kubectl wait --for=condition=Ready pod/build-tools-build -n jenkins --timeout=180s +kubectl exec -it -n jenkins build-tools-build -c docker-cli -- sh -c ' + for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break; sleep 2; done + docker login harbor.35.238.248.203.nip.io' + +kubectl exec -n jenkins build-tools-build -c docker-cli -- sh -c ' + cd /src && + docker build -t harbor.35.238.248.203.nip.io/base-images/build-tools:1 . && + docker push harbor.35.238.248.203.nip.io/base-images/build-tools:1' + +kubectl delete pod build-tools-build -n jenkins +kubectl delete configmap build-tools-src -n jenkins +``` + +Verify it has what the stages expect: + +``` +kubectl run bt --rm -it --restart=Never \ + --image=harbor.35.238.248.203.nip.io/base-images/build-tools:1 \ + -- sh -c 'git --version && yq --version && python3 -V && bash --version | head -1' +``` + +That pull needs no credentials — `base-images` is public — and the nodes +already trust the CA, which is what makes an ordinary pod able to run it. + ## Adding a new image or updating a version Add a line to `images.txt`, commit, push, then re-run step 3 — the diff --git a/build-tools.Dockerfile b/build-tools.Dockerfile new file mode 100644 index 0000000..1d2889e --- /dev/null +++ b/build-tools.Dockerfile @@ -0,0 +1,37 @@ +# The docker-cli image the pipeline's build pod runs its shell steps in. +# +# Bakes in everything the stages need — git, yq, bash, python3 with pip and +# venv for runHooks' python hooks, curl — so nothing is installed on demand +# on every single build. Installing tools per build was slow and, worse, +# quietly undermined reproducibility: a build's behaviour depended on +# whatever the package mirror served that morning. +# +# Lives here rather than in devops-lib because it is the same kind of thing +# as everything else in this repo: an image built by hand, occasionally, +# and pushed into Harbor for builds to pull. devops-lib only references the +# result by tag. +# +# Built and pushed manually — never by a Jenkins job, which would need this +# image to already exist in order to run. See README.md. +# +# Pushed to base-images, not homelab: that project is public, so build pods +# pull this with no credentials at all, which is the same reason the +# mirrored language images live there. +# +# The tag is pinned explicitly by devops-lib-gcp's dind-pod.yaml, so +# rebuilding this does not roll anything out until that pin is bumped too. +# Bump the tag when this file changes; do not overwrite an existing tag. +FROM harbor.35.238.248.203.nip.io/base-images/docker:27-cli + +# Pinned rather than the homelab's "releases/latest": an image that resolves +# a different yq every time it is built is not reproducible, and this is +# exactly the sort of thing that changes under you months later. Bump it +# deliberately. +ARG YQ_VERSION=v4.44.3 + +RUN apk add --no-cache git bash python3 py3-pip py3-virtualenv curl \ + && curl -sL -o /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ + && chmod +x /usr/local/bin/yq \ + && yq --version \ + && git --version diff --git a/images.txt b/images.txt index 974bc02..d7459ac 100644 --- a/images.txt +++ b/images.txt @@ -25,3 +25,8 @@ python:3.12-alpine python:3.12-alpine maven:3-eclipse-temurin-21-alpine maven:3-eclipse-temurin-21-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine php:8.3-cli-alpine php:8.3-cli-alpine +# Not a language runtime: this is what build-tools.Dockerfile builds FROM. +# Mirrored for the same reason as everything else here — so building the +# build image does not depend on Docker Hub either. Keep the tag in step +# with the FROM line in build-tools.Dockerfile. +docker:27-cli docker:27-cli