Skip to content

Kubernetes ConfigMaps — Complete Implementation Guide

DodaTech Updated 2026-06-28 9 min read

In this tutorial, you will learn about Kubernetes ConfigMaps. We cover key concepts, practical examples, and best practices to help you master this topic.

Kubernetes ConfigMaps and Secrets provide a decoupled configuration layer that separates environment-specific settings from container images, enabling immutable deployments across multiple environments.

What You'll Learn

By the end of this tutorial, you will know how to create and manage Kubernetes ConfigMaps and Secrets, mount them as environment variables or volumes, and implement a configuration strategy for containerized applications.

Why It Matters

ConfigMaps and Secrets are the standard way to manage configuration in Kubernetes. They enable immutable infrastructure by keeping configuration outside container images, support environment-specific settings, and provide secure storage for sensitive data.

Real-World Use

DodaTech runs 200 Microservices on Kubernetes. Each service gets its configuration via ConfigMaps created from a GitOps Repository, with Secrets managed through Sealed Secrets that are decrypted at deploy time.

Kubernetes ConfigMaps Learning Path

flowchart LR
  A[Configuration Hierarchy] --> B[Kubernetes ConfigMaps]
  B --> C[Creating ConfigMaps]
  B --> D[Mounting ConfigMaps]
  B --> E[Secrets]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Creating ConfigMaps

ConfigMaps can be created from literals, files, or entire directories.

# Create from literals
kubectl create configmap app-config \
  --from-literal=APP_NAME=myapp \
  --from-literal=LOG_LEVEL=info \
  --from-literal=PORT=8080

# Create from a file
kubectl create configmap app-config \
  --from-file=config.yaml

# Create from a directory
kubectl create configmap app-config \
  --from-file=./config-dir/

# Create from .env file
kubectl create configmap app-config \
  --from-env-file=.env
// Node.js application reading ConfigMap values
const config = {
  appName: process.env.APP_NAME,
  logLevel: process.env.LOG_LEVEL,
  port: parseInt(process.env.PORT || "8080")
};

console.log("Application config from ConfigMap:");
console.log("  Name:", config.appName);
console.log("  Log level:", config.logLevel);
console.log("  Port:", config.port);
// Application config from ConfigMap:
//   Name: myapp
//   Log level: info
//   Port: 8080

ConfigMap YAML Definition

ConfigMaps can be defined declaratively in YAML for version control.

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  APP_NAME: myapp
  LOG_LEVEL: info
  PORT: "8080"
  config.yaml: |
    database:
      host: postgres.example.com
      port: 5432
      poolSize: 10
    cache:
      ttl: 300
      maxEntries: 1000
// Deployment using ConfigMap
// apiVersion: apps/v1
// kind: Deployment
// metadata:
//   name: myapp
// spec:
//   template:
//     spec:
//       containers:
//       - name: myapp
//         envFrom:
//         - configMapRef:
//             name: app-config
//         volumeMounts:
//         - name: config-volume
//           mountPath: /etc/config

console.log("ConfigMap-based deployment configured");

Mounting ConfigMaps as Volumes

ConfigMaps can be mounted as files for applications that read configuration from files.

const fs = require("fs");
const path = require("path");

class ConfigMapFileReader {
  constructor(configPath = "/etc/config") {
    this.configPath = configPath;
  }

  readConfig(filename) {
    const filePath = path.join(this.configPath, filename);

    if (!fs.existsSync(filePath)) {
      console.warn(`Config file not found: ${filePath}`);
      return null;
    }

    const content = fs.readFileSync(filePath, "utf8");
    console.log(`Read config from ${filePath}`);
    return content;
  }

  parseYamlConfig() {
    const content = this.readConfig("config.yaml");
    if (!content) return {};

    // Simple YAML-like parser for demonstration
    const lines = content.split("\n");
    const config = {};
    let currentSection = null;

    for (const line of lines) {
      if (line.endsWith(":")) {
        currentSection = line.slice(0, -1).trim();
        config[currentSection] = {};
      } else if (currentSection && line.includes(":")) {
        const [key, value] = line.split(":").map(s => s.trim());
        config[currentSection][key] = value;
      }
    }

    return config;
  }
}

const reader = new ConfigMapFileReader();
const databaseConfig = reader.parseYamlConfig();
console.log("Database host:", databaseConfig.database?.host);
// Read config from /etc/config/config.yaml
// Database host: postgres.example.com

Kubernetes Secrets

Secrets are similar to ConfigMaps but store sensitive data encoded in base64.

# Create a secret from literals
kubectl create secret generic app-secrets \
  --from-literal=DB_PASSWORD=s3cr3t \
  --from-literal=API_KEY=abc123xyz

# Create a secret from a file
kubectl create secret generic app-secrets \
  --from-file=./secrets.env

# View secret (base64 encoded)
kubectl get secret app-secrets -o yaml
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  DB_PASSWORD: czNjcjN0
  API_KEY: YWJjMTIzeHl6
class SecretManager {
  constructor() {
    this.secrets = new Map();
  }

  addSecret(key, value) {
    this.secrets.set(key, value);
  }

  getSecret(key) {
    if (!this.secrets.has(key)) {
      throw new Error(`Secret ${key} not found`);
    }
    return this.secrets.get(key);
  }

  validateRequired(requiredKeys) {
    const missing = requiredKeys.filter(k => !this.secrets.has(k));

    if (missing.length > 0) {
      console.error("Missing required secrets:", missing.join(", "));
      process.exit(1);
    }

    console.log("All required secrets present");
  }
}

const secrets = new SecretManager();
secrets.addSecret("DB_PASSWORD", "s3cr3t");
secrets.addSecret("API_KEY", "abc123xyz");
secrets.validateRequired(["DB_PASSWORD", "API_KEY", "JWT_SECRET"]);
// Missing required secrets: JWT_SECRET

ConfigMap Update Strategy

ConfigMaps can be updated without restarting pods, but existing pods won't see changes unless they watch for updates.

class ConfigWatcher {
  constructor(configPath = "/etc/config") {
    this.configPath = configPath;
    this.currentConfig = {};
    this.watchers = [];
  }

  watchConfig(filename, callback) {
    const filePath = `${this.configPath}/${filename}`;

    // Poll for changes every 30 seconds
    setInterval(() => {
      const fs = require("fs");
      const content = fs.readFileSync(filePath, "utf8");

      if (content !== this.currentConfig[filename]) {
        this.currentConfig[filename] = content;
        console.log(`Config changed: ${filename}`);
        callback(content);
      }
    }, 30000);

    console.log(`Watching ${filePath} for changes`);
  }

  rollingRestartRequired() {
    console.log("ConfigMap updated -- rolling restart recommended");
    console.log("Run: kubectl rollout restart deployment/myapp");
  }
}

const watcher = new ConfigWatcher();
watcher.watchConfig("config.yaml", (content) => {
  console.log("Configuration updated, applying changes");
});
// Watching /etc/config/config.yaml for changes
// ConfigMap updated -- rolling restart recommended
// Run: kubectl rollout restart deployment/myapp

Sealed Secrets for GitOps

Sealed Secrets encrypt Secrets so they can be stored safely in Git repositories.

# Install sealed-secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml

# Encrypt a secret
kubectl create secret generic app-secrets \
  --dry-run=client \
  --from-literal=DB_PASSWORD=s3cr3t \
  -o json | \
  kubeseal --format yaml > sealed-secret.yaml
# sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: app-secrets
spec:
  encryptedData:
    DB_PASSWORD: AgBy3i4E...
  template:
    type: Opaque
    metadata:
      name: app-secrets
class SealedSecretManager {
  constructor() {
    this.sealedSecrets = new Map();
  }

  seal(name, data) {
    // In production, this calls kubeseal via the Kubernetes API
    this.sealedSecrets.set(name, {
      encrypted: true,
      data: Buffer.from(JSON.stringify(data)).toString("base64"),
      createdAt: new Date().toISOString()
    });

    console.log(`Sealed secret: ${name}`);
    return this.sealedSecrets.get(name);
  }

  unseal(name) {
    const sealed = this.sealedSecrets.get(name);
    if (!sealed) {
      throw new Error(`Sealed secret not found: ${name}`);
    }

    // The controller decrypts this automatically
    console.log(`Unsealed secret: ${name}`);
    return JSON.parse(Buffer.from(sealed.data, "base64").toString());
  }
}

const sealed = new SealedSecretManager();
sealed.seal("app-secrets", { DB_PASSWORD: "s3cr3t" });
console.log("Secret sealed for Git storage");

Best Practices

Organize ConfigMaps by environment and application for maintainability.

class ConfigMapOrganization {
  static namingConventions() {
    return {
      "app-config": "Application-wide configuration",
      "app-config-dev": "Development overrides",
      "app-config-prod": "Production overrides",
      "logging-config": "Logging settings",
      "monitoring-config": "Monitoring and alerting"
    };
  }

  static sizeLimits() {
    return {
      maxConfigMapSize: "1 MB per ConfigMap",
      maxSecretsPerNamespace: "1000",
      maxConfigMapsPerNamespace: "1000",
      recommendation: "Split large configs into multiple ConfigMaps"
    };
  }

  static immutableConfigMaps() {
    console.log("ConfigMaps can be marked as immutable for performance:");
    console.log("  metadata:");
    console.log("    annotations:");
    console.log("      kubernetes.io/immutable: 'true'");
    console.log("Immutable ConfigMaps cannot be updated -- create a new one instead");
  }
}

ConfigMapOrganization.immutableConfigMaps();

Common Mistakes

  1. Storing secrets in ConfigMaps -- ConfigMaps store data in plain text. Always use Secrets for sensitive data like passwords and API keys.

  2. Not base64-decoding Secrets -- Kubernetes Secrets are base64-encoded, not encrypted. Applications must decode the values before use.

  3. Hardcoding ConfigMap names -- ConfigMap names can vary between environments. Make the ConfigMap name configurable via an environment variable.

  4. Not handling ConfigMap updates -- Pods don't automatically see ConfigMap changes. Use volume mounts with subPath or implement a watch mechanism.

  5. Exceeding ConfigMap size limits -- ConfigMaps are limited to 1 MB. Store large configuration data in a database or object storage instead.

  6. Committing raw Secrets to Git -- Never commit unencrypted Secrets to version control. Use Sealed Secrets, Helm Secrets, or Mozilla SOPS.

Practice Questions

  1. What is the difference between a ConfigMap and a Secret in Kubernetes? Both store configuration data, but Secrets are intended for sensitive data. Secrets are base64-encoded and can be encrypted at rest with encryption configuration.

  2. How do you mount a ConfigMap as a file in a pod? Add a volume of type configMap in the pod spec, then volumeMount it at the desired path. Each key in the ConfigMap becomes a file in the mount path.

  3. What happens when you update a ConfigMap that is mounted as a volume? Existing pods see updated files within minutes when mounted as volumes (subPath mounts don't update). Environment variable mounts are not updated until the pod restarts.

  4. Challenge: Implement a ConfigMap controller that watches for changes and triggers rolling updates.

class ConfigMapController {
  constructor(k8sClient) {
    this.client = k8sClient;
    this.watchedConfigMaps = new Map();
  }

  async watchConfigMap(namespace, name, deploymentName) {
    const watch = new this.client.Watch();
    const path = `/api/v1/namespaces/${namespace}/configmaps/${name}`;

    watch.watch(path, {}, (type, obj) => {
      if (type === "MODIFIED") {
        console.log(`ConfigMap ${name} modified. Restarting ${deploymentName}`);
        this.rolloutRestart(namespace, deploymentName);
      }
    });

    console.log(`Watching ConfigMap ${name} for changes`);
  }

  async rolloutRestart(namespace, deployment) {
    const appsClient = new this.client.AppsV1Api();
    await appsClient.patchNamespacedDeployment(deployment, namespace, {
      spec: {
        template: {
          metadata: {
            annotations: {
              "kubectl.kubernetes.io/restartedAt": new Date().toISOString()
            }
          }
        }
      }
    });

    console.log(`Rolling restart triggered for ${deployment}`);
  }
}

const controller = new ConfigMapController({});
controller.watchConfigMap("production", "app-config", "myapp");
// Watching ConfigMap app-config for changes

FAQ

What is the maximum size of a ConfigMap?

ConfigMaps are limited to 1 MB in total. For larger configuration data, use a database, object storage, or mount a volume containing the files.

Can I update a ConfigMap without restarting pods?

Yes and no. Volume-mounted ConfigMaps are updated automatically within minutes (not with subPath). Environment variable mounts require a pod restart to pick up changes.

Are Kubernetes Secrets secure?

Secrets are base64-encoded, not encrypted by default. Enable encryption at rest with a KMS provider. Use Sealed Secrets or External Secrets Operator for GitOps workflows.

How do I handle multiple environments with ConfigMaps?

Create separate ConfigMaps per environment (app-config-dev, app-config-staging, app-config-prod) or use Kustomize overlays to patch ConfigMaps per environment.

What is the difference between env and envFrom in Kubernetes?

env sets individual environment variables. envFrom imports all keys from a ConfigMap or Secret as environment variables. envFrom cannot select individual keys.

Can I use ConfigMaps for binary data?

ConfigMaps are for text data. For binary data, use Secrets with binaryData field or store binary files in a volume.

Mini Project

Build a Kubernetes Configuration Management system that creates environment-specific ConfigMaps, mounts them as volumes, watches for changes, and implements a rolling update strategy.

class KubernetesConfigManager {
  constructor(options = {}) {
    this.appName = options.appName || "myapp";
    this.environment = options.environment || "development";
    this.configPath = options.configPath || "/etc/config";
  }

  generateConfigMapYaml() {
    return {
      apiVersion: "v1",
      kind: "ConfigMap",
      metadata: {
        name: `${this.appName}-config-${this.environment}`,
        namespace: this.environment,
        labels: {
          app: this.appName,
          environment: this.environment,
          managedBy: "ConfigManager"
        }
      },
      data: {
        "app.yaml": this.generateAppConfig(),
        "logging.yaml": this.generateLoggingConfig()
      }
    };
  }

  generateAppConfig() {
    const config = {
      server: {
        port: this.environment === "production" ? 8080 : 3000,
        host: "0.0.0.0",
        workers: this.environment === "production" ? 4 : 1
      },
      database: {
        poolSize: this.environment === "production" ? 20 : 5,
        timeout: 5000
      }
    };

    return Object.entries(config).map(([section, values]) => {
      return `${section}:\n  ${Object.entries(values).map(([k, v]) => `${k}: ${v}`).join("\n  ")}`;
    }).join("\n");
  }

  generateLoggingConfig() {
    return `level: ${this.environment === "production" ? "warn" : "debug"}
format: json
output: stdout
`;
  }

  static async deployConfigMap(yaml) {
    console.log(`Deploying ConfigMap: ${yaml.metadata.name}`);
    console.log(`  Namespace: ${yaml.metadata.namespace}`);
    console.log(`  Data keys: ${Object.keys(yaml.data).join(", ")}`);
    console.log("ConfigMap ready for kubectl apply");
  }
}

const mgr = new KubernetesConfigManager({
  appName: "api-gateway",
  environment: "production"
});

const configMap = mgr.generateConfigMapYaml();
KubernetesConfigManager.deployConfigMap(configMap);
// Deploying ConfigMap: api-gateway-config-production
//   Namespace: production
//   Data keys: app.yaml, logging.yaml
// ConfigMap ready for kubectl apply

What's Next

Now that you understand Kubernetes ConfigMaps, learn about feature flags for dynamic configuration. Then explore configuration pipeline patterns for managing config through your deployment pipeline.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro