Skip to main content
RunBook Academy

KubernetesXX · ConfigurationConfiguration

Configuration anti-patterns — what not to put in a ConfigMap

Advanced⏱ ~17 minkubectlkubeadm

What you'll learn

  • Identify the workload classes where ConfigMaps are wrong
  • Choose the right Kubernetes object for each configuration class
  • Recognise the "config in image" anti-pattern and its replacement
  • Reason about the 1 MiB size limit and alternatives

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

ConfigMaps are sometimes treated as a “store any data” object. They are not. Each anti-pattern below describes a workload class that ConfigMaps are wrong for and the right alternative. Operators who reach for the right object have cleaner configurations, fewer security incidents, and smaller ConfigMaps.

Anti-pattern 1: sensitive data in a ConfigMap

# WRONG: credentials in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: db-credentials
data:
  database_url: postgres://app:s3cr3t@db.prod/data

ConfigMaps are stored in etcd plaintext. Anyone with read access to the namespace can kubectl get configmap -o yaml and see the credentials. RBAC prevents this for unprivileged users, but a privileged user (cluster-admin, a compromised ServiceAccount, etcd read access) can read everything.

The right answer: Secret (with encryption at rest) or an external secret manager (Vault, AWS Secrets Manager, External Secrets Operator).

Anti-pattern 2: configuration embedded in the image

# WRONG: config in the image
FROM python:3.12
COPY ./config/ /etc/app/config/
COPY ./scripts/ /etc/app/scripts/
COPY app.py /app.py
CMD ["/app.py"]

The configuration is baked into the image. Every config change requires a new image and a rollout. The deployment pipeline is forced to handle configuration changes.

The right answer: image contains only the application binary; configuration comes from ConfigMaps, Secrets, or external stores. A new version rolls via a new image tag; a config change rolls via a ConfigMap change.

flowchart TB
    A["Image<br/>contains only<br/>app binary"] --> B[Pod]
    B --> C["ConfigMap<br/>mounted as files"]
    B --> D["Secret<br/>mounted as files"]
    B --> E["External store<br/>fetched at startup"]

Anti-pattern 3: ConfigMaps as a replacement for PVCs

# WRONG: 10 MiB file in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: large-config
data:
  schema.json: |
    { ... 10 MiB of JSON ... }

ConfigMaps are bounded at 1 MiB. Large files (schemas, templates, large datasets, machine-learning models) belong in a PVC, an object store, or an init container that downloads from an external source.

flowchart TB
    A[Need to ship a large file?] --> B{Updates at runtime?}
    B -->|yes| C["PVC with RWX<br/>or CSI volume"]
    B -->|no| D["Init container<br/>fetches at startup"]
    D --> E["Object store / git LFS<br/>downloaded once"]

Anti-pattern 4: ConfigMap as a code-deployment mechanism

# WRONG: shell scripts in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-scripts
data:
  setup.sh: |
    #!/bin/bash
    curl https://init.internal/setup
  migrate.sh: |
    #!/bin/bash
    psql -f migration.sql

Scripts in a ConfigMap are a code-deployment mechanism disguised as configuration. The version of setup.sh is not tracked in git; the “release” is a ConfigMap applied manually. This is a maintenance nightmare.

The right answer: scripts go in the image (versioned, tagged, scanned); ConfigMaps hold configuration values, not code.

Anti-pattern 5: ConfigMap as a feature-flag store

# WRONG: many feature flags in one ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: feature-flags
data:
  checkout_v2: "true"
  new_search: "false"
  beta_dashboard: "true"
  # ... 50 more flags

A ConfigMap is not a feature-flag system. It has no auditability, no targeting, no A/B testing, no percentage rollouts. A proper feature-flag system (LaunchDarkly, Split, Unleash, ConfigCat) provides these.

flowchart TB
    A[Need feature flags?] --> B{A/B test or<br/>targeted rollout?}
    B -->|yes| C["Feature-flag service<br/>LaunchDarkly / Split"]
    B -->|no, simple on/off| D[ConfigMap is fine]

For a handful of flags without targeting, a ConfigMap is acceptable. For real feature-flag operations, use a service.

Anti-pattern 6: ConfigMap with personally identifiable information

# WRONG: user data in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: users
data:
  user-1.json: |
    { "name": "Alice", "email": "alice@example.com" }

PII does not belong in ConfigMaps. ConfigMaps are cluster-wide read for any user with the right RBAC. PII belongs in a database, encrypted at rest, with strict access controls.

Anti-pattern 7: one ConfigMap per consumer

# WRONG: 50 ConfigMaps for one app
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-log-config
data: { log_level: info }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-db-config
data: { db_url: postgres://... }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-features
data: { checkout_v2: "true" }

Three ConfigMaps for one application is three objects to manage, three places to update. A single ConfigMap per application is the standard pattern:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  log_level: info
  db_url: postgres://...
  checkout_v2: "true"

The exception: configuration that is consumed by multiple applications with different lifecycles (e.g., a cluster-wide “shared config”) can be its own object.

The decision matrix

flowchart TB
    A[What are you configuring?] --> B{Sensitive?}
    B -->|yes| C["Secret<br/>+ encryption at rest"]
    B -->|no| D{Large file?}
    D -->|yes, persistent| E["PVC or<br/>CSI volume"]
    D -->|no| F{Code?}
    F -->|yes| G[Container image]
    F -->|no| H{Runtime-tunable?}
    H -->|yes| I["ConfigMap<br/>mutable"]
    H -->|no, baked-in| J["ConfigMap<br/>immutable"]
Data classRight object
Non-sensitive, runtime-tunableConfigMap (mutable)
Non-sensitive, release-bakedConfigMap (immutable)
Sensitive credentialsSecret + encryption at rest, or external secret manager
Large files (schemas, models)PVC, object store, init container
Code / scriptsContainer image
Feature flags with targetingFeature-flag service

Quiz

Knowledge check · 4 questions

  1. Q1. Which is a correct alternative for storing a 10 MiB JSON schema that does not fit in a ConfigMap?

  2. Q2. Storing shell scripts in a ConfigMap and running them via kubectl exec is a valid deployment mechanism for production code.

  3. Q3. Your team stores a database password in a ConfigMap because it is base64-encoded and the team thought base64 was encryption. Diagnose.

    ConfigMap db-credentials with password czNjcjN0 (base64 of s3cr3t). The team assumed the encoding was encryption. A privileged user reads the ConfigMap; the password is plaintext.

  4. Q4. Name three anti-patterns for using ConfigMaps and the right alternatives for each.

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Default to Secret for credentials. A ConfigMap holding a password is a security incident waiting to happen.
  • Default to PVC for large files. A ConfigMap with a 1 MiB JSON is a violation of the size limit.
  • Default to image for code. A ConfigMap with shell scripts is a deployment mechanism in disguise.
  • Audit ConfigMaps regularly. A query for configmaps where size > 100KiB catches oversized ConfigMaps; a query for configmaps with sensitive data (via a content audit) catches credential leaks.
  • Document the immutability and consumption pattern. A ConfigMap without a comment about how it is consumed is half a manifest.

The ConfigMap is the right object for non-sensitive runtime configuration. The discipline is in choosing it when it is right and the alternative when it is not.