Managing ConfigMaps and Secrets in Kubernetes



In a Kubernetes environment, managing application configuration and secrets is crucial for smooth, secure operations. We often encounter situations where we need to store non-sensitive data—like configuration files, environment variables, or command-line arguments—and sensitive data—like passwords, API tokens, or SSL certificates. Kubernetes provides two native resources to handle these scenarios: ConfigMaps for non-sensitive data and Secrets for sensitive data.

In this chapter, we’ll explore the differences between these two resources, look at the latest best practices, and walk through step-by-step commands to create, use, and update them. By the end, we will have a firm grasp on how to manage our cluster configurations more effectively and securely.

Why ConfigMaps and Secrets Matter

In a microservices environment, applications are typically containerized and ephemeral. We don’t want to bake environment-specific details (like database URLs, external service endpoints, or debug flags) directly into our container images. Doing so can lead to inflexible images and increased maintenance overhead. Instead, we can externalize these details with ConfigMaps or Secrets, which are dynamically injected into Pods at runtime.

What are ConfigMaps?

A ConfigMap is a key-value store for non-sensitive configuration data. We can store entire configuration files, single variables, or even partial files. ConfigMaps allow us to update configurations without rebuilding our images, giving us a flexible and modular approach to application deployment.

Creating a ConfigMap

We can create ConfigMaps from literal values, files, or directories. Here are a few examples using kubectl:

From Literal Values:

$ kubectl create configmap my-config \
  --from-literal=APP_MODE=development \
  --from-literal=APP_DEBUG=true

Output

configmap/my-config created

In this command, we’ve created a ConfigMap named my-config with two key-value pairs: APP_MODE and APP_DEBUG.

From a File:

Suppose we have a file named app-config.yaml with the following contents:

app:
  name: demo-application
  environment: development
  debug: "true"
  version: "1.0.0"

database:
  host: db-service
  port: 5432
  user: demo-user
  password: change-me

We can create a ConfigMap from this file:

$ kubectl create configmap app-config \
  --from-file=app-config.yaml

Output

configmap/app-config created

Now, our ConfigMap app-config contains the contents of app-config.yaml.

From a Directory:

If we have multiple configuration files in a directory called config/, we can run:

$ kubectl create configmap my-config-dir \
  --from-file=config/

Output

configmap/my-config-dir created

To verify, run:

$ kubectl get configmaps

Output

NAME               DATA   AGE
app-config         1      2m22s
kube-root-ca.crt   1      50m
my-config          2      4m15s
my-config-dir      2      37s
$ kubectl describe configmap app-config 

Output

Name:         app-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
app-config.yaml:
----
app:
  name: demo-application
  environment: development
  debug: "true"
  version: "1.0.0"

database:
  host: db-service
  port: 5432
  user: demo-user
  password: change-me

BinaryData: ==== Events: <none>

Injecting the ConfigMap into a Pod

We will now create a Pod that references the ConfigMap as a mounted volume, so our application can read the file at runtime. Below is a sample Pod YAML (pod-with-configmap.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo
spec:
  containers:
  - name: demo-container
    image: nginx:latest
    volumeMounts:
    - name: config-volume
      mountPath: /etc/app-config
  volumes:
  - name: config-volume
    configMap:
      name: app-config
      items:
      - key: app-config.yaml
        path: app-config.yaml

Apply the Pod:

$ kubectl apply -f pod-with-configmap.yaml 

Output

pod/configmap-demo created

We can then exec into the Pod to confirm the file is mounted:

$ kubectl exec -it configmap-demo -- cat /etc/app-config/app-config.yaml

Output

app:
  name: demo-application
  environment: development
  debug: "true"
  version: "1.0.0"

database:
  host: db-service
  port: 5432
  user: demo-user
  password: change-me

As we can see the content is originally the same as the one placed in app-config.yaml, meaning everything is working as expected.

Updating ConfigMaps

We can update our app-config.yaml file by editing it and then re-creating or applying the ConfigMap:

$ kubectl create configmap app-config \
  --from-file=app-config.yaml \
  --dry-run=client -o yaml | kubectl apply -f -

Output

configmap/app-config configured

This pattern ensures we apply changes to the existing ConfigMap rather than creating a new one from scratch. Keep in mind that existing Pods using this ConfigMap won’t automatically reload the changes unless we restart or roll them out again.

What are Secrets in Kubernetes?

A Secret in Kubernetes is a resource used to store and manage sensitive information. By default, Kubernetes stores Secrets in base64-encoded form, which isn’t fully secure on its own but prevents casual exposure in logs or CLI outputs. For more advanced security, we can enable Encryption at Rest or integrate with external vault solutions like HashiCorp Vault or AWS KMS.

Creating a Secret

We have several ways to create Secrets:

1. From Literal Values:

We can create a Secret directly from literal key-value pairs. For example:

$ kubectl create secret generic my-secret \
  --from-literal=username=admin \
  --from-literal=password=secretpass

Output

secret/my-secret created

In this example, we will end up with a Secret named my-secret that has base64-encoded values for username and password.

2. From a File:

We can also create a Secret from one or more files on our local machine. This is especially common for TLS certificates:

$ kubectl create secret generic tls-secret \
  --from-file=tls.crt=./mycert.crt \
  --from-file=tls.key=./mykey.key

Output

secret/tls-secret created

Here, tls-secret will contain two keys: tls.crt and tls.key, each storing the contents of the respective files.

3. From a Manifest:

We can define a Secret directly in a YAML file if we prefer to keep it in version control (though we should be cautious about storing sensitive data in plain text). For instance:

apiVersion: v1
kind: Secret
metadata:
  name: my-manual-secret
type: Opaque
data:
  username: YWRtaW4=
  password: c2VjcmV0cGFzcw==

In this example, username and password are already base64-encoded values (admin and secretpass respectively). We can apply this manifest with:

$ kubectl apply -f my-manual-secret.yaml

Output

secret/my-manual-secret created

We can verify the secret was created:

$ kubectl get secrets
Output:
db-secret          Opaque   2      5m14s
my-manual-secret   Opaque   2      20m
my-secret          Opaque   2      25m
tls-secret         Opaque   2      22m

Using Secrets in a Pod

We often mount Secrets into our Pods, either as environment variables or as files, much like ConfigMaps. Below is a Pod snippet that demonstrates using Secrets as environment variables:

apiVersion: v1
kind: Pod
metadata:
  name: secret-demo
spec:
  containers:
  - name: demo-container
    image: nginx:latest
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password

Apply the File:

$ kubectl apply -f secret-demo.yaml

Output

pod/secret-demo created

When the Pod starts, it will have DB_USERNAME and DB_PASSWORD set from the db-secret resource. We can confirm:

$ kubectl exec -it secret-demo -- env | grep DB_
Output:
DB_PASSWORD=secretpass
DB_USERNAME=admin

Updating a Secret

Similarly, if we need to rotate our credentials:

$ kubectl create secret generic db-secret \
  --from-literal=username=demo-user \
  --from-literal=password=newSecretPass \
  --dry-run=client -o yaml | kubectl apply -f -

Output

secret/db-secret configured

Best Practices for ConfigMaps and Secrets

Enable Encryption at Rest

For an added layer of security, enable Encryption at Rest in Kubernetes so that Secrets are encrypted on disk.

Least Privilege Access

Use RBAC rules to restrict who can read or modify ConfigMaps and Secrets. This helps reduce the risk of accidental or malicious changes.

Separate Sensitive and Non-Sensitive Data

If you mix sensitive data into a ConfigMap, it can be exposed inadvertently. Keep them in a Secret if there’s any doubt.

Rolling Updates and Managing Changes

One of the challenges with ConfigMaps and Secrets is that changes don’t automatically trigger a rolling update of our Pods. We have two main approaches:

Manual Rolling Update

After updating a ConfigMap or Secret, we can manually roll our Deployment by running:

 kubectl rollout restart deployment 
 

This ensures our Pods restart and pick up the new configuration.

Hash Annotation Trick

Another approach is to annotate our Pod or Deployment spec with a hash of the ConfigMap/Secret data. Whenever the data changes, the hash changes, prompting a new rollout. This technique can be automated with a GitOps workflow or a small script.

Conclusion

ConfigMaps and Secrets are core building blocks for managing configuration and sensitive data in Kubernetes. By using them properly, we can decouple our application code from environment-specific settings, reduce the risk of exposing sensitive information, and keep our containers as generic as possible.

By following these steps and guidelines, we will have a more secure and flexible Kubernetes environment, enabling us to focus on delivering robust applications without worrying about configuration drift or credential leakage.

Advertisements