Project Overview¶
What is this Blueprint?¶
This Blueprint is a reproducible, fully local GitLab + Kubernetes CI/CD stack — a single repo that builds a 5-node kind cluster on the developer's laptop, installs a self-hosted GitLab CE on top, and demonstrates the full inner loop from git push → CI pipeline → running workload on https://<app>.local.example.net.
The delivery model is agentic-driven — the same .agents/skills/ SKILL.md files that explain the install to a human also work as the runbook a Copilot / Cursor / Claude Code agent reads as background context the moment the repo opens.
Core Purpose¶
- Zero-cloud CI/CD: Everything (cluster, GitLab, Runner, Postgres, Redis, MinIO, OpenBao, sample app) runs on the developer machine. No cloud account, no managed Kubernetes, no public DNS — only a
/etc/hostsentry the user maintains themselves. - Self-host a real GitLab: GitLab chart 10.x (GitLab 19.x CE), with bundled Envoy Gateway for ingress, a bundled OpenBao subchart for Secrets Manager, and external CloudNativePG / Redis / MinIO charts (chart 10.x dropped the bundled subcharts for those).
- End-to-end demo of the inner loop: a
git pushto a GitLab project triggers a Runner-driven pipeline that runshelm lint,helm template,docker build(BuildKit/kaniko),docker pushto the in-cluster registry, andhelm upgrade --installagainst the cluster. - Idempotent bootstrap: every step (host prereqs → cluster → CNPG → Redis → MinIO → OpenBao → GitLab → Runner → Phase-3 projects + CI) is safe to re-run. A full
tofu destroy && apply && bootstrap --phase 2 && blueprint-phase3reproduces the exact end state. - Manifest the rules in code and skills: hard rules (cluster lifecycle is
tofuonly, no committed secrets, bootstrap prepares never applies, noglabin the bootstrap,*.local.example.netis for humans not pods) are encoded both inAGENTS.mdand in the per-phase skills.
Technology Stack¶
Languages & Runtimes¶
| Layer | Tech | Where |
|---|---|---|
| Bootstrap (host installer) | Python ≥3.11, packaged with uv / hatchling |
infra/scripts/bootstrap/, pyproject.toml |
| Cluster IaC | OpenTofu (tofu) ≥1.6 | infra/tofu/*.tf |
| Sample workload | Go (guestbook) | apps/guestbook/guestbook-go/ |
| Demo backing services | upstream redis:7.4.1 (no custom Dockerfile) |
apps/redis/, apps/redis-slave/ |
Frameworks & Tools¶
- Kubernetes in Docker:
kindv0.27.0, 5 nodes (1 control-plane + 3 gitlab + 1 runner),kindest/node:v1.31.0 - Helm ≥3.16 — every chart install is
helm upgrade --installfrom a locally cached.tgz - Local-path-provisioner v0.0.30 (raw manifest), patched so chart-managed PVCs land under
infra/data/shared/ - CloudNativePG operator + single-instance cluster (8Gi)
- Redis —
bitnami/redis,architecture=standalone - MinIO — single-pod
minio/minio+ 11 GitLab buckets created via in-clustermc - OpenBao (Vault-compatible KV v2) — bootstrapped standalone + chart-bundled
gitlab-openbao - GitLab CE chart 10.1.1 (chart-bundled Envoy Gateway + chart-bundled OpenBao subchart)
- GitLab Runner chart 0.71.0 (Kubernetes executor, registers against
gitlab-webservice-default.gitlab.svc:8181) - cfssl (chart's pre-install Job) mints the self-signed wildcard cert for
*.local.example.net
Key Python Dependencies¶
| Package | Purpose |
|---|---|
hvac ≥2.3,<3 |
OpenBao HTTP client (KV v2 + token auth) |
python-gitlab ≥5.0,<6 |
Typed GitLab REST API client (Phase 3) |
requests ≥2.32 |
Raw HTTP for endpoints python-gitlab doesn't cover |
click ≥8.1,<9 |
CLI wrappers (blueprint-bootstrap, blueprint-secrets, blueprint-phase3) |
pyyaml ≥6.0,<7 |
YAML parsing for CI templates + apps manifest |
Key Features¶
- Three CLI entry points (post
uv sync): blueprint-bootstrap [--phase 1\|2] [--check] [--dry-run] [--destroy] [--port-forward] [--user]blueprint-secrets read\|ui\|port-forward— auto-port-forwards127.0.0.1:8200for OpenBaoblueprint-phase3 [--check] [--destroy] [--reset-clones] [--project X] [--no-overwrite-ci]- Every installer is a single-responsibility class wired together by composition roots (
BootstrapApp,Phase3App) — appending "another installer" means adding one class, one wiring line, and (if user-facing) one CLI option. SOLID over monoliths. - Versions pinned in one place (
infra/scripts/bootstrap/VERSIONS.json) — no class hardcodes a version string. - Helm charts cached locally at
infra/helm-charts/<name>-<version>.tgz; re-installs don't need network. - No plaintext secrets in git — OpenBao stores day-to-day secrets (
secret/gitlab/initial_root_password,secret/gitlab/runner/registration_token,secret/gitlab/bootstrap/admin_token); host-side snapshots land ininfra/secrets/(gitignored, mode 0600). - Templates are real YAML files on disk —
apps/shared-code/templates/*.yml.tplis the per-project CI source.phase3/ci_render.pydoesstring.Template.safe_substituteover them. The rule "templates must have their own files" has a regression test. - AGENTS.md frontmatter rule: skill
description:fields are single-line quoted YAML (no folded continuations). - Runbook-as-skill: three
.agents/skills/provision-phase-{1,2,3}/SKILL.mdfiles following the canonical 10-section template (Pre-flight → Install → Smoke tests → URLs → Iteration loop → Pinned versions → Common pitfalls → Rules of thumb → When install is green → How to undo).
Project Structure¶
blueprint/
├── AGENTS.md # hard rules + layout map (humans + agents)
├── index.md # quick-start + post-install cheat-sheet
├── pyproject.toml # uv project → blueprint-bootstrap + blueprint-secrets + blueprint-phase3
├── uv.lock # committed for reproducibility
├── apps/ # CANONICAL GitLab-side source (frozen after first push)
│ ├── shared-code/ # cross-app CI templates + helper scripts
│ ├── guestbook/ # demo: Go app + helm chart + Dockerfile
│ ├── redis/ # demo: redis master + helm chart (no Dockerfile → upstream)
│ └── redis-slave/ # demo: redis slave workload + helm chart
├── apps-local/ # WORKING TREE (gitignored; one git clone per GitLab project)
├── docs/
│ ├── prereqs.md # OS matrix + hardware floor + ports + DNS
│ ├── phase-1.md # cluster bring-up runbook
│ ├── phase-2.md # GitLab + Runner + OpenBao contributor guide
│ ├── phase-3.md # GitLab-side app onboarding + per-project CI contract
│ ├── secrets.md # full secret inventory + flow diagram
│ └── smart-docs/ # this documentation (smart-docs C4 output)
├── .agents/skills/
│ ├── provision-phase-1/SKILL.md
│ ├── provision-phase-2/SKILL.md
│ └── provision-phase-3/SKILL.md
└── infra/
├── helm-charts/ # locally cached chart tarballs
├── scripts/
│ ├── bootstrap.py # thin shim → bootstrap package
│ ├── phase3.py # thin shim → phase3_cli.py
│ └── bootstrap/ # class-based package (SOLID)
│ ├── VERSIONS.json # SINGLE SOURCE OF TRUTH for all versions
│ ├── cli.py # blueprint-bootstrap entry point (click)
│ ├── secrets_cli.py # blueprint-secrets entry point (click)
│ ├── phase3_cli.py # blueprint-phase3 entry point (click)
│ ├── app.py # composition root: BootstrapApp (Phase 1+2)
│ ├── app_phase3.py # composition root: Phase3App
│ ├── app_installer.py # HelmAppInstaller + HeadlampInstaller
│ ├── helm_cache.py # downloads charts to infra/helm-charts/
│ ├── tofu.py # tofu init/validate/next_steps (no apply)
│ ├── prereq.py # prereq ABCs (Docker/Kubectl/Kind/Helm/Tofu)
│ ├── versions.py # VERSIONS dict loader + tool_pin()
│ ├── paths.py # resolved filesystem paths
│ ├── logger.py # Logger protocol + Console/Null
│ ├── shell.py # CommandRunner protocol + Subprocess/DryRun
│ ├── os_detect.py # apt/dnf/pacman/brew branching
│ ├── installer.py # OSFamily → package manager Strategy
│ ├── port_forward.py # generic 127.0.0.1:<svc>:port helper
│ ├── phase2/ # Phase 2 installers (see Architecture doc)
│ └── phase3/ # Phase 3 modules (see Architecture doc)
├── data/ # hostPath source for the kind cluster (gitignored)
├── secrets/ # gitignored, mode 0600 — see docs/secrets.md
└── tofu/ # OpenTofu IaC for the kind cluster
Getting Started¶
# 1. Get the code
git clone https://github.com/bruj0/k8s-cicd-gitlab.git
cd k8s-cicd-gitlab/blueprint
# 2. Install Python deps into .venv/ (uv.lock committed → reproducible)
uv sync
# 3. Phase 1 prep: prereqs + tofu init + helm chart cache. Prints next commands.
uv run blueprint-bootstrap --phase 1
# 4. YOU apply — the bootstrap never applies infra.
tofu -chdir=infra/tofu apply -auto-approve
# 5. Phase 2 install — Gateway CRDs + CNPG + Redis + MinIO + OpenBao + GitLab + Runner.
# End-to-end takes ~10 min on a beefy laptop.
uv run blueprint-bootstrap --phase 2
# 6. Post-install host-side steps (trust CA, /etc/hosts, OpenBao login).
# 7. Phase 3 — provision the 4 GitLab projects + CI pipelines.
uv run blueprint-phase3
Or hand the whole thing to an AI agent — paste the three per-phase SKILL.md files into the first message and prompt "Run provision-phase-1, then provision-phase-2, then provision-phase-3. Don't run anything that needs sudo; print and ask. Follow each skill's Smoke tests section before declaring green." The agent reads each skill's Pre-flight → Install → Smoke tests → Iteration loop in order.
For the deep architecture, see 2. Architecture Overview.md.
For how a git push becomes a running workload, see 3. Workflow Overview.md.
Architecture Summary¶
Three discrete layers, all contained inside one repo:
- Infrastructure (IaC):
infra/tofu/—kind_cluster.cicdresource provisions a 5-node cluster with shared hostPath mounts (infra/data/shared/) for stable PVCs.null_resource.wipe_datadestroy provisioner guarantees full wipe ontofu destroy(paired with the bootstrap's--destroy). - Application stack (Phase 2):
infra/scripts/bootstrap/phase2/— Gateway API CRDs → local-path SC → CNPG → Redis → MinIO → OpenBao → wildcard TLS → GitLab (with bundled Envoy + bundled OpenBao subchart) → GitLab Runner. Orchestrated byPhase2Pipeline(13 ordered, idempotent steps). - GitLab-side app onboarding (Phase 3):
infra/scripts/bootstrap/phase3/— mints admin PAT viagitlab-rails runner→ seedsglab auth login→ createsblueprint-appsgroup → for each entry inapps_manifest.yaml(shared-code, guestbook, redis, redis-slave): creates project, clones intoapps-local/, rsyncs fromapps/, renders.gitlab-ci.yml, sets CI/CD variables, commits, pushes, triggers a smoke pipeline.
Phase 3 survives tofu destroy && apply && bootstrap --phase 2 by re-minting the PAT — no manual glab step required to rebuild. See 2. Architecture Overview.md § Phase 3 invariants for the contract.