# New resource — didn't exist in the original devops-lib. Homelab's real # pipelines built on static agents (node('slave02')) with Docker already # available; this homelab's Jenkins uses dynamic per-build Kubernetes # agents (agent.enabled in the jenkins chart), which don't have a Docker # daemon by default. This pod template adds one as a sidecar container — # the "docker" container runs privileged dind, "docker-cli" is what the # pipeline actually execs into via container('docker-cli'), talking to # its sibling over localhost since containers in one pod share a network # namespace. apiVersion: v1 kind: Pod spec: containers: - name: docker image: docker:27-dind securityContext: privileged: true # Harbor's ingress serves plain HTTP too (TLS disabled cluster-wide # by design — see claude.md's "everything is plain HTTP" note). # Docker defaults to attempting HTTPS against any bare registry # hostname regardless of network path, so this is needed # regardless of which hostname is used — was previously # harbor-core.harbor.svc.cluster.local (cluster-internal Service # DNS, works from this pod but not from the node's own containerd # when pulling for a real Deployment); switched to the Contour # ingress hostname so push and pull can share one reference. args: - "--insecure-registry=harbor.192.168.1.7.nip.io" env: - name: DOCKER_TLS_CERTDIR value: "" volumeMounts: - name: docker-graph-storage mountPath: /var/lib/docker - name: docker-cli # Custom image (see build-tools.Dockerfile in this same directory) # — bakes in git/yq/bash/python3+pip/venv/curl so nothing needs # apk-installing or curl-downloading on every single build (was # slow and, per its own point, defeats the purpose of a # reproducible pipeline to be quietly downloading a tool binary # fresh on every run). Versioned tag, not :latest — a rebuild of # the tools image doesn't roll out until this pin is bumped too. image: harbor.192.168.1.7.nip.io/homelab/build-tools:1 command: ["cat"] tty: true env: - name: DOCKER_HOST value: tcp://localhost:2375 # docker-config below mounts the Harbor push-auth secret # read-only at /root/.docker (needed so `docker push` finds # config.json without an explicit `docker login` step) — but # modern `docker build` defaults to BuildKit/buildx, which wants # to create its own state dir at /root/.docker/buildx and fails # with "read-only file system" since the whole mount is # read-only. Forcing the classic builder avoids needing to write # there at all. - name: DOCKER_BUILDKIT value: "0" # For syncArgoApp.groovy — read directly from the ESO-managed # Secret, not a Jenkins-native credential (nothing in this # pipeline uses Jenkins' own credential store; staying consistent # rather than mixing the two approaches). - name: ARGOCD_TOKEN valueFrom: secretKeyRef: name: argocd-jenkins-ci-token key: token # Without this, every build using this pod template hard-fails # to even start until the token secret exists — including the # very first demo-go-app run, before the manual # `argocd account generate-token` bootstrap step has happened. optional: true volumeMounts: - name: docker-config mountPath: /root/.docker readOnly: true volumes: - name: docker-graph-storage emptyDir: {} - name: docker-config secret: secretName: harbor-robot-dockerconfig # kubernetes.io/dockerconfigjson secrets store their data under # the fixed key `.dockerconfigjson` (mandated, since that's what # kubelet reads for imagePullSecrets) — without this remap, the # mounted file at /root/.docker is literally named # `.dockerconfigjson`, not `config.json`, which is the only # filename the docker CLI itself ever reads for stored # credentials. Docker found nothing there and silently pushed # unauthenticated, which Harbor correctly rejected as # unauthorized — confirmed the robot account/credentials # themselves were fine the whole time by requesting a push token # directly from Harbor's token endpoint with the same username/ # password and getting one back with push access granted. items: - key: .dockerconfigjson path: config.json