ConfigMaps and Secrets: Managing Configuration in Kubernetes

posted Originally published at medium.com 4 min read

When you're building applications for Kubernetes, one of the first challenges you'll face is: "Where do I put my configuration data?" Should database connection strings live in your Docker image? What about API keys? How do you handle different configurations for development, staging, and production environments?

This is where Kubernetes ConfigMaps and Secrets come to the rescue. In this post, I would like to explore, with you, these essential Kubernetes resources that separate your application code from its configuration, making your deployments more secure, flexible, and maintainable.

The Problem: Hardcoded Configuration

Before we dive into the solution, let's understand the problem. Consider this simple Node.js application:

// app.js - DON'T DO THIS
const express = require('express');
const mysql = require('mysql2');

const app = express();

// Hardcoded configuration - BAD PRACTICE
const config = {
  port: 3000,
  database: {
    host: 'mysql-prod.company.com',
    user: 'admin',
    password: 'super-secret-password',
    database: 'production_db'
  },
  apiKey: 'sk-1234567890abcdef'
};

const connection = mysql.createConnection(config.database);
app.listen(config.port);

This approach has several critical problems:

  1. Security Risk: Sensitive data like passwords and API keys are embedded in your code
  2. Environment Rigidity: The same image can't work across different environments
  3. Secret Exposure: Passwords end up in your Git repository and Docker images
  4. Deployment Complexity: Changing configuration requires rebuilding and redeploying your entire application

The Kubernetes Solution: Externalizing Configuration

Kubernetes provides two primary resources for managing configuration:

  • ConfigMaps: For non-sensitive configuration data
  • Secrets: For sensitive information like passwords, tokens, and keys

Both resources follow the same core principle: separate configuration from application code.

ConfigMaps: Your Configuration Data Store

What is a ConfigMap?

A ConfigMap is a Kubernetes API object that stores configuration data as key-value pairs. Think of it as a dictionary or hash map that your applications can reference at runtime.

Creating ConfigMaps

There are several ways to create ConfigMaps. Let's explore each method:

Method 1: Using kubectl with Literal Values

kubectl create configmap app-config \
  --from-literal=PORT=3000 \
  --from-literal=DATABASE_HOST=mysql.default.svc.cluster.local \
  --from-literal=DATABASE_NAME=myapp \
  --from-literal=LOG_LEVEL=info

Method 2: From a Configuration File

First, create a configuration file:

PORT=3000
DATABASE_HOST=mysql.default.svc.cluster.local
DATABASE_NAME=myapp
LOG_LEVEL=info
DEBUG_MODE=false

Then create the ConfigMap:

kubectl create configmap app-config --from-file=app.properties

Method 3: Using YAML Manifests (Recommended)

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  PORT: "3000"
  DATABASE_HOST: "mysql.default.svc.cluster.local"
  DATABASE_NAME: "myapp"
  LOG_LEVEL: "info"
  DEBUG_MODE: "false"
  # You can also include entire configuration files
  app.properties: |
    PORT=3000
    DATABASE_HOST=mysql.default.svc.cluster.local
    DATABASE_NAME=myapp
    LOG_LEVEL=info
    DEBUG_MODE=false

Apply the ConfigMap:

kubectl apply -f configmap.yaml

Consuming ConfigMaps in Your Applications

There are three primary ways to use ConfigMaps in your pods:

1. Environment Variables

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        env:
        # Individual environment variables
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: PORT
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DATABASE_HOST
        # Or load all ConfigMap keys as environment variables
        envFrom:
        - configMapRef:
            name: app-config

2. Volume Mounts

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        volumeMounts:
        - name: config-volume
          mountPath: /etc/config
          readOnly: true
      volumes:
      - name: config-volume
        configMap:
          name: app-config

With this approach, your configuration files will be available at /etc/config/ inside the container.

3. Command Line Arguments

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        command: ["./myapp"]
        args:
        - "--port=$(PORT)"
        - "--database-host=$(DATABASE_HOST)"
        env:
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: PORT
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DATABASE_HOST

Secrets: Protecting Sensitive Data

What is a Secret?

A Secret is similar to a ConfigMap, but specifically designed for sensitive data. Kubernetes stores Secrets in base64 encoding and provides additional security features like:

  • Secrets are stored in etcd in encrypted form (when encryption at rest is enabled)
  • Secrets are only sent to nodes that have pods requiring them
  • Secrets are stored in memory (tmpfs) and never written to disk
  • Access can be controlled with RBAC policies

Types of Secrets

Kubernetes supports several Secret types:

  1. Opaque: Generic secrets for arbitrary user data (default)
  2. kubernetes.io/dockerconfigjson: Docker registry credentials
  3. kubernetes.io/tls: TLS certificates and keys
  4. kubernetes.io/service-account-token: Service account tokens

Creating Secrets

Method 1: Using kubectl with Literal Values

kubectl create secret generic app-secrets \
  --from-literal=DATABASE_PASSWORD=super-secret-password \
  --from-literal=API_KEY=sk-1234567890abcdef \
  --from-literal=JWT_SECRET=my-jwt-secret-key

Method 2: From Files

# Create files with sensitive data
echo -n 'super-secret-password' > db-password.txt
echo -n 'sk-1234567890abcdef' > api-key.txt

kubectl create secret generic app-secrets \
  --from-file=DATABASE_PASSWORD=db-password.txt \
  --from-file=API_KEY=api-key.txt

# Clean up the files
rm db-password.txt api-key.txt

Method 3: Using YAML Manifests

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: default
type: Opaque
data:
  # Values must be base64 encoded
  DATABASE_PASSWORD: c3VwZXItc2VjcmV0LXBhc3N3b3Jk  # super-secret-password
  API_KEY: c2stMTIzNDU2Nzg5MGFiY2RlZg==  # sk-1234567890abcdef
  JWT_SECRET: bXktand0LXNlY3JldC1rZXk=  # my-jwt-secret-key

Pro Tip: Use the stringData field to avoid manual base64 encoding:

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  DATABASE_PASSWORD: super-secret-password
  API_KEY: sk-1234567890abcdef
  JWT_SECRET: my-jwt-secret-key

Consuming Secrets

Secrets are consumed in the same ways as ConfigMaps:

Environment Variables

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        env:
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DATABASE_PASSWORD
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: API_KEY
        envFrom:
        - secretRef:
            name: app-secrets

Volume Mounts

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        volumeMounts:
        - name: secret-volume
          mountPath: /etc/secrets
          readOnly: true
      volumes:
      - name: secret-volume
        secret:
          secretName: app-secrets
          defaultMode: 0400  # Read-only for owner only

In the next post we’ll see how to use secrets and ConfigMap in a complete application setup.

If you read this far, tweet to the author to show them you care. Tweet a Thanks
0 votes
0 votes

More Posts

Controlling Memory and CPU Utilization for Go Apps in Kubernetes: GOMEMLIMIT and GOMAXPROCS

stjam - Jul 6

How Containerization and Kubernetes Revolutionize Software Deployment Efficiency

Aditya Pratap Bhuyan - Mar 7

Kubernetes Predictions Were Wrong

Steve Fenton - Jul 8

One giant Kubernetes cluster for everything

Nicolas Fränkel - Mar 20

Building a Kubernetes Operator in Go with Kube-Shift

Mohit Nagaraj 1 - Jul 20
chevron_left