# 08 — Adding a new app This is the payoff for the app-of-apps structure: adding something new is "drop a file here," not a special procedure. ## The pattern Every app in this repo is two things sitting side by side in [`apps/`](../apps): ``` apps/ demo.yaml ← the Application (tells Argo CD where to look) demo/ ← the actual manifests (what gets deployed) ``` To add a new app called, say, `whoami`: 1. Create `apps/whoami/` and put its Kubernetes manifests in there — a Deployment, a Service, whatever it needs. Use `apps/demo/` as a template for structure and comment style. 2. Create `apps/whoami.yaml`, copied from [`apps/demo.yaml`](../apps/demo.yaml) with the names changed: `metadata.name`, `spec.source.repoURL` (still this cluster's public Gitea repository — see [`docs/04-app-of-apps.md`](04-app-of-apps.md#every-application-needs-its-own-repourl) for why every Application needs this set individually, not inherited), `spec.source.path` (→ `apps/whoami`), and `spec.destination.namespace`. 3. Commit and push both. That's it — no `kubectl apply`. The app-of-apps (already watching `apps/` — see [`docs/04-app-of-apps.md`](04-app-of-apps.md)) notices the new `whoami.yaml`, creates the `whoami` Application, which in turn deploys everything in `apps/whoami/`. 4. Confirm: `argocd app list` should show `whoami` alongside `demo`, `metallb`, and `longhorn`. If your new app needs storage, request it with a plain `PersistentVolumeClaim` and no `storageClassName` — Longhorn's chart set itself as the cluster's default StorageClass, so it's used automatically. See [`docs/06-longhorn.md`](06-longhorn.md). If your new app needs `hostNetwork`, `hostPath` volumes, or privileged containers (uncommon, but real for things like storage or networking addons), Kubernetes' Pod Security Admission will reject it by default — see `apps/metallb.yaml` and `apps/longhorn.yaml` for the working pattern: an Application-level `syncPolicy.managedNamespaceMetadata.labels` opting just that namespace into the `privileged` profile. Ordinary apps like `whoami` never need this. ## Manifest-based vs. Helm-based apps `apps/demo/` is plain Kubernetes manifests — no templating, nothing to render — which is why it's the easiest one to read start to finish while you're still learning what each field does. An app's folder can be a **Helm chart** instead. Argo CD detects this automatically: if it finds a `Chart.yaml` at the source path, it treats that app as a Helm chart rather than plain manifests. This matters most when you want to pull in a *third-party* chart — a database, an ingress controller, anything with a public Helm chart already maintained by someone else — without copying their manifests into your repo by hand. You declare it as a dependency and let Helm (via Argo CD) resolve it. You don't have to imagine this one — it's already running. [`apps/metallb/`](../apps/metallb) is exactly this pattern, for real: its [`Chart.yaml`](../apps/metallb/Chart.yaml) declares a dependency on the upstream MetalLB chart, and it even goes a step further by also carrying its own templates ([`apps/metallb/templates/`](../apps/metallb/templates)) for two custom resources that configure MetalLB once it's installed — useful to see if you ever need an app that's *mostly* someone else's chart but with a couple of your own resources layered on top. See [`docs/05-metallb.md`](05-metallb.md) for the full walkthrough, and [`apps/metallb.yaml`](../apps/metallb.yaml) for the matching Application — notice it's structurally identical to `apps/demo.yaml`; Argo CD figures out it's a Helm chart on its own from the presence of `Chart.yaml`, no new syntax required in the Application resource itself. > **Ask Claude:** "walk me through how I'd bring in [some real chart you > have in mind] as a Helm-based app in this structure" whenever you're > ready to try one of your own — `apps/metallb/` is a real template to > copy from. If the new app has its own `LoadBalancer` Service (see [`docs/05-metallb.md`](05-metallb.md)), pick its address from the free addresses listed in [`docs/network.md`](network.md), and add a row for it there once it's up — that file only stays useful if every app that gets an address shows up in it. ## Removing an app The reverse of adding one: delete `apps/.yaml` and `apps//`, commit, and push. The app-of-apps notices the missing Application manifest and prunes the `Application` object itself — but that alone does **not** delete what that Application deployed. Argo CD only cascades an Application's deletion into deleting its managed resources (namespace, Deployment, Service, everything) if that Application carries this finalizer: ```yaml metadata: finalizers: - resources-finalizer.argocd.argoproj.io ``` Without it, the resources are simply orphaned — still running, but no longer tracked by Argo CD or described anywhere in Git. `apps/demo.yaml` and `apps/metallb.yaml` both carry this finalizer. `apps/longhorn.yaml` deliberately does **not** — see the comment in that file for why cascading a storage layer's deletion is a different, much higher-stakes decision than cascading a stateless app's. > **Ask Claude:** "what would happen right now if I deleted > `apps/longhorn.yaml` and it had this finalizer?" before you ever add one > to an app that holds real data. ## Checklist - [ ] New app's manifests live in `apps//` - [ ] New `apps/.yaml` Application created, modeled on `demo.yaml` - [ ] `repoURL` set to this cluster's public Gitea repository, not left as a placeholder - [ ] Pushed to Git - [ ] `argocd app list` shows the new app, `Synced`/`Healthy` - [ ] If it has a `LoadBalancer` Service, its address is recorded in `docs/network.md` Continue to [`docs/09-growing-up.md`](09-growing-up.md) for what to add next as this cluster grows beyond a learning setup.