KubernetesXX · ConfigurationConfiguration
Configuration anti-patterns — what not to put in a ConfigMap
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
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 class | Right object |
|---|---|
| Non-sensitive, runtime-tunable | ConfigMap (mutable) |
| Non-sensitive, release-baked | ConfigMap (immutable) |
| Sensitive credentials | Secret + encryption at rest, or external secret manager |
| Large files (schemas, models) | PVC, object store, init container |
| Code / scripts | Container image |
| Feature flags with targeting | Feature-flag service |
Quiz
Knowledge check · 4 questions
Q1. Which is a correct alternative for storing a 10 MiB JSON schema that does not fit in a ConfigMap?
Q2. Storing shell scripts in a ConfigMap and running them via kubectl exec is a valid deployment mechanism for production code.
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.
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 > 100KiBcatches oversized ConfigMaps; a query forconfigmaps 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.