The homelab pushes to a project called "homelab"; this cluster's is apps-registry. Harbor rejects a push to a missing project with "unauthorized: project homelab not found" — the word unauthorized sends you looking at the robot account, when the credentials were never the problem. Only the harbor_project default changes. The com/homelab and org/homelab paths in this repo are the library's own package and resource paths and have nothing to do with the registry; renaming those would break the library. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N
devops-lib (GKE)
Jenkins Shared Library for the CI/CD pipeline on the GKE cluster. The GCP counterpart of the homelab repo of the same name, and a copy rather than a shared repo because several values here are cluster-specific in ways that would break the other cluster if crossed over.
Register it in Jenkins under the name devops-lib, exactly as on the
homelab. Consuming repos then need no change at all — the same two-line
Jenkinsfile works on either cluster, and which library it resolves to is a
property of the Jenkins it runs on.
What differs from the homelab copy, all of it a consequence of GKE being a real cloud rather than one VM:
- The registry hostname is
harbor.35.238.248.203.nip.io, in the push target and in all five fallback Dockerfiles. - Harbor speaks TLS. The homelab's dind passes
--insecure-registry; here the pod mounts the private CA into dockerd's trust store instead. Node trust covers pulls only — a push is a separate client. helm_repo_urluses cluster DNS, since the clone happens inside a build pod. The homelab points it at an ingress hostname.build-toolsis not in this repo. It lives indevops-base-images-gcpalongside the mirrored base images, and is referenced here only by tag.
Everything else — the stage flow, the fallback templates, the hooks contract — is unchanged. The library was already adapted from a much larger, company-wide one; see git history for what was removed.
Using it in a service repo
The entire Jenkinsfile is 2 lines:
@Library('devops-lib') _
homelabPipeline(repo_name: 'my-service')
repo_name is the only required key. Everything else has a sensible
default — override any of them by passing extra keys to homelabPipeline,
or by committing a config.yaml to the service repo's own root (merged
in after checkout; repo-committed values win over the Jenkinsfile call).
| Key | Default | Notes |
|---|---|---|
service_name |
repo_name |
Second path segment under devops-helm-charts/values/ |
argo_app_name |
repo_name |
Must match the ArgoCD Application's metadata.name |
harbor_project |
apps-registry |
Must already exist in Harbor, and be public unless you also wire an imagePullSecret — the app values assume anonymous pull. A push to a missing project fails as unauthorized: project <name> not found |
helm_repo_url |
devops-helm-charts-gcp, over cluster DNS |
http://gitea-http.gitea.svc.cluster.local:3000/gitadmin/… — pod-to-pod, so it never leaves the cluster and comes back through the ingress |
image_tag_yq_path |
.deployment.image.tag |
Override this if the app's chart isn't 1.0.0 — e.g. sts-2.0.0 uses .podtemplate.image.tag instead. Getting this wrong doesn't fail loudly: yq -i creates the path if missing rather than erroring, silently leaving the real field un-bumped. |
dockerBuildVersion |
none | Only read when the repo has no Dockerfile of its own — picks a fallback template (see below). No default; either ship a Dockerfile or set this. |
Pipeline stages
checkOut → loadConfig → runHooks(pre_build) → buildDocker → runHooks(post_build) → updateHelmTag → syncArgoApp → notify, all inside
a podTemplate (resources/org/homelab/dind-pod.yaml) via
node(POD_LABEL) { ... }.
loadConfig— if the repo has aconfig.yamlat its root, its keys are merged into the pipeline config (repo values win).runHooks— readsconfig.yaml'shooks.pre_build/hooks.post_buildlists, each{name, script, interpreter, requirements, blocking, timeout_seconds}. Blocking by default;blocking: falsedemotes a failure to advisory (log + continue). Script paths must be repo-relative (no.., no absolute paths).buildDocker— uses the repo's ownDockerfileif present; otherwise renders one fromresources/com/homelab/<lang>-Dockerfilebased ondockerBuildVersion(e.g.go-1.22,node-20,python-3.12,java-21,php-8.3). All fallback templates pull base images from Harbor'sbase-imagesproject (mirrored via the separatedevops-base-images-gcprepo), not Docker Hub directly. Only versions actually mirrored there resolve — an unmirrored tag fails the build rather than silently falling back to Docker Hub.updateHelmTag— clonesdevops-helm-charts, bumps the image tag viayqatimage_tag_yq_path, commits, pushes tomain.syncArgoApp— calls the ArgoCD REST API to syncargo_app_name.
Adding a new language's fallback template
- Add the base image to
devops-base-images/images.txt, re-mirror it into Harbor. - Add
resources/com/homelab/<lang>-Dockerfile, parametrized by${version}(rendered viaconstructTemplate.groovy'sSimpleTemplateEnginewrapper). - Add a case for it in
buildDocker.groovy'stemplatesmap.
Build-tools image
The docker-cli container runs
harbor.35.238.248.203.nip.io/base-images/build-tools:1, which bakes in
git, yq, bash, python3 with pip and venv, and curl, so nothing is installed
on demand on every build.
It is not built here. The Dockerfile lives in devops-base-images-gcp,
next to the mirrored base images, because it is the same kind of artefact:
built by hand, occasionally, and pushed to Harbor. A Jenkins job could not
build it anyway — it is the image Jenkins builds in.
dind-pod.yaml pins the tag, so rebuilding the image rolls nothing out
until that pin is bumped. Bump the tag rather than overwriting one.
Registry trust
dind-pod.yaml mounts the registry-ca ConfigMap (published by
devops-infra-argo-config-gcp) into the dind container at
/etc/docker/certs.d/harbor.35.238.248.203.nip.io/ca.crt.
Without it, pushes fail TLS verification while pulls of the same image succeed, which reads like a broken registry. The reason is that the two are different clients: pulls are performed by containerd on the node, which was told to trust this CA when the node pool was created, whereas the push comes from dockerd inside the build pod, which has its own trust store. The directory name must be the registry hostname exactly — dockerd looks the path up by host and silently ignores a mismatch.