# Adding a Platform Component

A platform component is a directory under `payload/platform/` with exactly one `application.yaml`; the `platform` ApplicationSet turns it into an ArgoCD Application on the next poll. Workloads go in the [other repository](<https://homelab.wlkr.ch/development/add-workload/index.md>). Why the platform is shaped this way is in [GitOps Strategy](<https://homelab.wlkr.ch/architecture/gitops/index.md>).

## Steps

1. **Create the directory** with a complete `application.yaml` copied from a sibling: the generator copies its labels, annotations, finalizers and `spec` verbatim, so a fragment is not an Application. It carries `project: infra`, like every component except the two that configure ArgoCD itself. Keep the [sync policy](<https://homelab.wlkr.ch/architecture/gitops/#sync-policy>) as it is — every component carries the same one, `retry` included, and a chart that lands before its CRDs stays failed without it.
2. **Pin the version once**, as `targetRevision` in that file. The `Makefile` reads the bootstrap versions out of these manifests and Renovate bumps them there — see [Version pins](<https://homelab.wlkr.ch/architecture/gitops/#version-pins>).
3. **Put each resource with what it needs, not with what it configures.** Nothing orders the Applications, so a `ClusterSecretStore` sits with OpenBao and the issuers are their own component — see [Bootstrap convergence](<https://homelab.wlkr.ch/architecture/gitops/#bootstrap-convergence>). A resource whose CRD comes from another component gets `argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true`. A component with an `ExternalSecret` needs its namespace in both lists of `payload/platform/openbao/cluster-secret-store.yaml`, or the ExternalSecret reads `denied by spec.condition`. A chart from a new repository needs that host in the repo-server's policy in `payload/platform/security/network-policies/argocd.yaml`, or the Application reads `ComparisonError`.
4. **Label the namespace** in `payload/platform/security/pod-security.yaml` with the Pod Security Admission level it demonstrably needs, one sentence of why next to any `privileged`, and add a `privileged` one to the list in `namespace-privileged-allowed` — see [Security Policies](<https://homelab.wlkr.ch/platform/security-policies/#pod-security-admission>). Then **deny it to workloads**: add `"!<namespace>"` to the `apps` project's destinations in `payload/platform/argocd-projects/projects.yaml`, which otherwise lets every workload Application write into it. A `privileged` namespace is the one that matters: the admission policy guards new Namespaces, not deployments into an existing one, so a workload commit could place a host-networked pod on every node.
5. **Write its network policy** as `payload/platform/security/network-policies/<namespace>.yaml`: the `default-ingress` and `default-egress` deny pair plus one policy per workload that needs more, copied from the namespace most like it. New HTTP rules start as `http: [{}]` until the [rollout](<https://homelab.wlkr.ch/platform/security-policies/#rollout>) has recorded a week of requests.
6. **Expose a UI** with an `HTTPRoute` on `infra-gateway` next to the component, and put it behind Authentik — see [Gateway API](<https://homelab.wlkr.ch/platform/gateway-api/#usage>) and [Authentik](<https://homelab.wlkr.ch/platform/authentik/index.md>). A route that crosses namespaces needs a `ReferenceGrant` in the target namespace.
7. **Check Renovate sees the pin.** Chart versions are covered; an image tag inside `valuesObject` needs a `# renovate:` comment — see [Maintenance](<https://homelab.wlkr.ch/development/maintenance/index.md>).
8. **Write the page** under `docs/platform/` with the shared skeleton — intro, At a glance, Configuration, Usage, Health check, Pitfalls, Recovery only with a real runbook — add it to the nav in `zensical.toml` and to the [Components table](<https://homelab.wlkr.ch/platform/#components>). The strict docs build fails on a page the nav does not list.
9. **Open the PR** and run the checks in [Contributing](<https://homelab.wlkr.ch/development/contributing/index.md>). After the merge the Application appears within one polling interval; a Degraded one is usually waiting on a CRD, a Secret or OpenBao — see [Troubleshooting](<https://homelab.wlkr.ch/operations/troubleshooting/index.md>).
