talos-platform-apps — Knowledge Bundle
Bundle OKF 0.1 · 15 conceitos · devobagmbh/talos-platform-apps
Open source Repository Open in the app JSON README (API)
About
# talos-platform-apps — Knowledge Bundle
An [Open Knowledge Format](SPEC.md) v0.1 bundle: the **primary documentation
home** for this repository ([DR-0002](decisions/DR-0002-knowledge-bundle-as-primary-doc-home.md)).
It consolidates the catalog's architecture, contracts, gates, workflows,
per-sub-layer reference, and decision records into one navigable, self-describing
home for humans and agents. A catalog gap analysis is tracked separately in issue #523. Concepts are
authoritative and self-contained for their topic; each cites its `sources` for
provenance and carries a verification `timestamp`.
`AGENTS.md` remains the machine-readable **conventions** source of truth and
`DOCUMENTATION.md` the doc-authoring standard; this bundle documents and orients
to them without restating their normative rules verbatim. Component and sub-layer
READMEs migrate into the bundle perspectively (DR-0002); until consolidated, a
component is described in both places (a bounded, tracked duplication, not a
Details
- Kind
- OKF bundles
- Topic
- No topic detected
- Publisher
- devobagmbh
- Origin
- okf_github
- Category
- dados
- Version
- 0.1
- Open pull requests
- 12
- Last push
- 2026-09-10T06:16:13Z
- Repository state
- ativo
- Language
- Open Policy Agent
- Added
- 2026-09-09 12:03:57
- Updated
- 2026-09-09 12:03:57
- Origin id
devobagmbh/talos-platform-apps:knowledge/index.md
README
# talos-platform-apps
[](https://www.talos.dev/)
[](https://kubernetes.io/)
[](https://cilium.io/)
[](https://gateway-api.sigs.k8s.io/)
[](https://helm.sh/)
[](https://github.com/sigstore/cosign)
[](https://oras.land/)
[](https://www.conftest.dev/)
[](https://argo-cd.readthedocs.io/)
[](https://cert-manager.io/)
[](https://kind.sigs.k8s.io/)
[](https://github.com/FiloSottile/mkcert)
[](https://www.jetify.com/devbox/)
[](https://direnv.net/)
[](https://taskfile.dev/)
[](https://github.com/features/actions)
OCI sub-layers of the Devoba Talos platform: `automation`, `databases`, `identity`, `lifecycle`, `network`, `observability`, `registry`, `secrets`, `storage-block`, and `storage-objects`. Pre-rendered manifests, cosign-signed (keyless); SLSA v1 provenance and CycloneDX SBOM attestations are planned (phase 2+, see `task attest`). Consumed by consumer-cluster repos.
## Purpose
This repo is the **central platform catalog** of the Devoba Talos platform: **everything that is not substrate** (does not belong in `talos-platform-base`) lives here as independently versioned, signed OCI artifacts — Helm charts + values + optional custom manifests, pre-rendered in CI into final manifests. **Consumer-cluster repos draw from the catalog** by referencing exactly the OCI components they need (by tag / Argo `targetRevision`, not by Helm render at apply time). Division of labor: **Base = substrate, Apps = catalog, Consumer = composition** — whatever is not substrate belongs in the catalog, never in Base.
Rationale: deterministic, reviewable deployment artifacts with cryptographic supply-chain verification. A cluster update is a tag bump in the consumer configuration. See [ADR-0009](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0009-platform-layer-model.md).
## Sub-layer and component overview
OCI distribution is **per component** (ADR-0009, revision 2026-05-26). The sub-layer remains a directory grouping and tag namespace.
| Sub-layer | Components | Backlog issue |
|---|---|---|
| [`automation`](sub-layers/automation/) | renovate, velero | #16 |
| [`databases`](sub-layers/databases/) | cnpg | #15 |
| [`identity`](sub-layers/identity/) | dex | #47 |
| [`lifecycle`](sub-layers/lifecycle/) | crossplane, ipxe, providers, compositions | #12 |
| [`network`](sub-layers/network/) | multus-cni-crds | #48 |
| [`observability`](sub-layers/observability/) | prometheus-operator, loki, mimir, tempo, alloy, grafana | #17 |
| [`registry`](sub-layers/registry/) | harbor | #14 |
| [`secrets`](sub-layers/secrets/) | external-secrets, clustersecretstore-defaults | #15a |
| [`storage-block`](sub-layers/storage-block/) | democratic-csi, synology-csi | #50 |
| [`storage-objects`](sub-layers/storage-objects/) | garage, garage-buckets | #13 |
Each sub-layer has a `README.md` with a component table including sync-wave order. Each component has its own `README.md` + `compatibility.yaml` with a `requires` block (component dependencies, including cross-sub-layer ones such as `databases/cnpg` for Harbor).
## Local Setup
The dev environment runs entirely on **Devbox** (Nix-based) + **direnv**. Tool versions are pinned per repo and on `PATH` automatically after `cd` — no global `brew install` needed.
### Prerequisites
| Tool | Version | Installation note |
|---|---|---|
| **Devbox** | ≥ 0.16 | `curl -fsSL https://get.jetify.com/devbox \| bash` |
| **direnv** | ≥ 2.36 | macOS: `brew install direnv`; Linux: distro package. Hook it into your shell (see [direnv.net/docs/hook.html](https://direnv.net/docs/hook.html)) |
| **git** | ≥ 2.40 | already installed |
### Setup
```bash
git clone git@github.com:devobagmbh/talos-platform-apps.git
cd talos-platform-apps
direnv allow
lefthook install # activate the Git pre-commit / commit-msg hooks (lint, signing, secrets, scope)
```
`direnv allow` triggers the `.envrc`, which activates Devbox. On first invocation Devbox installs all tools (`helm`, `kubectl`, `cosign`, `oras`, `syft`, `go-task`, `yq`, `jq`, `sops`, `age`) into a reproducible Nix store. Subsequent `cd`s into the repo switch the environment automatically.
`lefthook install` wires the local Git hooks (`.git/hooks/`) — **required once per clone** so the pre-commit gates (including the commit-signing check below) actually run; without it an unsigned or non-conforming commit is caught only later on the server.
### Commit signing (required for merge)
`main` enforces **signed commits** (branch protection `required_signatures`). An unsigned commit makes a PR `mergeStateStatus: BLOCKED` even when review and checks are green. The fix is to **sign the commit** — never to admin-override the gate. (This is **git commit signing**, distinct from the **cosign OCI artifact signing** done by CI — see [Render / sign / publish workflow](#render--sign--publish-workflow).)
Configure signing **once, globally** on your machine. Commit signing is a per-developer, per-machine identity setting — a global config then signs commits in **every** repository, **every** fresh clone, and **every** git worktree automatically, so there is no per-repo or per-clone step to remember:
```bash
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub # your PUBLIC key (.pub); adjust if your key has a different name
git config --global commit.gpgsign true
```
> **Note:** `user.signingkey` points at the **public** key (`.pub`), not the private key. Global `commit.gpgsign true` enables signing in **all** your repositories — if you already sign other projects (e.g. with GPG), run the three commands with `--local` inside this clone instead, so your machine-wide `gpg.format`/key are not overwritten.
For the green **Verified** badge three conditions MUST additionally hold on GitHub (these are per-account and cannot be scripted from a repo):
1. The **same** public key is registered as a **Signing key** (Settings → SSH and GPG keys → *New SSH key* → key type **Signing Key**) — a separate entry from the Authentication key, even with identical key material.
2. Your committer email is a **verified** email on that same account.
3. If the key is **passphrase-protected**, it is loaded into the ssh-agent (`ssh-add --apple-use-keychain <key>` on macOS) — otherwise non-interactive / agent-driven commits fail to sign and are rejected.
**Verify** via the GitHub **Verified** badge on a pushed commit (the authoritative check). The local `git log --show-signature -1` additionally needs a configured `gpg.ssh.allowedSignersFile` to print `Good "git" signature`; without it a correctly-signed commit shows as unverifiable locally even though GitHub accepts it.
### Tools provided by Devbox
See `devbox.json`. Versions are pinned in `devbox.lock` as needed — updates happen in a controlled manner via `devbox update`.
### Tasks (instead of make)
`go-task` replaces make. Tasks are declared in `Taskfile.yml`. Example targets:
```bash
task render:one -- lifecycle/crossplane # render one component to rendered/manifest.yaml
task sign -- lifecycle/crossplane v0.1.0 # cosign sign of the published OCI tag
task attest -- lifecycle/crossplane v0.1.0 # SBOM + SLSA provenance (deferred stub, phase 2+)
task publish -- lifecycle/crossplane v0.1.0 # render → package → push → sign in one go
task ci # local reproduction of the GHA pipeline
```
### Local live testing (Talos + ArgoCD)
For end-to-end tests of individual sub-layers (render → OCI push → Argo sync → apply) there is a prod-conformant **Talos** cluster (docker provisioner) — the same substrate as the consumer clusters (Talos nodes, Cilium CNI, Gateway API, kube-proxy off, KubePrism) — with a local OCI registry behind `registry.localhost.direct` (mkcert TLS):
```bash
task local:up # Talos + Cilium + Gateway + ArgoCD + registry bridge
task local:publish -- lifecycle/crossplane 0.0.0-dev # push the component into the local registry
task local:apply -- lifecycle 0.0.0-dev # create the sub-layer's Argo Applications
task local:argo:ui # open https://argocd.localhost.direct
task local:down # tear everything down
```
Full architecture, endpoints, component details, and troubleshooting: [`local/README.md`](local/README.md).
### CI
The production pipeline runs on **GitHub Actions** (workflows under `.github/workflows/`). Triggers: PRs (render + lint, no push) and tag push `<sub-layer>/<component>-vX.Y.Z` (render + OCI push + cosign sign + verify). Tags are normally cut by release-please (see § Release automation); cosign signing is keyless via the GHA OIDC identity (`oci-publish.yml@refs/tags/...`).
**Three binding CI rules** for this and all other platform repos:
1. **Devbox cache active**: every job uses `jetify-com/devbox-install-action` with `enable-cache: true`. Tool versions come exclusively from `devbox.json`/`devbox.lock` — no separate `actions/setup-go`/`-helm`/`-kubectl` steps. This makes the CI environment byte-identical to the workstation, and builds are cache-warm after the first run.
2. **Locally reproducible**: every single task in `Taskfile.yml` runs on the workstation exactly as in CI. Before `git push` the full pipeline is replayed locally (`task ci`). No GHA-specific code in tasks — outer logic (OIDC, tag detection, matrix) stays in the workflow.
3. **Pipeline = thin task caller**: workflow steps only call `task <name>`. No inline `helm template`/`oras push`/`cosign sign` commands in the YAML. Whoever wants to change pipeline behavior changes the task — workflow diffs stay minimal and easy to review.
## Render / sign / publish workflow
```
Helm chart + values
│
▼
helm template
│
▼
rendered/manifest.yaml
│
▼
task package → <component>-<tag>.tar.gz (single OCI layer, tar of the Kustomize base: kustomization.yaml + manifest.yaml)
│
▼
oras push ghcr.io/devobagmbh/talos-platform-apps/<sub-layer>/<component>:<tag>
│
▼
cosign sign --yes
│
▼
cosign verify (keyless; identity oci-publish.yml@refs/tags/...)
# phase 2+ (deferred — NOT wired in oci-publish.yml today; see `task attest`):
# syft → CycloneDX SBOM → cosign attest
# slsa-github-generator → provenance → cosign attest
```
### Release automation (release-please)
Releases are automated per component — you do **not** hand-cut tags in the normal flow:
1. Merge a `feat`/`fix` commit that touches exactly one `sub-layers/<sl>/components/<c>/` (the lefthook `lint:commit-scope` gate enforces one component per commit, because release-please maps commits to components by path).
2. `release-please.yml` opens (or updates) a **per-component release PR** that bumps the version and updates the changelog.
3. Merging that release PR cuts the tag `<sub-layer>/<component>-vX.Y.Z`. The tag is created with a **GitHub App installation token** (not the built-in `GITHUB_TOKEN`, whose events do not cascade to other workflows), so the tag-push triggers `oci-publish.yml`, which renders → pushes → signs → verifies the component. The cosign signing identity stays `oci-publish.yml@refs/tags/...` — unchanged for `task verify` and for consumer-side Kyverno verification.
The per-component version SoT is [`.release-please-manifest.json`](.release-please-manifest.json); [`release-please-config.json`](release-please-config.json) enumerates every component (not-yet-implemented stubs carry `initial-version: 0.1.0`); every `components/` directory is a release package — a *stack* like `kube-prometheus-stack` is a composition documented in its sub-layer README, not a `components/` directory.
**One-time setup (org admin).** release-please cuts tags via a GitHub App token (short-lived, minted per run — no long-lived PAT). Create a GitHub App in `devobagmbh` with repo permissions **Contents: Read & Write**, **Pull requests: Read & Write**, **Issues: Read & Write**; **install it on this repository only** (not org-wide — limits the App key's blast radius); then set repo variable `RELEASE_PLEASE_APP_ID` and repo secret `RELEASE_PLEASE_APP_PRIVATE_KEY`. Until `RELEASE_PLEASE_APP_ID` is set the `release-please` job is **skipped** (neutral, no failing check); manual tag push remains available for releases in the meantime.
**Manual / backfill tag** — a hand-pushed `<sub-layer>/<component>-vX.Y.Z` tag also triggers `oci-publish.yml` directly (first-time backfill or a hotfix). Caveat: push **at most three tags per `git push`** — tags beyond the third in a single push raise no workflow run at all (silently), so batch larger backfills.
**Adoption cutover (pre-existing components).** `bootstrap-sha` in `release-please-config.json` marks where release-please starts managing this repo. Component changes merged *before* that SHA are **not** auto-released by the first run — release-please anchors on its own last release PR (or `bootstrap-sha`), never on the manually backfilled tags, and the manifest already records each component's last published version. Pre-cutover unreleased changes are therefore published via the **manual-backfill path above** when a component next warrants a version (`bootstrap-sha`/`last-release-sha` are top-level-only — there is no per-package config alternative). The pending pre-cutover backfill is tracked in issue #259.
**Recovering a stuck release** — if a release PR merged but no OCI artifact appeared, the cut tag did not reach `oci-publish.yml`. Check the [Actions tab](../../actions/workflows/oci-publish.yml) for a run on that tag:
- A run exists but **failed** (e.g. a transient upstream Helm-repo error during render) → re-run it from the Actions UI (*Re-run failed jobs*). No tag change needed.
- **No run** exists (the tag event never fired) → re-push the tag to re-raise the event (`oci-publish.yml` has no `workflow_dispatch`, so a tag re-push is the only trigger). Re-push the tag **at the commit it already points at** (the release PR's merge commit), never at `HEAD`, or a different tree is published — capture that commit *before* deleting the tag, and confirm it is the intended release commit (not a moved or tampered ref) before re-signing:
```bash
rel_sha=$(git rev-list -n 1 <sub-layer>/<component>-vX.Y.Z) # commit the tag points at — capture FIRST
git push origin :refs/tags/<sub-layer>/<component>-vX.Y.Z # delete the remote tag
git tag -fs <sub-layer>/<component>-vX.Y.Z "$rel_sha" # re-create at the same commit (signed)
git push origin <sub-layer>/<component>-vX.Y.Z # re-push → triggers oci-publish.yml
```
While the tag is deleted, a consumer Argo app pinning it by `targetRevision` sees a transient gap until the re-push lands.
**Rotating the App key** — generate a new private key in the GitHub App's settings, update the repo secret `RELEASE_PLEASE_APP_PRIVATE_KEY` with the new PEM, then delete the old key in the App settings. `RELEASE_PLEASE_APP_ID` is unchanged. Old keys stay valid until deleted, so there is no downtime window.
## Conventions
- **Per-component versioning**: SemVer per component (`<sub-layer>/<component>-vMAJ.MIN.PATCH`). Each component has an independent lifecycle (managed by release-please).
- **OCI paths**: `ghcr.io/devobagmbh/talos-platform-apps/<sub-layer>/<component>:<tag>` as the manifest, same path for SBOM/provenance attestations.
- **Signing**: cosign keyless (OIDC via the GitHub Actions workflow identity). Verification in consumer clusters via the Kyverno ClusterPolicy `image-verify-platform-oci` (see [Issue #21](https://github.com/devobagmbh/talos-platform-docs/issues/21)).
- **Value separation**: cluster-specific Helm values stay in the consumer-cluster repos. This layer holds defaults and shared values.
- **Language**: English throughout — code, comments, READMEs, and docs (platform policy 2026-06-03). Code and Helm values follow upstream conventions (English).
- **Tools**: all dev-relevant binaries come from Devbox — direct `brew install <tool>` is forbidden to avoid version drift.
- **Consumer composition**: consumer-cluster repos (layer 3) reference the OCI components by tag / Argo `targetRevision` and compose their cluster configuration from them. Which subset a consumer uses lives in the respective consumer repo, not here.
## Related docs
- [ADR-0009 — Platform-Layer-Model](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0009-platform-layer-model.md)
- [ADR-0012 — Platform-Registry-Proxy (Harbor)](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0012-platform-registry-proxy.md)
- [ADR-0013 — In-cluster registry (Harbor on both clusters)](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0013-in-cluster-registry.md)
- [ADR-0015 — Monitoring architecture (LGTM-A)](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0015-monitoring-architecture.md)