From 323ae2042166fcce1cc816da9e28a4f9d3280d10 Mon Sep 17 00:00:00 2001 From: Mukul Sharma Date: Thu, 3 Sep 2026 09:15:04 +0530 Subject: [PATCH] initial commit: base image mirror manifest + one-off setup docs --- README.md | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++ images.txt | 21 +++++++++ mirror.sh | 26 +++++++++++ 3 files changed, 182 insertions(+) create mode 100644 README.md create mode 100644 images.txt create mode 100755 mirror.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..d67b2c5 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# 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 + - name: docker-config + mountPath: /root/.docker + readOnly: true + volumes: + - name: docker-graph-storage + emptyDir: {} + - name: mirror + configMap: + name: base-images-mirror + defaultMode: 0755 + - name: docker-config + secret: + secretName: harbor-robot-dockerconfig + items: + - key: .dockerconfigjson + path: config.json +EOF + +kubectl wait --for=condition=Ready pod/base-images-mirror -n jenkins --timeout=120s +kubectl exec -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 + 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). diff --git a/images.txt b/images.txt new file mode 100644 index 0000000..7815de3 --- /dev/null +++ b/images.txt @@ -0,0 +1,21 @@ +# Manifest of base images mirrored into Harbor's base-images project. +# One line each: +# +# All chosen as the leanest official variant that still keeps a shell + +# package manager (not distroless — deliberate choice: homelab +# debuggability via kubectl exec matters more here than the last bit of +# attack-surface reduction). go/alpine were already minimal and are +# mirrored unchanged; node/python/java/php all moved from their +# Debian-slim (or, for php, full Apache+Debian) default to an Alpine +# equivalent. +# +# Re-run mirror.sh after editing this file to add a language/version or +# pick up a base image update — nothing here happens automatically. + +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 diff --git a/mirror.sh b/mirror.sh new file mode 100755 index 0000000..781e32d --- /dev/null +++ b/mirror.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Reads images.txt and pulls/retags/pushes each entry into Harbor's +# base-images project. Meant to run inside a docker-capable container +# with push credentials already available (docker-cli + dind sidecar, +# harbor-robot-dockerconfig mounted at /root/.docker/config.json) — same +# pattern devops-lib's build-tools.Dockerfile bootstrap already uses, see +# README.md's one-off kubectl pod commands. Not run through any Jenkins +# pipeline — genuinely one-time/occasional, re-run by hand when +# images.txt changes. +set -eu + +REGISTRY="${REGISTRY:-harbor.192.168.1.7.nip.io}" +PROJECT="${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 ==="