Automating TLS Certificates with Cert-Manager
Stop manually renewing certificates. Set up Cert-Manager to automate Let's Encrypt DNS-01 challenges with Cloudflare.
Introduction
In a production cluster, TLS is not optional. But manually generating certificates, creating Kubernetes Secrets, and rotating them every 90 days is toil.
Why Cert-Manager?
- Automation: It detects when a certificate is about to expire and renews it.
- Integration: It works seamlessly with Ingress controllers (Traefik, Nginx).
- DNS-01 support: Obtain wildcard certificates (
*.bluerobin.local) without exposing your cluster to the public internet.
What We’ll Build
- ClusterIssuer: A cluster-wide configuration for Let’s Encrypt.
- External Secrets: A secure way to pass your Cloudflare API token.
- Wildcard Certificate: A single certificate covering
*.bluerobin.local.
Architecture Overview
flowchart TB
subgraph Lifecycle["🔒 Certificate Lifecycle"]
Cert["📜 Certificate Resource"] --> |creates| CSR["📝 CertificateRequest"]
CSR --> Order["📋 ACME Order"]
Order --> Challenge["🏆 Challenge (DNS-01)"]
Challenge --> CF["☁️ Cloudflare DNS API"]
CF --> LE["🔐 Let's Encrypt CA"]
LE --> Secret["🔑 K8s Secret\n(tls.crt + tls.key)"]
end
classDef primary fill:#7c3aed,stroke:#fff,color:#fff
classDef secondary fill:#06b6d4,stroke:#fff,color:#fff
classDef db fill:#f43f5e,stroke:#fff,color:#fff
classDef warning fill:#fbbf24,stroke:#fff,color:#fff
class Lifecycle secondary
Section 1: The Trust Chain
We use the DNS-01 challenge type. This proves ownership of the domain by adding a TXT record to your DNS, rather than hosting a file on a public HTTP server (HTTP-01). This allows us to get certificates for internal services that aren’t reachable from the internet.
# core-infra/security/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@bluerobin.local
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
email: admin@bluerobin.local
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
Section 2: Managing Secrets
We don’t hardcode the Cloudflare Token. We use External Secrets to fetch it from Infisical.
# core-infra/security/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: cloudflare-api-token
namespace: cert-manager
spec:
secretStoreRef:
kind: ClusterSecretStore
name: infisical-store
target:
name: cloudflare-api-token-secret
data:
- secretKey: api-token
remoteRef:
key: CLOUDFLARE_API_TOKEN
Section 3: Requesting the Certificate
With the Issuer ready, we can request our wildcard certificate.
# core-infra/security/certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: bluerobin-wildcard
namespace: traefik
spec:
secretName: bluerobin-wildcard-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- "bluerobin.local"
- "*.bluerobin.local"
Conclusion
Once applied, cert-manager will continually monitor the bluerobin-wildcard-tls secret. 30 days before expiration, it will talk to Cloudflare, prove ownership, fetch a new certificate from Let’s Encrypt, and update the secret. Your applications (and Traefik) will pick up the new certificate automatically.