# devops-base-images One-time-setup repo for mirroring minimized base images into Harbor's `base-images` project, so language builds (via `devops-lib`'s `buildDocker.groovy` fallback templates) never depend on Docker Hub at build time, and the images that actually ship are the leanest official variant available for each language. Not wired into any Jenkins pipeline — this is deliberately a manual, occasional operation (re-run when `images.txt` changes: a new language, a version bump, or picking up an upstream base-image update), using the same one-off `kubectl`-based DinD pod pattern `devops-lib`'s `build-tools.Dockerfile` bootstrap already uses, since the VM itself may not have Docker installed directly. ## One-time setup **1. Create the Harbor project** (public — these are just re-hosted public images, no confidentiality concern, and public avoids needing any pull credentials wired into every build): Harbor UI → New Project → name `base-images` → check **Public** → Create. Or via API: ``` curl -X POST -u admin:'' -H "Content-Type: application/json" \ -d '{"project_name":"base-images","metadata":{"public":"true"}}' \ "http://harbor.192.168.1.7.nip.io/api/v2.0/projects" ``` **2. Run the mirror**, via a throwaway DinD pod (same shape as the `build-tools` image bootstrap): ``` cat <<'EOF' | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: base-images-mirror namespace: jenkins data: images.txt: | golang:1.22-alpine golang:1.22-alpine alpine:3.20 alpine:3.20 node:20-alpine node:20-alpine 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 mirror.sh: | #!/bin/sh set -eu REGISTRY="harbor.192.168.1.7.nip.io" PROJECT="base-images" while read -r src target; do [ -z "$src" ] && continue case "$src" in \#*) continue ;; esac dest="${REGISTRY}/${PROJECT}/${target}" echo "=== ${src} -> ${dest} ===" docker pull "$src" docker tag "$src" "$dest" docker push "$dest" done < images.txt echo "=== done ===" --- apiVersion: v1 kind: Pod metadata: name: base-images-mirror namespace: jenkins spec: restartPolicy: Never containers: - name: docker image: docker:27-dind securityContext: privileged: true 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 image: docker:27-cli command: ["cat"] tty: true env: - name: DOCKER_HOST value: tcp://localhost:2375 volumeMounts: - name: mirror mountPath: /mirror volumes: - name: docker-graph-storage emptyDir: {} - name: mirror configMap: name: base-images-mirror defaultMode: 0755 EOF ``` **No `harbor-robot-dockerconfig` mount here on purpose** — that robot account is scoped only to the `homelab` project (push+pull there specifically); it has no grant on `base-images` at all, and making `base-images` public only grants anonymous *pull*, never push. Push always needs real credentials scoped to that project regardless. For a one-off manual task like this, simplest is just logging in with your Harbor admin account directly inside the pod, rather than provisioning a whole new robot account for something run this rarely: ``` kubectl wait --for=condition=Ready pod/base-images-mirror -n jenkins --timeout=120s kubectl exec -it -n jenkins base-images-mirror -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.192.168.1.7.nip.io ' ``` (enter your Harbor admin username/password when prompted) ``` kubectl exec -n jenkins base-images-mirror -c docker-cli -- sh -c ' cd /mirror && sh mirror.sh ' kubectl delete pod base-images-mirror -n jenkins kubectl delete configmap base-images-mirror -n jenkins ``` **3. Verify:** ``` curl -s http://harbor.192.168.1.7.nip.io/v2/_catalog ``` Should list `base-images/golang`, `base-images/alpine`, `base-images/node`, `base-images/python`, `base-images/maven`, `base-images/eclipse-temurin`, `base-images/php`. ## Adding a new image / updating a version Add a line to `images.txt`, commit, push, then re-run step 2 above (the ConfigMap's `images.txt` needs updating to match — copy the current file content in, same as the initial setup).