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) # devops-base-images (GKE)
Mirrors minimized base images into Harbor's `base-images` project, so Owns every image the build pipeline pulls, in Harbor's `base-images`
language builds — `devops-lib`'s `buildDocker.groovy` fallback templates — project:
never depend on Docker Hub at build time, and the images that ship are the
leanest official variant for each language. - **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 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 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 maven:3-eclipse-temurin-21-alpine maven:3-eclipse-temurin-21-alpine
eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine
php:8.3-cli-alpine php:8.3-cli-alpine php:8.3-cli-alpine php:8.3-cli-alpine
docker:27-cli docker:27-cli
mirror.sh: | mirror.sh: |
#!/bin/sh #!/bin/sh
set -eu 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` cause: it must be `/etc/docker/certs.d/harbor.35.238.248.203.nip.io/ca.crt`
exactly, hostname included, in the dind container. 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 ## Adding a new image or updating a version
Add a line to `images.txt`, commit, push, then re-run step 3 — the 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 maven:3-eclipse-temurin-21-alpine maven:3-eclipse-temurin-21-alpine
eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine
php:8.3-cli-alpine php:8.3-cli-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