Skip to content

cert-manager

Issues and renews TLS certificates for every public hostname in the cluster. No one has ever copy-pasted a .pem file into this repo — every TLS Secret you see (argocd-server-tls, grafana-tls, auth-tls, the app's TLS Secret) is generated by cert-manager from a Let's Encrypt HTTP-01 challenge.

Installed by k8s/envs/dev/sys/cert-manager/application.yaml. The ClusterIssuer is defined in k8s/envs/dev/sys/cert-manager/cluster-issuer.yaml.

Chart and source

Field Value
Helm chart cert-manager
Repository https://charts.jetstack.io
Version v1.14.0
installCRDs true (passed as a Helm parameter)
Destination namespace cert-manager (created by Argo CD)
Argo CD Application cert-manager

installCRDs: true matters — it ships the Certificate, Issuer, ClusterIssuer, CertificateRequest, Order, and Challenge CRDs. The rest of the cluster (especially the per-app Ingresses) depends on these existing before sync.

ClusterIssuer

One issuer, cluster-wide, for production Let's Encrypt:

# k8s/envs/dev/sys/cert-manager/cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@tomoda.life
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: traefik

Three things to note:

  • Production endpoint. This is the real acme-v02.api.letsencrypt.org — certificates are real, and so are the Let's Encrypt rate limits (50 certs per registered domain per week, 5 duplicate certs per week). There is no staging issuer installed, so don't burn through the budget by creating and deleting certs in a loop.
  • HTTP-01 over Traefik. When cert-manager issues a cert it writes a temporary Ingress solving the ACME challenge. That challenge Ingress declares class: traefik, which means the solver only works because Traefik is the cluster's ingress controller. If Traefik is down, no new certs can be issued.
  • No DNS-01. Wildcard certs aren't supported here. Every Ingress that wants TLS needs to list its exact host(s) — which is fine, every host we use is a literal subdomain of tomoda.life.

How a workload requests a cert

A per-app Ingress declares two things:

metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - argo-app.tomoda.life
      secretName: argocd-server-tls

cert-manager's Ingress shim notices the annotation, creates a Certificate resource targeting argocd-server-tls, runs the HTTP-01 challenge, and writes the resulting cert+key into argocd-server-tls in the same namespace. Traefik picks the Secret up automatically and serves it for that host.

Concrete examples in this repo:

  • argocd-server-tls for argo-app.tomoda.life (k8s/envs/dev/sys/argocd-ingress/ingress.yaml)
  • grafana-tls for grafana.tomoda.life (k8s/envs/dev/sys/monitoring/values.yaml)
  • auth-tls for auth.tomoda.life (k8s/envs/dev/sys/oauth2-proxy/application.yaml)

Auto-renewal

cert-manager renews certs roughly 30 days before expiry (Let's Encrypt issues 90-day certs). Renewal is the same HTTP-01 flow as the initial issuance, so it has the same Traefik dependency. No human action is required — but if you see a cert in Status: False for Ready, check the Order and Challenge resources in that namespace.

kubectl get certificate -A
kubectl describe certificate <name> -n <namespace>
kubectl get order,challenge -A

Operational notes

  • Removing a TLS Secret manually triggers re-issuance — cert-manager will notice the Secret is gone and re-run the challenge. Useful for forcing a refresh, but burns one against the rate limit.
  • CRDs are installed via the chart, not separately. If you ever uninstall the cert-manager Application, the CRDs go with it, and every Certificate resource in the cluster disappears in the same sync — followed by every TLS Secret. Don't do this without a plan.
  • No external secrets here. cert-manager doesn't authenticate to anything except Let's Encrypt over HTTP, so there are no GCP or AWS credentials involved.

For the broader TLS story (Cloudflare edge TLS, certificate inventory, what hostnames terminate where), see Security → TLS.