ConfigMaps and Secrets in Kubernetes

ConfigMaps and Secrets in Kubernetes

Disclaimer: This post was originally written for my company’s blog . I’ve translated it and added a little personal touch for this space.

One of Kubernetes’ key features is the ability to manage configuration data and sensitive information within our cluster both secure and surprisingly easy. Keeping our configuration and secrets organized is one of those small things that saves us headaches later. In this post, we’ll take a closer look at the two Kubernetes objects that make this happen: ConfigMaps and Secrets.

ConfigMaps

ConfigMaps let us store non-sensitive configuration data separately from our applications. Think of them as a way to keep our configuration files, environment variables, and other settings organized and easy to update — without touching our app itself.

Creating a ConfigMap

There are a few ways to create a ConfigMap: using YAML files, the kubectl command line, or directly via the Kubernetes API. Here is a simple YAML example:

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-configmap
data:
  DATABASE_HOST: "example-host"
  DATABASE_USER: "example-user"

We can apply it to our cluster with:

kubectl apply -f example-configmap.yaml

Now our configuration lives in our cluster and is ready for our pods to use. We can inject these values now in a few ways - but in practice, most people use either environment variables or mounted volumes. Let’s hence take a closer look at both.

Using a ConfigMap as Environment Variables

Environment variables are great for passing small sets of simple values to our applications. Here’s how to inject the whole ConfigMap into a pod:

apiVersion: v1
kind: Pod
metadata:
  name: example-app
spec:
  containers:
    - name: example-container
      image: example-image
      envFrom:
        configMapRef:
          name: example-configmap

If we only need specific values, we can inject them individually:

apiVersion: v1
kind: Pod
metadata:
  name: example-app
spec:
  containers:
    - name: example-container
      image: example-image
  env:
    - name: DATABASE_HOST
      valueFrom:
        configMapKeyRef:
          name: example-configmap
          key: DATABASE_HOST

Mounting a ConfigMap as a Volume

For larger or more complex configuration data, mounting ConfigMaps as volumes is usually more convenient than environment variables - and also straightforward:

apiVersion: v1
kind: Pod
metadata:
  name: example-app
spec:
  containers:
    - name: example-container
      image: example-image
      volumeMounts:
        - name: config
          mountPath: etc/example-app-config
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: example-configmap

Secrets

Secrets are a lot like ConfigMaps, but they’re built specifically for sensitive information such as passwords, tokens, or keys. They’re encoded and have strict access controls, which makes them a safer way to handle confidential data in our cluster.

Creating a Secret

Like with ConfigMaps, we can create Secrets via YAML files, kubectl, or the Kubernetes API. Here’s a simple YAML example:

apiVersion: v1
kind: Secret
metadata:
  name: example-secret
type: Opaque
data:
  DATABASE_PASSWORD: ZXhhbXBsZS1wYXNzd29yZA== # base64 encoding of 'example-password'

To encode a value in base64, we can run:

echo -n 'example-password' | base64

Then we deploy the Secret to our cluster:

kubectl apply -f example-secret.yaml

Important: Kubernetes only encodes Secrets in base64 — it doesn’t actually encrypt them. That means if we store Secrets in shared repositories, anyone can decode them easily - not secure at all. Tools like SOPS can provide real encryption and protect our sensitive data. Also, inside Kubernetes, Secrets are stored unencrypted, so we should always restrict cluster access carefully.

Using a Secret

Just like ConfigMaps, Secrets can be injected into our pods either as environment variables or as files on the filesystem.

Using a Secret as an environment variable:

apiVersion: v1
kind: Pod
metadata:
  name: example-app
spec:
  containers:
    - name: example-container
      image: example-image
      env:
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: example-secret
              key: DATABASE_PASSWORD

Mounting a Secret as a volume:

apiVersion: v1
kind: Pod
metadata:
  name: example-app
spec:
  containers:
    - name: example-container
      image: example-image
      volumeMounts:
        - name: secret-volume
          mountPath: etc/secret-volume
          readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: example-secret

Immutability of ConfigMaps & Secrets

So far, all the configurations we’ve looked at can be updated after creation. Sometimes that’s exactly what we want, but other times, unintended changes can break things or cause inconsistencies in our cluster. That’s where immutability comes in.

We can make a ConfigMap or Secret immutable by adding the immutable: true field in the YAML:

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-configmap
data:
  DATABASE_HOST: "example-host"
  DATABASE_USER: "example-user"
immutable: true

Or for Secrets:

apiVersion: v1
kind: Secret
metadata:
  name: example-secret
type: Opaque
data:
  DATABASE_PASSWORD: ZXhhbXBsZS1wYXNzd29yZA==
immutable: true

Once deployed in our cluster, we can’t change them — great for stability, but it also means we need to think ahead. Because if we want to update values, we have to delete and recreate the object, and any pods using it will need a refresh too. Quite some work. 🤪 So, immutability is super handy, but we should consider carefully when and where to use it during development.