Minimal new deployment
Example
Every app needs three resources in a single YAML file under apps/:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: sfz # or: events, luca
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: example/myapp:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: sfz
spec:
selector:
app: myapp
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: sfz
annotations:
kubernetes.io/ingress.class: traefik
cert-manager.io/cluster-issuer: ca-issuer
external-dns.kubernetes.io/target: 217.78.162.68
spec:
rules:
- host: myapp.aalen.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
name: http
tls:
- secretName: myapp-tls
hosts:
- myapp.aalen.space
Then add myapp.yaml to apps/kustomization.yaml.
Ingress annotations
Required on every Ingress:
kubernetes.io/ingress.class: traefik — tells Kubernetes which ingress controller should pick up this resource. Without it, Traefik ignores the Ingress entirely.
cert-manager.io/cluster-issuer: ca-issuer — triggers cert-manager to request a TLS certificate from the internal CA and store it in the secret named by tls[].secretName. Without this, the TLS block has no certificate to serve.
external-dns.kubernetes.io/target: 217.78.162.68 — tells external-dns which IP to point the DNS record at. external-dns uses this instead of the Service IP because Traefik sits in front via MetalLB at that address. Without it, a DNS record is still created, but it points to the internal cluster IP which is unreachable from outside.
Optional:
traefik.ingress.kubernetes.io/router.middlewares: <namespace>-<name>@kubernetescrd — attaches a Traefik Middleware to the router. The name format is <namespace>-<middlewarename>@kubernetescrd. Used for WebSocket headers, CORS, or custom response headers (see components/adc-config/middleware.yaml).
nginx.ingress.kubernetes.io/proxy-body-size: 100M — raises Traefik's body size limit for uploads. Despite the nginx. prefix, Traefik reads these annotations too. Default is 1m, which will cause 413 errors for file uploads.
Secrets
Secrets are encrypted with SOPS + age and stored as *.enc.yaml alongside the manifests. Flux decrypts them at apply time via the sops-age secret. Only data and stringData fields are encrypted.
To encrypt a new secret:
sops --encrypt --in-place apps/secrets/myapp.enc.yaml
Reference it in the app with secretRef or secretKeyRef. Add the file to apps/secrets/kustomization.yaml.
Dependency updates
Renovate keeps Helm chart and container image versions up to date automatically. Minor and patch updates are auto-merged; major updates require manual review and are opened as merge requests.