# 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 harbor-core Service serves plain HTTP internally (TLS is # disabled cluster-wide by design — see claude.md's "everything is # plain HTTP" note). Docker still defaults to attempting HTTPS # against any bare registry hostname regardless of whether the # network path actually involves TLS anywhere — that default isn't # about routing through Contour/Ingress, it's just the client's own # convention. Without this flag, `docker push` hangs doing a TLS # handshake against a server that's only ever spoken HTTP. args: - "--insecure-registry=harbor-core.harbor.svc.cluster.local" env: - name: DOCKER_TLS_CERTDIR value: "" volumeMounts: - name: docker-graph-storage mountPath: /var/lib/docker - name: docker-cli image: docker:27-cli 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