KubeHarbor
KubeHarbor is a static portfolio demo. It packages the fictional Vandelay Industries LedgerLite FastAPI API as a Helm chart, Kustomize overlays, and Argo CD application manifests. The manifests are rendered and validated offline. Nothing in this project is deployed to a cluster. kind was not run. There is no live cluster.
Vandelay Industries is fictional. The container image ghcr.io/OWNER/ledgerlite:0.1.0 is a placeholder and is not claimed to be published. The prod digest sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef is a placeholder.
The chart listens on port 8000. Probes use /healthz, /readyz, and a startup probe. The migrate Job runs alembic upgrade head.
What is rendered
Three environments, ledgerlite-dev, ledgerlite-staging, and ledgerlite-prod:
| dev | staging | prod | |
|---|---|---|---|
| replicas / HPA | 1 / off | 2 / 2–4 | 3 / 3–10 |
| API resources | 50m/64Mi → 250m/256Mi | 100m/128Mi → 500m/256Mi | 250m/256Mi → 1/512Mi |
| PDB | none | minAvailable 1 | minAvailable 2 |
| Postgres | in-cluster StatefulSet | external | external |
| ServiceMonitor | off | on | on |
| ClusterIssuer | letsencrypt-staging | letsencrypt-staging | letsencrypt-prod |
| Image | tag 0.1.0 | tag 0.1.0 | digest pin |
helm template charts/ledgerlite with the default values fails on purpose. The error contains existingSecret. Set existingSecret to a Secret name, or set externalSecrets.enabled=true. The chart does not put a database URL or a password in values.
Extra objects
The minimum file list did not name every object the two renderers have to share. These were added so the Helm and Kustomize output stay in the same set:
- Helm templates
namespace.yaml,resourcequota.yaml, andlimitrange.yamlcreate the namespace (Pod Securityrestricted), the quota, and the limit range. - Helm template
backup-pvc.yamlis the dev backup volume. - The dev Kustomize overlay adds the Postgres StatefulSet, the headless Service (
clusterIP: None), the backup PVC, the backup CronJob (pg_dump, 7 files), and the in-cluster Postgres NetworkPolicies. externalsecret.yamlrenders anexternal-secrets.io/v1ExternalSecret whenexternalSecrets.enabledis true. A separate render,rendered/helm/dev-externalsecret.yaml, exercises that schema. It is not one of the six compared renders.
The Kustomize migrate Job is a normal Job with argocd.argoproj.io/hook: PreSync. The Helm Job uses helm.sh/hook: pre-install,pre-upgrade. The consistency test ignores only those hook annotations (and the Helm checksum/config annotation).
Layout
charts/ledgerlite/Helm chart, values, schema, and helm-unittest suites.kustomize/base,kustomize/components/{external-db,monitoring,pdb},kustomize/overlays/{dev,staging,prod}.gitops/argocd/AppProject, three Applications, and an app-of-apps. Dev and staging sync from the Helm chart with automation on. Prod syncs from the Kustomize prod overlay with automation off, and with prune and selfHeal on. These files are not applied.rendered/committed Helm and Kustomize output.make rendermust not change it.results/checks.jsonis written bymake checkfrom real tool output.policies/.kube-linter.yamlandpolicies/checkov-skips.mdhold every exclusion or skip, with a reason.site/kubeharbor/is the static page, built bymake showcase.
Commands
From this directory:
make tools
make render
make check
make showcase
make test
make tools downloads pinned linux-amd64 binaries into .tools/ (gitignored) and checks the sha256 values hardcoded in scripts/install_tools.sh. Helm cache, config, data, and plugins stay under .tools/helm. checkov is installed into /tmp/kubeharbor-checkov. pytest, PyYAML, and Pygments are installed into .venv.
Pinned tools: helm v3.16.4, kustomize v5.4.3, kubeconform v0.6.7, kube-linter v0.7.6, helm-unittest v0.7.2, checkov 3.2.449.
make check lints the three values files with helm lint --strict, runs helm-unittest, kubeconform, kube-linter, checkov, and pytest, then writes results/checks.json. kubeconform uses -strict and local schemas only. It does not pass -ignore-missing-schemas. kubeconform v0.6.7 rejects the version token 1.30.x, so the command passes -kubernetes-version 1.30.14. The schema files are the v1.30.14-standalone-strict set plus the ServiceMonitor, Certificate, ExternalSecret, Application, and AppProject schemas. Every rendered file, the ExternalSecret render, and gitops/argocd/*.yaml are validated. Skipped resources must be 0.
Checkov runs the Kubernetes framework on rendered/ and gitops/, and the Helm framework on the chart. Skips are listed in policies/checkov-skips.md and policies/checkov.yaml. The page copies the counts from results/checks.json.
Secret names
Create the Secret in the target namespace before install. The value below is a placeholder, not a credential:
kubectl -n ledgerlite-dev create secret generic ledgerlite-dev-db \
--from-literal=DATABASE_URL='REPLACE_ME' \
--from-literal=POSTGRES_USER='REPLACE_ME' \
--from-literal=POSTGRES_PASSWORD='REPLACE_ME' \
--from-literal=POSTGRES_DB='REPLACE_ME'
Staging and prod only need DATABASE_URL in ledgerlite-staging-db and ledgerlite-prod-db. An ExternalSecret can point at the ClusterSecretStore name in values (vandelay-cluster-store in the defaults). That store is not a real cluster.
GitHub workflow
.github/workflows/k8s.yml would run make tools, make check, and make showcase on ubuntu-latest. This monorepo does not register that file, because GitHub only loads workflows from the repository root. The workflow is not executed here, and it has no secrets.
Installing on kind when Docker is available
These are instructions for a machine that has Docker and kind. They were not run for this demo. The showcase page does not claim that a kind cluster exists.
kind create cluster --name ledgerlite
kubectl create secret generic ledgerlite-dev-db -n ledgerlite-dev \
--from-literal=DATABASE_URL='REPLACE_ME' \
--from-literal=POSTGRES_USER='REPLACE_ME' \
--from-literal=POSTGRES_PASSWORD='REPLACE_ME' \
--from-literal=POSTGRES_DB='REPLACE_ME' || true
helm install ledgerlite charts/ledgerlite \
--namespace ledgerlite-dev --create-namespace \
-f charts/ledgerlite/values-dev.yaml
kubectl apply -k kustomize/overlays/dev
kubectl apply -f gitops/argocd/project.yaml
kubectl apply -f gitops/argocd/app-dev.yaml
kind delete cluster --name ledgerlite
The namespace create and the Secret will race if you paste this as one script. Create the namespace, then the Secret, then install. The image pull will fail until a real image replaces the placeholder.
Showcase
https://3.1.79.63/portfolio/kubeharbor/ is a static page. It has no process and no port. The footer states that the manifests were rendered and validated offline, that nothing was deployed, and that the client is fictional.