Skip to main content

ConfigMaps & Secrets: Configure Python in Kubernetes

ConfigMaps and Secrets are Kubernetes resources for managing application configuration and sensitive data. A ConfigMap stores non-sensitive configuration (database URLs, feature flags, log levels) as key-value pairs or files. A Secret stores sensitive data (passwords, API keys, credentials) and is encrypted at rest (in etcd) and in transit. Rather than baking configuration into your Python container image, you externalize it to ConfigMaps and Secrets, allowing the same image to run in development, staging, and production with different configurations.

What Is a ConfigMap and How Do You Create One?

A ConfigMap is a Kubernetes resource that holds non-sensitive key-value configuration data. You create a ConfigMap via YAML or the kubectl create configmap command. Here's a ConfigMap for a Python Flask application:

apiVersion: v1
kind: ConfigMap
metadata:
name: python-app-config
namespace: default
data:
DATABASE_URL: "postgresql://user:pass@db-svc:5432/appdb"
LOG_LEVEL: "INFO"
DEBUG: "false"
API_TIMEOUT: "30"

Or create it from the command line:

kubectl create configmap python-app-config \
--from-literal=DATABASE_URL="postgresql://user:pass@db-svc:5432/appdb" \
--from-literal=LOG_LEVEL=INFO \
--from-literal=DEBUG=false

You can also create a ConfigMap from a file:

# Create a config file
cat > app.conf << EOF
[database]
host=db-svc
port=5432
name=appdb
EOF

# Create ConfigMap from file
kubectl create configmap python-app-config --from-file=app.conf

Injecting ConfigMap Data into Python Pods

There are two ways to inject ConfigMap data into a pod: as environment variables or as mounted files (volumes).

Injecting as Environment Variables

apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: app
image: my-registry/python-app:1.0.0
envFrom:
- configMapRef:
name: python-app-config
ports:
- containerPort: 8000

The envFrom field injects all key-value pairs from the ConfigMap as environment variables. Your Python code accesses them via os.environ:

import os

database_url = os.getenv("DATABASE_URL")
log_level = os.getenv("LOG_LEVEL", "DEBUG") # Default to DEBUG if not set

print(f"Connecting to {database_url}")
print(f"Log level: {log_level}")

Injecting as Mounted Files

For larger configuration files, mount the ConfigMap as a volume:

apiVersion: v1
kind: Deployment
metadata:
name: python-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: app
image: my-registry/python-app:1.0.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
ports:
- containerPort: 8000
volumes:
- name: config-volume
configMap:
name: python-app-config
items:
- key: app.conf
path: app.conf

Your Python code reads the file:

import configparser

config = configparser.ConfigParser()
config.read("/etc/config/app.conf")

db_host = config.get("database", "host")
db_port = config.get("database", "port")

Understanding Kubernetes Secrets and Secure Data Storage

Secrets store sensitive data. Kubernetes encrypts Secrets at rest in etcd (though this must be enabled—many clusters do not enable encryption by default). Secrets come in several types: Opaque (generic key-value), kubernetes.io/basic-auth, kubernetes.io/ssh-auth, tls (certificates), and others.

Create a Secret for database credentials:

kubectl create secret generic python-db-secret \
--from-literal=database-user=appuser \
--from-literal=database-password=SecureP@ssw0rd \
--from-literal=api-key=sk-1234567890abcdef

Or via YAML (note: values are base64-encoded for readability, not encryption—this is why Secrets are not truly secure without etcd encryption):

apiVersion: v1
kind: Secret
metadata:
name: python-db-secret
type: Opaque
data:
database-user: YXBwdXNlcg== # base64 of "appuser"
database-password: U2VjdXJlUEBzc3cwcmQ= # base64 of "SecureP@ssw0rd"
api-key: c2stMTIzNDU2Nzg5MGFiY2RlZg== # base64 of "sk-1234567890abcdef"

Injecting Secrets into Python Pods

Inject Secrets the same way as ConfigMaps—as environment variables or volumes:

apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: app
image: my-registry/python-app:1.0.0
env:
- name: DATABASE_USER
valueFrom:
secretKeyRef:
name: python-db-secret
key: database-user
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: python-db-secret
key: database-password
- name: API_KEY
valueFrom:
secretKeyRef:
name: python-db-secret
key: api-key
ports:
- containerPort: 8000

Your Python application reads these environment variables:

import os
import psycopg2

db_user = os.getenv("DATABASE_USER")
db_password = os.getenv("DATABASE_PASSWORD")
api_key = os.getenv("API_KEY")

# Connect to database
conn = psycopg2.connect(
host="db-svc",
port=5432,
database="appdb",
user=db_user,
password=db_password
)

Best Practices: ConfigMaps vs Secrets

AspectConfigMapSecret
Data TypeNon-sensitive configurationSensitive credentials, keys
Size Limit1 MB1 MB
EncryptionAt rest only if enabledYes (if etcd encryption enabled)
Use CaseLog levels, feature flags, URLsPasswords, API keys, certificates
AuditNot audited by defaultShould be audited for security

Never store credentials in ConfigMaps. Always use Secrets, and ensure your Kubernetes cluster has etcd encryption enabled. Even better, integrate with external secret management tools (Vault, AWS Secrets Manager) via operators like External Secrets Operator.

Mounting Secrets as Files for TLS Certificates

For TLS certificates, mount a Secret as a volume:

apiVersion: v1
kind: Secret
metadata:
name: python-app-tls
type: tls
data:
tls.crt: <base64-encoded-certificate>
tls.key: <base64-encoded-key>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app-deployment
spec:
replicas: 1
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: app
image: my-registry/python-app:1.0.0
volumeMounts:
- name: tls-certs
mountPath: /etc/tls
readOnly: true
ports:
- containerPort: 8443
volumes:
- name: tls-certs
secret:
secretName: python-app-tls
items:
- key: tls.crt
path: cert.pem
- key: tls.key
path: key.pem

Your Python app uses the certificates for HTTPS:

import ssl
from flask import Flask

app = Flask(__name__)

if __name__ == "__main__":
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(
certfile="/etc/tls/cert.pem",
keyfile="/etc/tls/key.pem"
)
app.run(host="0.0.0.0", port=8443, ssl_context=ssl_context)

Key Takeaways

  • ConfigMaps store non-sensitive configuration; Secrets store sensitive credentials and keys.
  • Inject ConfigMaps and Secrets as environment variables (for simple key-value) or mounted volumes (for files or certificates).
  • Kubernetes encodes Secret values in base64, but encryption at rest requires enabling etcd encryption.
  • Never store credentials in ConfigMaps or container images; always use Secrets.
  • For production, consider external secret management (Vault, AWS Secrets Manager) via operators.

Frequently Asked Questions

Is base64 encoding in Secrets secure?

No, base64 is encoding, not encryption. Anyone with kubectl access can decode Secret values with kubectl get secret python-db-secret -o yaml. You must enable etcd encryption and restrict RBAC access to Secrets. Even better, use external secret managers.

Can I update a ConfigMap without redeploying pods?

Yes, but pods do not automatically reload ConfigMap data. If you inject via envFrom, you must restart pods for changes to take effect. If you mount as a volume, Kubernetes updates the mounted files automatically (usually within ~1 minute), and your application code should watch for changes or be reloaded.

How do I use Docker image secrets (private registries) in Kubernetes?

Create an image pull secret and reference it in the Deployment:

kubectl create secret docker-registry registry-secret \
--docker-server=my-registry.azurecr.io \
--docker-username=<username> \
--docker-password=<password>

Then in your Deployment spec:

spec:
imagePullSecrets:
- name: registry-secret

What happens if a Secret is deleted while pods are running?

If a Secret is mounted as a volume and deleted, Kubernetes removes the volume from new pods but existing pods retain the mounted files (read from the node's local cache). This is a graceful degradation but also a security risk—deleted Secrets may linger in memory or logs.

Should I version my ConfigMaps and Secrets?

Yes, a best practice is to version ConfigMaps by name (e.g., python-app-config-v1, python-app-config-v2) and update Deployments to reference the new version. This allows you to roll back to previous configurations if needed.

Further Reading