Adding a Workload¶
This guide walks through deploying a new application to the cluster from scratch. The good news: once the first one exists, adding the next is creating a directory and pushing. The whole point of the machinery in the rest of this documentation is that this page is short.
Overview¶
All workloads live under payload/workloads/<app-name>/. A workloads parent ArgoCD Application auto-discovers any application.yaml file in that directory tree, so once the parent exists, adding a new folder is all that's needed to register an app with ArgoCD. No console, no kubectl apply, no step that only exists in someone's memory.
Note
There are currently no workloads, so payload/workloads/ and its parent Application do not exist. The first workload needs Step 1 below; subsequent ones can skip it.
Step 1: Create the Workloads Parent Application¶
Add this document to payload/root.yaml (the apps AppProject it references is
already defined in payload/argocd/argocd-projects.yaml):
---
# Parent Application: manages user workloads from payload/workloads/
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: workloads
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: apps
source:
repoURL: https://github.com/JanWelker/homelab.git
targetRevision: HEAD
path: payload/workloads
directory:
recurse: true
include: "{**/application.yaml}"
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
The root Application (payload/argocd/root-application.yaml) syncs
payload/root.yaml, so merging the change is enough — no kubectl apply.
Step 2: Create the App Directory¶
Step 3: Create the ArgoCD Application¶
Create payload/workloads/my-app/application.yaml. For a plain manifest-based app:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
annotations:
argocd.argoproj.io/manifest-generate-paths: .
spec:
project: apps
source:
repoURL: https://github.com/JanWelker/homelab.git
targetRevision: HEAD
path: payload/workloads/my-app
directory:
exclude: "application.yaml"
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
For a Helm chart, replace source with a sources block.
Step 4: Add Kubernetes Manifests¶
At minimum you need a Deployment and a Service. Place them alongside application.yaml:
payload/workloads/my-app/
├── application.yaml
├── namespace.yaml # optional if using CreateNamespace=true
├── deployment.yaml
├── service.yaml
├── httproute.yaml # if you want to expose the app
└── networkpolicy.yaml # recommended
Note
A namespace with a policy is default-deny for ingress once one selects its pods, while every other namespace stays open. If you write one, use a CiliumNetworkPolicy rather than a plain NetworkPolicy — the reasons are in Security Policies, and the short version is that a plain one blocks health probes and your pods will restart forever. See also Security Posture.
Step 5: Expose the App (Optional)¶
Create httproute.yaml to route traffic from the apps-gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: my-app
spec:
parentRefs:
- name: apps-gateway
namespace: kube-system
sectionName: https
hostnames:
- "my-app.k8s.wlkr.ch"
rules:
- backendRefs:
- name: my-app-svc
port: 80
See Gateway API for more details.
Step 6: Commit and Push¶
ArgoCD will detect the new application.yaml on the next sync (or immediately if auto-sync is enabled on the parent app) and deploy your workload. The hostname's DNS record and TLS certificate are already handled — external-dns publishes the record from the HTTPRoute, and the Gateway's wildcard certificate covers the name. Neither needs a step of its own.
Step 7: Document It¶
Not optional, and not busywork. An undocumented workload is one you will
rediscover in eighteen months by reading YAML and guessing. Add a page at
docs/workloads/my-app.md and register it in zensical.toml. There is no
Workloads section in the nav yet — the first workload creates it, after
Platform and before Operations:
The build runs with --strict, so an unregistered page fails CI.