18.5 Secret Types: Opaque, TLS, dockerconfigjson, service-account-token
Right, so you’ve got your ConfigMaps for your regular, non-secret configuration. That’s great. But sometimes you have things you’d rather not broadcast to the entire cluster, like API keys, database passwords, or the fact that you still use abc123 as a password. Enter Secrets. They’re like ConfigMaps, but with a thin, almost performative veil of secrecy. Don’t get too excited; by default, they’re just base64-encoded, not encrypted. Anyone with kubectl get secrets -o yaml can decode them in about five seconds. It’s the Kubernetes equivalent of hiding your house key under the doormat. We’ll get to making them actually secret later, but first, let’s talk about the different types, because Kubernetes, in its infinite wisdom, decided there shouldn’t just be one kind.
The Opaque Secret (The “Catch-All”)
This is the default type and your go-to for most things that aren’t the other, more specific types. The name is hilariously ironic because it’s the least opaque of them all—it’s literally just a labeled key-value store. You create one from a file, from a literal value, or from an env file. Here’s how you do it from files, which is the sane way:
# Save your super-secret credentials to files (add these to your .gitignore, PLEASE)
echo -n 'admin' > ./username.txt
echo -n 'S3cr3tP@ssw0rd!' > ./password.txt
# Create the secret from the files
kubectl create secret generic my-db-creds \
--from-file=./username.txt \
--from-file=./password.txt
Now, if you run kubectl get secret my-db-creds -o yaml, you’ll see the data field with your two keys (username.txt and password.txt) and their base64-encoded values. To use it in a pod, you mount it as a volume or expose it as an environment variable, just like a ConfigMap. The crucial part is that kubelet on the node will decode the base64 and write the plain text value into the container’s filesystem. The secrecy is only during transport and storage in etcd (if you configure encryption, which, again, you should).
The kubernetes.io/tls Secret
This one is for TLS certificates and keys. It’s slightly less of a farce than Opaque because it at least expects specific keys: tls.crt and tls.key. Kubernetes will validate that the data you provide for these keys is actually a valid certificate and key pair. It’s a minimal check, but it’s something.
# Assuming you have a key and cert file
kubectl create secret tls my-tls-secret \
--cert=path/to/cert.crt \
--key=path/to/key.key
Why use this over a generic Opaque secret? Mainly for two reasons: 1) It’s self-documenting. Anyone (or any tool like an ingress controller) that sees the type knows exactly what to expect inside. 2) Some controllers, most notably the one for Ingress resources, will only look for secrets of this type. They’ll ignore a generic secret with the same data.
The kubernetes.io/dockerconfigjson Secret
This is a weirdly specific but incredibly common one. It holds the credentials for pulling images from a private container registry (Docker Hub, Google Container Registry, etc.). You could craft the JSON config file by hand and stuff it into an Opaque secret, but please don’t. Kubernetes gives you a one-liner:
kubectl create secret docker-registry my-registry-creds \
--docker-server=registry.mycompany.com \
--docker-username=myuser \
--docker-password='Y0urP@ss' \
--docker-email=me@mycompany.com
This command creates a secret of type kubernetes.io/dockerconfigjson with a single data key, .dockerconfigjson, which contains the entire auth configuration in the exact JSON format that the Docker client and the kubelet expect. When you create a Pod that needs to pull from a private registry, you add imagePullSecrets: [name: my-registry-creds] to its spec. The kubelet uses this credential to authenticate before it pulls your image.
The kubernetes.io/service-account-token Secret
This is the oddball. You don’t typically create these yourself. Kubernetes creates them automatically for each ServiceAccount. Their purpose is to store the bearer token that a pod can use to authenticate to the Kubernetes API server. If you’ve ever run kubectl exec ... and looked inside a pod’s /var/run/secrets/kubernetes.io/serviceaccount/, you’ve seen one of these in action.
The data inside includes token (the actual JWT), ca.crt (the cluster’s certificate authority bundle), and namespace (the namespace the pod is running in). The magic is that the Kubernetes client libraries inside your pod are often pre-configured to look for this exact secret in this exact location, making API access from within the cluster almost effortless.
Here’s the kicker, and it’s a big one: these tokens do not expire. Ever. Well, almost ever. This is one of those questionable design choices I mentioned. A token for the default service account in a namespace, if somehow leaked, is a permanent key to the kingdom of whatever permissions that service account has. This is why the best practice is to never use the default service account for your workloads. Create a dedicated service account with minimal permissions, and Kubernetes will automatically generate a new, equally-never-expiring token secret for it. The solution to the permanent token problem is to use external tools like cert-manager for things like service account token volume projection, but that’s a story for another chapter. For now, just know that these secrets are powerful, automatically managed, and dangerously long-lived. Treat them with the respect they deserve, which is a lot.