helm install cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--namespace kube-system \
--set-file caBundle=/path/to/ca-bundle.pemWith monitoring enabled:
helm install cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--namespace kube-system \
--set-file caBundle=/path/to/ca-bundle.pem \
--set serviceMonitor.enabled=true \
--set grafanaDashboard.enabled=true \
--set prometheusRule.enabled=true \
--set podDisruptionBudget.enabled=trueIf you already have a ConfigMap with your CA certificates:
helm install cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--namespace kube-system \
--set caBundleExistingConfigMap=my-existing-ca-configmapBy default the chart expects a ConfigMap key named ca-bundle.pem. If your
existing ConfigMap uses a different key (for example ca.crt or tls.crt),
set caBundleKey to match:
helm install cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--namespace kube-system \
--set caBundleExistingConfigMap=my-existing-ca-configmap \
--set caBundleKey=ca.crtThe file is always projected into the plugin container as
/etc/cainjekt/ca-bundle.pem, so CAINJEKT_CA_FILE does not need to be
changed.
Add the annotation to any pod that should receive CA injection:
apiVersion: v1
kind: Pod
metadata:
name: my-app
annotations:
cainjekt.natron.io/enabled: "true"
spec:
containers:
- name: app
image: my-app:latestThis works for Deployments, StatefulSets, Jobs, CronJobs — any resource that creates pods.
To enable injection for all pods in a namespace, set the label on the namespace:
apiVersion: v1
kind: Namespace
metadata:
name: my-team
labels:
cainjekt.natron.io/enabled: "true"Pods in this namespace will be injected unless they explicitly opt out. Pod-level annotations take precedence over namespace labels. The namespace label is checked via the Kubernetes API (cached for 1 minute).
To skip injection for sidecars or other containers within an injected pod:
annotations:
cainjekt.natron.io/enabled: "true"
cainjekt.natron.io/exclude-containers: "istio-proxy,linkerd-proxy"If your organization uses a different annotation domain:
helm install cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--set annotationPrefix=ca.example.com \
--set-file caBundle=/path/to/ca-bundle.pemPods would then use ca.example.com/enabled: "true".
By default, all processors run. You can filter which processors are active per pod:
annotations:
cainjekt.natron.io/enabled: "true"
# Only run these processors (comma-separated)
cainjekt.natron.io/processors.include: "os-debian,lang-python"
# Or exclude specific ones
cainjekt.natron.io/processors.exclude: "os-fallback,lang-java"Available processors:
OS store (modify system trust stores):
os-debian, os-rhel, os-opensuse, os-alpine, os-arch, os-fallback
Language (set environment variables):
lang-go, lang-java, lang-nodejs, lang-python, lang-ruby
Every injected container has a status file at /etc/cainjekt/status.json:
kubectl exec my-pod -- cat /etc/cainjekt/status.json | jq .Output:
{
"injected": true,
"timestamp": "2026-04-15T08:27:15Z",
"distro": "debian",
"trust_store": "/etc/ssl/certs/ca-certificates.crt",
"processors": [
{ "name": "os-debian", "category": "os", "applicable": true },
{ "name": "lang-python", "category": "language", "applicable": true }
]
}# Debian/Ubuntu
kubectl exec my-pod -- cat /etc/ssl/certs/ca-certificates.crt | grep -c "BEGIN CERTIFICATE"
# RHEL/Fedora
kubectl exec my-pod -- cat /etc/pki/tls/certs/ca-bundle.crt | grep -c "BEGIN CERTIFICATE"
# Individual CA file
kubectl exec my-pod -- ls /usr/local/share/ca-certificates/cainjekt.crt# Java
kubectl exec my-pod -- env | grep JAVA_TOOL_OPTIONS
# Node.js
kubectl exec my-pod -- env | grep NODE_EXTRA_CA_CERTS
# Python
kubectl exec my-pod -- env | grep SSL_CERT_FILEkubectl logs -n kube-system -l app.kubernetes.io/name=cainjekt -fkubectl port-forward -n kube-system daemonset/cainjekt 9443:9443
curl http://localhost:9443/metrics | grep cainjekt_Update the ConfigMap with the new CA bundle:
kubectl create configmap cainjekt-ca-bundle \
--from-file=ca-bundle.pem=/path/to/new-ca-bundle.pem \
--namespace=kube-system \
--dry-run=client -o yaml | kubectl apply -f -Or with Helm:
helm upgrade cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--namespace kube-system \
--set-file caBundle=/path/to/new-ca-bundle.pemNew containers will automatically get the updated CA. Existing containers keep their current CA until they are restarted.
- Check if injection was enabled:
kubectl get pod <name> -o jsonpath='{.metadata.annotations}' - Check the status file:
kubectl exec <pod> -- cat /etc/cainjekt/status.json - Check if the trust store was modified:
kubectl exec <pod> -- cat /etc/ssl/certs/ca-certificates.crt | tail -20 - Check cainjekt logs:
kubectl logs -n kube-system -l app.kubernetes.io/name=cainjekt
The container has a read-only root filesystem. OS trust stores cannot be modified, but language processors still set env vars. If you're using curl or wget (which read the OS trust store), they won't trust the CA. Use language-specific tools instead, or make the rootfs writable.
Check the distro detection: kubectl exec <pod> -- cat /etc/os-release. If the file doesn't exist (distroless images), the fallback processor tries common paths but may not find the right one. Language env vars will still work.
Check CAINJEKT_LOG_LEVEL=debug for detailed logs:
helm upgrade cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--set logLevel=debugCommon causes: invalid CA bundle (check PEM format), NRI socket issues (check containerd version), insufficient permissions.
If pods that opt in via a namespace label (not a pod annotation/label) stop
being injected — typically a fixed time after a cainjekt pod (re)start — check
cainjekt_ns_lookup_errors_total. A nonzero value means the Kubernetes API
lookup for the namespace label is failing, most often because the service
account token expired. cainjekt re-reads the rotated token automatically, so a
sustained error rate points at RBAC or API reachability instead. Pods that opt in
via a pod annotation or label are unaffected (no API call), so moving the
opt-in to the pod template is a robust workaround for environments with very
short token lifetimes.
| Scenario | Works? | Details |
|---|---|---|
| Standard Linux distros | Yes | Debian, Ubuntu, Alpine, RHEL, Fedora, Arch, openSUSE |
| Java apps (JDK 18+) | Yes | JAVA_TOOL_OPTIONS with -Djavax.net.ssl.trustStoreType=PEM |
| Java apps (JDK < 18) | No | PEM trust store type not supported; would need JKS keystore manipulation |
| Node.js apps | Yes | NODE_EXTRA_CA_CERTS |
| Python apps | Yes | SSL_CERT_FILE, REQUESTS_CA_BUNDLE |
| Ruby apps | Yes | SSL_CERT_FILE |
| Go apps | Yes | SSL_CERT_FILE + OS trust store |
| Static Go binaries | Partial | OS trust store works if paths exist; SSL_CERT_FILE requires Go binary in image |
| Distroless images | Partial | Language env vars work; OS trust store may not |
| Read-only rootfs | Partial | Language env vars work; OS trust store cannot be modified |
| Scratch images (no shell) | No | No OS trust store, no language binaries to detect |
| .NET apps | Untested | May work via OS trust store on Linux |
| Custom TLS implementations | No | Apps that don't read system CAs or env vars |