- Shell 57.8%
- Open Policy Agent 28.7%
- Makefile 7.3%
- Go Template 6.2%
|
All checks were successful
Validate / Render, lint, scan, and test policy (push) Successful in 14s
|
||
|---|---|---|
| .forgejo/workflows | ||
| .github/workflows | ||
| applications/smoke/chart | ||
| bootstrap | ||
| clusters/foundry | ||
| docs | ||
| policies/conftest | ||
| scripts | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .gitleaks.toml | ||
| .pre-commit-config.yaml | ||
| .yamllint.yaml | ||
| CONTRIBUTING.md | ||
| Makefile | ||
| mise.toml | ||
| README.md | ||
| renovate.json5 | ||
| versions.yaml | ||
Anvil
Anvil is the GitOps-managed desired state for the Kubernetes cluster running on
foundry.
This repository intentionally starts small. It contains a secure sample workload, a pinned Argo CD package, and manual-sync Argo Applications. It does not contain credentials or a CI workflow that talks to Kubernetes. Nothing has been applied to a cluster by building this repository.
The mental model
Anvil will have three distinct layers:
foundryand Ansible create a healthy host and K3s substrate.- Argo CD reads this repository from its GitHub recovery mirror.
- Kubernetes controllers turn the declarations in this repository into running workloads and, later, selected external cloud resources.
CI sits before Argo CD. It renders and checks declarations, but never applies them.
What exists now
applications/smoke/chart/ Local Helm chart and foundry values
bootstrap/argocd/ Pinned Argo CD installation package
bootstrap/root/ One-time root Application package
clusters/foundry/ Projects and child Applications
policies/conftest/ Repository safety rules and tests
scripts/render.sh Turns charts into ordinary YAML
scripts/validate.sh Fast and full validation entry point
mise.toml Exact local and CI tool versions
versions.yaml Human-readable version inventory
docs/decisions/ Architecture decision records
The foundry-root Application reads clusters/foundry/ from the public GitHub
mirror. That path defines separate platform and workload projects plus child
Applications for Argo CD and the smoke chart. Every Application starts in manual
sync mode and has no cascading deletion finalizer.
Chart versus values
The smoke chart answers: “What resources does this application need?” Its templates produce a Deployment and a ClusterIP Service.
The chart's default values.yaml contains visible, reviewable runtime choices
such as the image, replica count, probes, resources, and security contexts. The
small values/foundry.yaml file contains Foundry-specific labels. Argo CD will
own namespace creation and Pod Security labels in the next repository slice;
the chart deliberately does not own a Namespace.
Rendering combines both layers:
make render
Inspect the result under .rendered/. Rendering is a useful learning step: the
output is approximately what Argo CD will compare with the Kubernetes API.
Understanding the smoke workload
The sample uses podinfo, a small HTTP service:
- The selected application version is recorded in
versions.yamland the image uses an explicit version tag rather thanlatest. Before this workload is connected to Argo, that reference should be upgraded to a registry digest; version tags are clearer thanlatestbut can still be moved by a publisher. - A Deployment keeps one pod running.
- Readiness decides when the pod may receive Service traffic.
- Liveness lets Kubernetes restart an unhealthy process.
- The ClusterIP Service is reachable only inside the cluster.
- CPU and memory requests help scheduling; limits bound accidental consumption.
- The pod receives no Kubernetes API token because it does not need one.
- It runs as a non-root user with all Linux capabilities dropped, a read-only root filesystem, and the runtime-default seccomp profile.
- There is no Ingress, persistent storage, cloud resource, or secret.
Validation workflow
For the checks that work with the repository's minimum installed dependencies:
make validate-fast
This rejects obvious mutable images and plaintext Kubernetes Secrets, then
renders every application chart with its foundry values.
For the complete CI-equivalent suite, install mise, then install the exact tool versions and run validation:
make install-tools
mise exec -- make validate
Full validation adds:
- YAML and shell linting.
- GitHub Actions syntax validation.
- Kubernetes 1.36 schema validation.
- Conftest policy tests and manifest policy evaluation.
- Working-tree secret scanning.
The Forgejo and GitHub workflows run the same make validate command. Keeping
the logic in repository scripts makes local behavior visible and avoids two
different CI implementations.
Safe change loop
- Read the relevant chart templates, values, and decision record.
- Make one small declarative change.
- Run
make renderand inspect the generated YAML. - Run
mise exec -- make validate. - Open a Forgejo pull request and review the rendered behavior, not only the source diff.
- Merge only after CI passes.
Once Argo CD exists, deployment will still happen by merging reviewed Git—not by
running kubectl apply from a workstation or CI.
Bootstrap
The repository is prepared for a guarded, one-time Argo CD bootstrap. Plan mode contacts no cluster:
make bootstrap-plan CONTEXT=YOUR_CONTEXT
Execution is an explicit operator action and still leaves every Argo Application in manual sync mode. Read docs/bootstrap.md completely before running it. This repository build did not execute the bootstrap.
Repository boundary
The foundry repository owns Ubuntu, users, SSH, Tailscale, disks, mounts,
packages, K3s installation and lifecycle, node enrollment, stable node labels,
and transitional Docker Compose services.
Anvil owns Kubernetes namespaces, RBAC, Argo CD, Helm releases, Kustomize overlays, ingress, certificates, workloads, Kubernetes storage objects, Crossplane, policies, observability, backups, and Kubernetes recovery procedures.
See docs/architecture.md and the records in docs/decisions for the reasoning behind this structure.
Next slice
After the manual bootstrap and smoke synchronization have been observed and understood, the next safe slice is a shadow Traefik deployment on high NodePorts. It must not compete with Caddy for ports 80 and 443.