local-k8s — GitOps for the local GreenKube Minikube cluster
This repository is the single source of truth for the workloads running in the
local Minikube cluster. Declarative state is reconciled by ArgoCD (app-of-apps):
the root Application watches clusters/minikube/apps/ and creates one child
Application per component.
bootstrap/bootstrap.sh # day-0: cluster + ingress + cert-manager + ArgoCD + Gitea
│
▼
clusters/minikube/root-app.yaml # applied by the bootstrap, points at the in-cluster Gitea repo
│
▼
clusters/minikube/apps/*.yaml # child Applications (nginx, OpenCost, Prometheus, Grafana, VPA, demo)
Everything under clusters/ is reconciled continuously. Editing a manifest in
this repository (directly or through a GreenKube pull request) is the only way
to change the cluster.
Why Gitea?
ArgoCD needs a Git server it can reach; the GreenKube PR bot needs a Git API it can open pull requests against. Running Gitea inside the cluster keeps the loop self-contained and fully testable offline:
GreenKube UI ──► PR bot ──► Gitea PR ──► merge ──► ArgoCD sync ──► cluster
▲ │
└────────────── apply detection & verification ◄──────────────┘
On a real installation, point the Applications and the
greenkube.cloud/git-repo annotations at your GitHub/GitLab repository instead.
Bootstrap
Prerequisites: Docker, Minikube, kubectl and Helm.
./bootstrap/bootstrap.sh
The script is idempotent and performs the day-0 steps that cannot be GitOps-managed (a Git server and ArgoCD must exist before they can be self-managed):
- starts Minikube if needed;
- installs ingress-nginx and cert-manager (and the
letsencrypt-prodissuer); - installs ArgoCD (pinned version);
- installs Gitea and creates the
greenkubeuser + API token; - pushes this repository and the GreenKube source repository (
devbranch) to Gitea; - creates the
greenkube-gitopsnamespace secret (GIT_TOKEN, Postgres credentials); - applies the AppProject and the root Application.
After that, ArgoCD owns the rest.
Access
kubectl port-forward svc/argocd-server -n argocd 8080:443
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
Gitea is reachable from the host after
kubectl port-forward -n gitea svc/gitea-http 3000:3000.
Managed applications
| Application | Source | Namespace | Notes |
|---|---|---|---|
nginx |
ingress-nginx chart + local ingress manifests | ingress-nginx |
Adopts the controller and manages platform routes; ingress resources use Prune=false |
opencost |
OpenCost chart | opencost |
Uses the in-cluster Prometheus service |
prometheus |
kube-prometheus manifests | monitoring |
Owns the monitoring stack except Grafana resources |
grafana |
kube-prometheus manifests | monitoring |
Owns only the grafana-* manifests |
gitea |
Gitea chart | gitea |
Adopts the bootstrap release and serves gitea.greenkube.cloud |
vpa |
Fairwinds chart | vpa |
Vertical Pod Autoscaler in recommendation mode (updater disabled) |
demo-app |
this repo | demo |
Annotated workload used to exercise rightsizing and the PR bot |
ArgoCD adopts the existing ingress-nginx and Gitea releases and manages
OpenCost, kube-prometheus, Grafana and VPA. ArgoCD itself and cert-manager
remain bootstrap components. The prometheus and grafana Applications use the same
pinned kube-prometheus source revision but select disjoint manifest sets.
GreenKube source annotations
The PR bot finds the manifest of a workload through annotations:
| Annotation | Required | Meaning |
|---|---|---|
greenkube.cloud/git-repo |
yes | Git remote URL (Gitea by default) |
greenkube.cloud/git-path |
no | Manifest path; discovery scans the repo when omitted |
greenkube.cloud/git-branch |
no | Base branch override (defaults to main) |
Each managed workload must carry the annotation of the repository that contains its own manifest — that is what makes the pull request possible.
Repository layout
bootstrap/bootstrap.sh Day-0 installer (idempotent)
clusters/minikube/
├── project.yaml ArgoCD AppProject
├── root-app.yaml App-of-apps root Application
├── apps/ One ArgoCD Application per component
├── values/ Helm values per component
└── manifests/ Raw manifests (demo workloads)