Fold build-tools into this repo

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N
This commit is contained in:
Mukul Sharma
2026-09-13 01:12:24 +05:30
co-authored by Claude Opus 5
parent b38af45c31
commit 2f80cfe288
3 changed files with 148 additions and 4 deletions
+106 -4
View File
@@ -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
+37
View File
@@ -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
+5
View File
@@ -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