Certificate governance without the ceremony: cert-manager + Venafi on k3s
December 2022 — because PKI shouldn’t require a JIRA ticket and three approvals
There’s a version of certificate management that lives in large enterprises. You file a request. A team reviews it. Someone checks a compliance box. Five days later, a certificate lands in your inbox as a .pfx file. You paste it into a UI. Two weeks from now it expires and nobody notices until a monitoring alert fires at 2am.
I don’t want that in my home cluster. I also don’t want the other extreme: --set cert-manager.enable-insecure-http=true scattered across Helm values because certificates were “too complicated to figure out right now.”
The right answer is cert-manager with actual governance behind it. And it turns out Venafi — the enterprise PKI platform — has a free cloud tier called VaaS that makes this genuinely accessible for self-hosters.
The problem I was solving
I run k3s at home. Across the various services I host — monitoring, Git, dashboards, internal tools — I needed:
- TLS everywhere, including internal services
- Certificates that renew automatically before anyone notices
- A policy layer so I can’t accidentally issue a wildcard for
*.google.comon a misconfigured Issuer
Let’s Encrypt covers the public-facing use case well. But for internal domains and for learning how enterprise PKI actually works, I wanted something more.
The moving parts
Your laptop / cluster node
│
├── k3s cluster
│ ├── cert-manager (Helm)
│ │ └── VenafiIssuer (custom resource)
│ │ └── talks to VaaS API
│ └── your services
│ └── Certificate (custom resource) → TLS Secret
│
└── Venafi as a Service (vaas.venafi.com, free tier)
├── Application (logical grouping)
├── Certificate Issuing Template (policy)
└── API key
cert-manager handles the Kubernetes side: watches Certificate resources, talks to the configured Issuer, stores the resulting TLS key+cert in a Secret. Venafi handles the policy side: only approved templates can issue certificates, you get audit logs, you can enforce SANs and key types.
Setting it up
1. Install cert-manager
export KUBECONFIG=[YOUR_KUBECONFIG]
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
--create-namespace \
--namespace cert-manager \
--version v1.10.1 \
--set installCRDs=true
Wait for all three pods to be running:
kubectl get pods -n cert-manager
# cert-manager-xxxxx 1/1 Running
# cert-manager-cainjector-xxxxx 1/1 Running
# cert-manager-webhook-xxxxx 1/1 Running
2. Create a VaaS account and get an API key
Sign up at vaas.venafi.com. It’s free.
Once in:
- Create an Application — this is the logical container for your certificates. Name it something like
homelab. - Create a Certificate Issuing Template — this is your policy. Set allowed domains, key type (RSA 2048 or ECDSA P-256), validity period, etc.
- Retrieve your API key from your profile settings.
3. Create the API secret in Kubernetes
# vaas-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: vaas-secret
namespace: cert-manager
type: Opaque
stringData:
apikey: "YOUR-VAAS-API-KEY-HERE"
kubectl apply -f vaas-secret.yaml
4. Create the VenafiIssuer
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: venafi-vaas
namespace: default
spec:
venafi:
zone: "homelab\\Default" # Application\Template in VaaS
cloud:
apiTokenSecretRef:
name: vaas-secret
key: apikey
kubectl apply -f issuer.yaml
kubectl describe issuer venafi-vaas
# Conditions: Ready=True
5. Request a certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-service-tls
namespace: default
spec:
secretName: my-service-tls-secret
duration: 2160h # 90 days
renewBefore: 360h # renew 15 days before expiry
dnsNames:
- my-service.homelab.local
issuerRef:
name: venafi-vaas
kind: Issuer
A few seconds later:
kubectl get certificate my-service-tls
# NAME READY SECRET AGE
# my-service-tls True my-service-tls-secret 12s
cert-manager watches this certificate, renews it automatically before renewBefore, and keeps the Secret up to date. Your service references the Secret. You never touch it again.
What you actually get
The Certificate resource is the contract. You declare what you want — the domains, the duration, where to store it. cert-manager + Venafi deliver it and keep it fresh. The Venafi console gives you an audit trail: every issuance, renewal, and revocation is logged, with who requested it and via which template.
For a home cluster this might seem like overkill. It’s not. It’s how you learn the workflow that runs in every regulated enterprise environment, and you learn it on hardware you can break without consequence.
The asciinema below walks through the full setup from a blank k3s node:
The part that trips people up: the zone string
The zone field in the Issuer spec is Application\Template. The backslash needs to be escaped in YAML, so it becomes "homelab\\Default". If you get 401 Unauthorized or zone not found errors, check this first.
Also: the VaaS free tier has a certificate limit. For a home cluster running a handful of services, you won’t hit it. But keep it in mind if you plan to auto-issue certificates for ephemeral resources at high frequency.
Why not just Let’s Encrypt?
You could. For internet-facing services, Let’s Encrypt is the right answer. cert-manager supports it natively and it requires no external account.
But Let’s Encrypt won’t issue for internal hostnames like my-service.homelab.local. It requires public DNS validation. For anything internal, you need either a self-signed CA (which means distributing trust anchors to every client) or a hosted PKI like Venafi.
More importantly: this is the architecture that runs in banks, hospitals, and government systems. Learning it at home, where failure means a broken dashboard rather than a compliance violation, is how you build the muscle memory to understand it when it matters.
