skip to content

Kubernetes Secrets carry a `type` field with values such as `Opaque`, `kubernetes.io/tls` and `kubernetes.io/dockerconfigjson`. What does the type actually change, and can you change it later?

level: middleimportance: should knowfreq 42%

answer

  1. type = shape contract + API-server key validation
  2. tls → tls.crt/tls.key; dockerconfigjson → .dockerconfigjson
  3. Opaque = no validation, the default
  4. type is immutable after creation
  5. kubectl create secret tls|docker-registry|generic

basics

~20 s

The type is a contract: the API server validates that the required keys exist for built-in types, and consumers select Secrets by type. Opaque means no validation. It changes nothing about storage or encryption, and the type is fixed once the Secret is created.

solid answer

~50 s

`type` declares what shape the data has, so both the API server and consumers can rely on it. - **`Opaque`** — the default, arbitrary key/value, no validation. - **`kubernetes.io/tls`** — must contain `tls.crt` and `tls.key`; Ingress controllers, gateways and cert-manager look for exactly this. - **`kubernetes.io/dockerconfigjson`** — must contain `.dockerconfigjson`; this is the type `imagePullSecrets` expects. - **`kubernetes.io/basic-auth`** (`username`/`password`), **`kubernetes.io/ssh-auth`** (`ssh-privatekey`), **`kubernetes.io/service-account-token`** (annotated with the ServiceAccount name; the token controller populates `token`, `ca.crt`, `namespace`). - **`bootstrap.kubernetes.io/token`** for node bootstrap. The API server rejects creation if the required keys for a built-in type are missing, so a typo surfaces at `kubectl apply` rather than as a mysterious Ingress failure. The type is **immutable after creation** — to change it you delete and recreate. What it does not change: storage, encoding, encryption at rest, or how a Pod may consume it. A `kubernetes.io/tls` Secret can be mounted like any other.

code

yaml · 9 lines
yaml
apiVersion: v1
kind: Secret
metadata:
  name: api-tls
  namespace: edge
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...

go deeper

for a junior

Name the common types and what keys each needs — tls.crt/tls.key for TLS, .dockerconfigjson for registry credentials — and use the kubectl create secret helpers.

for a middle

Explain that the type is a validated contract consumers select on, that it changes nothing about storage, and that it is immutable after creation.

for a senior

Use it diagnostically: wrong type explains an Ingress serving a fallback certificate or an ErrImagePull, and typing Secrets correctly turns silent runtime failures into apply-time errors.

for a principal

Treat types as part of the platform's config taxonomy — custom namespaced types for controller-owned material, field-selector-driven tooling, and conventions that make Secret purpose auditable without reading values.

## Type as a contract, not a mechanism Every Secret has a `type` string. It is metadata that declares the *shape* of the payload. Two parties act on it: the API server, which validates required keys for built-in types at admission, and consumers, which use it to decide whether a given Secret is the kind of thing they need. Crucially, the type changes nothing about how the object is stored, encoded, encrypted or delivered to Pods. A `kubernetes.io/tls` Secret is base64 in `data` and plaintext in etcd exactly like an `Opaque` one. Candidates who claim TLS-typed Secrets are "handled more securely" are guessing. ## The built-in types **`Opaque`** — the default when you omit the field. Arbitrary keys, no validation whatsoever. Most application credentials live here. **`kubernetes.io/tls`** — requires `tls.crt` and `tls.key`; `ca.crt` is a conventional optional extra. This is the type Ingress controllers, Gateway API implementations and service meshes look for when a manifest references a TLS Secret, and it is what cert-manager writes. Getting the type wrong here is the classic "my Ingress serves the default certificate" bug: the controller looks for a TLS-typed Secret with those exact key names and quietly falls back when it does not find one. **`kubernetes.io/dockerconfigjson`** — requires the key `.dockerconfigjson` (note the leading dot), whose value is a Docker-style config JSON containing registry hosts and auths. This is the type `imagePullSecrets` consumes; an `Opaque` Secret holding the same JSON will not work. There is a legacy `kubernetes.io/dockercfg` type using the older `.dockercfg` format. **`kubernetes.io/basic-auth`** — `username` and `password`. Used by tooling that wants a conventional shape (some Ingress auth annotations, Argo CD repository credentials). **`kubernetes.io/ssh-auth`** — requires `ssh-privatekey`. Used for git-over-SSH credentials by GitOps controllers and init containers. **`kubernetes.io/service-account-token`** — annotated with `kubernetes.io/service-account.name`; the token controller fills in `token`, `ca.crt` and `namespace`. Modern clusters mount short-lived projected tokens into Pods automatically instead, and these long-lived Secrets are created only on demand — they are the right answer when something outside the cluster needs a token, and the wrong answer for in-cluster workloads. **`bootstrap.kubernetes.io/token`** — kubeadm node-join tokens, living in `kube-system`. You can also invent your own type string, conventionally namespaced like `example.com/my-type`. Custom types get no API-server validation; they exist so *your* controller can filter (`kubectl get secrets --field-selector type=example.com/my-type`) and so humans can tell at a glance what a Secret is for. ## Validation you get for free For the built-in types the API server enforces required keys at write time. Create a `kubernetes.io/tls` Secret with `cert.pem` and `key.pem` instead of `tls.crt`/`tls.key` and the request is rejected outright. This is genuinely useful: it converts a silent runtime misconfiguration (an Ingress serving a fallback certificate, an image pull failing with an unhelpful 401) into a loud failure at apply time. It is also the reason you should type your Secrets properly rather than defaulting everything to `Opaque`. ## Immutability of the type field `type` cannot be changed after creation — the API server rejects updates to it. If you created a registry credential as `Opaque` and pulls are failing, you must delete and recreate it with the right type. Note this is a separate rule from the `immutable: true` flag; the type is fixed on every Secret, immutable or not. ## The kubectl helpers `kubectl create secret` has subcommands per type, and using them is the easy way to get keys and encoding right: - `kubectl create secret generic` → `Opaque` (with `--from-literal`, `--from-file`, `--from-env-file`) - `kubectl create secret tls` → `kubernetes.io/tls` - `kubectl create secret docker-registry` → `kubernetes.io/dockerconfigjson` Each handles the base64 encoding and, for docker-registry, assembles the config JSON correctly — including the base64 `auth` field that people typically get wrong by hand. ## How to use the type in review When reviewing a manifest, the type tells you what the Secret is for without reading its data — which matters because you often cannot read its data. `type: kubernetes.io/tls` in a namespace with no Ingress is worth a question; `Opaque` where a controller expects a specific type is a latent bug. Treat it as documentation the API server enforces.

  • You created a registry credential as `Opaque` with the right JSON inside, and image pulls still fail. What do you do?
    Delete and recreate it as `kubernetes.io/dockerconfigjson` with the data under the `.dockerconfigjson` key. The kubelet's credential lookup selects Secrets by that type, so an `Opaque` Secret is ignored no matter what it contains, and the `type` field cannot be patched on an existing object.
  • Can you define your own Secret type?
    Yes — any string works, conventionally namespaced like `example.com/session-key`. You get no API-server key validation, but your own controller can filter on it with a field selector and humans can see what the Secret is for. Only the `kubernetes.io/*` and `bootstrap.kubernetes.io/*` types carry built-in validation and built-in consumers.

saying these in an interview costs you the question

  • Thinking typed Secrets are stored or encrypted differently from `Opaque` ones.
  • Trying to patch `type` on an existing Secret instead of recreating it.
  • Using key names like `cert.pem`/`key.pem` in a `kubernetes.io/tls` Secret.
  • Forgetting the leading dot in the `.dockerconfigjson` key.
  • Assuming an `Opaque` Secret containing docker auth JSON works as an `imagePullSecrets` target.

context