Certificates for *.rachelski.internal are issued by cert-manager and chain up to the rachelski internal root CA. No container image trusts that CA out of the box. Without it, an app that calls an internal HTTPS endpoint fails with errors like x509: certificate signed by unknown authority, SSLCertVerificationError or PKIX path building failed.
trust-manager fixes this cluster-wide. It builds one CA bundle and puts a copy of it in every namespace, so your app only has to mount it.
| ConfigMap | rachelski-public-bundle (in every namespace, created automatically, also in new namespaces) |
| Contents | Debian's standard public CAs plus the rachelski internal root CA |
data.ca-certificates.crt |
PEM bundle for OpenSSL, Go, Python, Node, curl and most other runtimes |
binaryData.ca-certificates.jks |
Java KeyStore, password changeit |
Because the bundle includes the public CAs, it replaces the image's own CA store without breaking calls to public sites such as GitHub or Let's Encrypt-secured APIs.
The platform defines the source Bundle (rachelski-public-bundle, in k3s/terraform/cert-manager.tf). Apps never create their own Bundle.
https://*.rachelski.internal URL from a pod. Examples: OIDC discovery and token endpoints on Keycloak, Vault, Gitea, or another app's IngressYou don't need it for plain in-cluster calls to http://<service>.<namespace>.svc.cluster.local, which is the preferred way for one app to call another.
Mount the bundle over /etc/ssl/certs/. Anything that reads the OS default store picks it up with no further config. cert-manager and External Secrets Operator use this pattern here.
spec:
containers:
- name: myapp
volumeMounts:
- name: ca-certificates
mountPath: /etc/ssl/certs/
readOnly: true
volumes:
- name: ca-certificates
configMap:
name: rachelski-public-bundle
defaultMode: 0644
items:
- key: ca-certificates.crt
path: ca-certificates.crt
This works for Debian, Ubuntu and Alpine-based images, Go binaries and distroless images, all of which read /etc/ssl/certs/ca-certificates.crt. It hides whatever else the image had in /etc/ssl/certs/, which is normally fine.
Use this when you'd rather not shadow /etc/ssl/certs, or when the runtime ignores the OS store:
volumeMounts:
- name: ca-certificates
mountPath: /etc/rachelski-ca
readOnly: true
env:
- name: SSL_CERT_FILE # OpenSSL, Go, Ruby, Python ssl module
value: /etc/rachelski-ca/ca-certificates.crt
- name: REQUESTS_CA_BUNDLE # Python requests (uses certifi, ignores the OS store)
value: /etc/rachelski-ca/ca-certificates.crt
- name: NODE_EXTRA_CA_CERTS # Node.js (adds to its built-in list)
value: /etc/rachelski-ca/ca-certificates.crt
- name: CURL_CA_BUNDLE
value: /etc/rachelski-ca/ca-certificates.crt
Set only the variables your runtime uses. Python requests and Node.js need these even with Pattern 1, because neither reads the OS store by default.
RHEL/UBI-based images keep their store at /etc/pki/tls/certs/ca-bundle.crt. Use Pattern 2 for them.
Mount the JKS and pass it as the JVM truststore:
volumeMounts:
- name: ca-certificates
mountPath: /etc/rachelski-ca
readOnly: true
env:
- name: JAVA_TOOL_OPTIONS
value: >-
-Djavax.net.ssl.trustStore=/etc/rachelski-ca/ca-certificates.jks
-Djavax.net.ssl.trustStorePassword=changeit
volumes:
- name: ca-certificates
configMap:
name: rachelski-public-bundle
items:
- key: ca-certificates.jks
path: ca-certificates.jks
The changeit password protects nothing. A truststore only holds public certificates.
Most charts expose extraVolumes / extraVolumeMounts, sometimes named volumes / volumeMounts. Use the same blocks as above in your values file:
extraVolumeMounts:
- name: ca-certificates
mountPath: /etc/ssl/certs/
readOnly: true
extraVolumes:
- name: ca-certificates
configMap:
name: rachelski-public-bundle
defaultMode: 0644
items:
- key: ca-certificates.crt
path: ca-certificates.crt
Some charts have their own CA option, such as caBundle, extraCACerts or tls.trustedCA. Point it at the same ConfigMap and key if one exists.
subPath freezes the file. It never updates. Mount a directory as shown above instead.kubectl rollout restart deploy/myapp.insecureSkipVerify, verify=False, NODE_TLS_REJECT_UNAUTHORIZED=0 or -k are not acceptable fixes.Bundle. The cluster-wide one already reaches every namespace.kubectl run ca-test -n myapp --rm -it --restart=Never --image=curlimages/curl \
--overrides='{"spec":{"containers":[{"name":"ca-test","image":"curlimages/curl",
"args":["-sSI","https://gitea.rachelski.internal"],
"volumeMounts":[{"name":"ca","mountPath":"/etc/ssl/certs"}]}],
"volumes":[{"name":"ca","configMap":{"name":"rachelski-public-bundle"}}]}}'
Without the mount this fails with a certificate error. With it, you get HTTP headers back.