Skip to content

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.