Add a new application
Adding an app means committing a directory to the repo. Argo CD notices it and syncs it for you, so there is no kubectl apply and no manual step against the cluster. This guide walks through what that directory looks like and the conventions worth knowing.
How it works
Section titled “How it works”Every app lives under kubernetes/cluster/active/apps/<name>/ and is split into a base and a prod overlay. The overlay is the unit Argo CD syncs, and a small config.json inside it is what gets the app noticed: an ApplicationSet watches the repo for apps/**/overlays/*/config.json and turns each one it finds into an Argo CD Application that it keeps in sync. So the whole job is writing files and pushing them.
The layout:
📁 apps/cyberchef/ 📁 base/ 📄 kustomization.yaml 📄 values.yaml 📁 overlays/prod/ 📄 kustomization.yaml 📄 values-override.yaml 📄 config.json 📄 replacements.yaml 📁 resources/ 📁 external-secrets/replacements.yaml, resources/, and external-secrets/ are optional. Add them only when the app needs them, for example an HTTPRoute under resources/.
Step-by-step Guide
Section titled “Step-by-step Guide”1. Decide how to package it
Section titled “1. Decide how to package it”Most apps wrap a Helm chart with Kustomize, which keeps the chart’s values in the repo and lets the overlay layer changes on top. It is the common pattern here, not a requirement: if no chart fits, a plain Kustomization of hand-written manifests works too.
When you do use a chart, the rough order of preference is the project’s official chart, then TrueCharts, then the bjw-s app-template (for apps which lack a good Helm chart). CyberChef uses the TrueCharts chart. Inspect a chart before committing to it:
helm show chart oci://oci.trueforge.org/truecharts/cyberchefhelm show values oci://oci.trueforge.org/truecharts/cyberchef2. Set the base values
Section titled “2. Set the base values”base/values.yaml holds the stable, non-secret configuration: image, ports, persistence intent, anything intrinsic to the app. Keep it small, or leave it empty when the chart defaults are fine, as CyberChef does. The base kustomization usually just exists to be extended:
---apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationresources: []3. Render the chart in the overlay
Section titled “3. Render the chart in the overlay”The overlay pulls the chart with helmCharts and layers values-override.yaml (environment-specific values) on top of the base. Here is CyberChef’s overlay, which uses a TrueCharts chart and exposes itself publicly:
---apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationresources: - ../../base - resources/httproute.yaml # public apps only, see step 5helmCharts: - name: cyberchef repo: oci://oci.trueforge.org/truecharts version: 13.5.0 # pinned, kept current by Renovate releaseName: cyberchef namespace: cyberchef valuesFile: ../../base/values.yaml additionalValuesFiles: - values-override.yaml includeCRDs: truename is the chart’s name and has to match what the registry publishes. releaseName and namespace are your choices. They line up here because the chart happens to be called cyberchef, but with a generic chart like app-template the name would be app-template while the release and namespace stay your app’s name.
4. Register it with config.json
Section titled “4. Register it with config.json”This is the file the ApplicationSet discovers. It is mostly the app name and the namespace it deploys into:
{ "appName": "cyberchef", "destName": "in-cluster", "destNamespace": "cyberchef", "destServer": "https://kubernetes.default.svc", "project": "default", "userGivenName": "cyberchef", "annotations": {}, "labels": {}}5. Expose it (optional)
Section titled “5. Expose it (optional)”Pick the path that matches how the app should be reached (see traffic flow):
- Public: add an
HTTPRouteunderresources/, with aparentRefto thehttp-gateway-apigateway inenvoy-gateway-ingress, on a hostname under${FQDN_01}. This is what CyberChef does:
---apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: cyberchefspec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: http-gateway-api namespace: envoy-gateway-ingress hostnames: - "cyberchef.${FQDN_01}" rules: - backendRefs: - kind: Service name: cyberchef namespace: cyberchef port: 10219 matches: - path: type: PathPrefix value: /- Tailnet only: use a Tailscale Ingress, either through the chart’s Ingress values or an
IngresswithingressClassName: tailscaleand thetailscale.com/proxy-group: ingress-proxiesannotation, on a hostname under${FQDN_02}.
The route lives in its own file rather than in the chart’s values because, unlike Ingress, the Gateway API’s HTTPRoute is not yet supported by many charts. When a chart does expose route values, you can define it there instead of adding a separate resource.
Real domains never go in the repo. Hostnames stay as ${FQDN_01} or ${FQDN_02}, and replacements.yaml (with a configMapGenerator that reads /tmp/env-vars.env) substitutes the real value when the manifests are rendered. To list the app on the Homepage dashboard, add the gethomepage.dev/* annotations to its route.
Apps that need secrets or a database follow established patterns: External Secrets manifests under external-secrets/, and a CloudNativePG cluster under resources/ for PostgreSQL. The outline app is a good reference for both.
6. Render it before committing
Section titled “6. Render it before committing”Build the overlay the same way Argo CD does, and read the output rather than just checking it passes. Confirm the hostname substitution landed and that the route points at the rendered Service name and port:
kubectl kustomize --enable-helm --load-restrictor=LoadRestrictionsNone \ kubernetes/cluster/active/apps/cyberchef/overlays/prodArgo CD owns the committed state, so the normal path never includes kubectl apply.
7. Commit and let Argo CD sync
Section titled “7. Commit and let Argo CD sync”Push to the repo. The ApplicationSet picks up the new config.json, creates the Application, and syncs it, creating the namespace and self-healing on drift. Check it landed:
kubectl get applications -n argocd | grep cyberchefkubectl get pods -n cyberchef