
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Hardcoded credentials and environment-specific logic buried in source code remain the primary cause of deployment failures and security breaches I encounter during audits. True portability requires strict adherence to 12-Factor Config Across Runtimes, where configuration is strictly separated from code and injected dynamically at startup. This guide provides the concrete implementation patterns you need to enforce this separation consistently across Kubernetes, Docker, and traditional Linux servers without breaking existing workflows.
How do you implement 12-Factor Config Across Runtimes correctly?
The third factor of the Twelve-Factor App methodology states that configuration storing environment-specific details must be strictly separated from code. In practice, this means your application should not know whether it is running in a Kathmandu data center or an AWS us-east-1 region. The binary or container image remains identical; only the injected configuration changes. Achieving true 12-Factor Config Across Runtimes requires moving beyond simple .env files in development and establishing a rigorous injection contract for production.
A common mistake I see in Nepal-based startups and global enterprises alike is treating configuration as a file management problem rather than an interface problem. Your application defines an interface (required environment variables), and the runtime fulfills that contract. If the runtime fails to provide a required variable, the application must crash immediately at startup, not fail silently hours later. This "fail-fast" behavior is non-negotiable for reliable operations.
Defining the Configuration Contract
Before touching Kubernetes manifests or systemd units, define exactly what your application needs. Document every environment variable, its type, default value, and whether it is required. This specification serves as the binding agreement between developers and operators.
# Example: Configuration Contract for Payment Service
# PAYMENT_GATEWAY_URL (string, required) - Base URL for payment processor
# PAYMENT_API_KEY (secret, required) - Auth token, never log this
# DB_POOL_SIZE (integer, optional, default: 10) - Max database connections
# LOG_LEVEL (enum: debug|info|warn|error, optional, default: info) Validate this contract at boot. Libraries like python-decouple, node-env-var, or Go's viper can enforce types and presence checks before the first request is served. For deeper validation strategies, refer to Kubernetes secrets management done right which covers safe handling of sensitive inputs.
How does Kubernetes handle 12-Factor configuration injection?
Kubernetes provides native primitives for 12-Factor Config Across Runtimes through ConfigMaps and Secrets. These objects decouple configuration data from pod specifications, allowing you to update settings without rebuilding images. However, the mechanism of injection matters significantly for security and observability.
Environment Variables vs. Volume Mounts
Pure 12-Factor advocates prefer environment variables because they are universally supported and language-agnostic. However, environments have limitations: they cannot hold large structured data, they are visible in process listings, and updating them requires a pod restart. Volume mounts solve the size and update problems but violate strict 12-Factor purity since the app reads files instead of ENV.
| Criterion | Environment Variables | Volume Mounts |
|---|---|---|
| 12-Factor Purity | High (Strict adherence) | Medium (File-based override) |
| Hot Reload | No (Requires restart) | Yes (SubPath excepted) |
| Secret Safety | Low (Visible in /proc) | Higher (File permissions) |
| Structured Data | Poor (Flat key-value only) | Excellent (JSON/YAML files) |
| Size Limit | ~128KB total env block | MBs to GBs |
In my experience managing SOC 2 compliant infrastructure, the pragmatic approach is hybrid: use environment variables for simple scalars and connection strings, but mount complex configurations or high-sensitivity secrets as files with restricted permissions. Always enable encryption at rest for etcd if storing Secrets in Kubernetes.
Injecting Values Safely
Use the envFrom field to inject entire ConfigMaps cleanly, avoiding verbose individual env entries. For secrets, reference them explicitly to maintain audit trails.
apiVersion: v1
kind: Pod
metadata:
name: payment-service
spec:
containers:
- name: app
image: registry.example.com/payment-svc:v2.4.1
envFrom:
- configMapRef:
name: payment-config
env:
- name: PAYMENT_API_KEY
valueFrom:
secretKeyRef:
name: payment-secrets
key: api-key This pattern ensures that non-sensitive defaults live in version-controlled ConfigMaps while credentials remain in managed Secrets. For teams using GitOps, tools like ArgoCD can sync these resources declaratively, as detailed in setting up GitOps with ArgoCD.
How do you manage 12-Factor config on bare metal and VMs?
Not every workload runs in Kubernetes. Many Nepali government systems and legacy enterprise applications still operate on standalone Ubuntu or RHEL servers. You can still achieve 12-Factor Config Across Runtimes without containers by leveraging systemd's powerful environment management features. Avoid placing .env files in application directories where they might be accidentally committed or exposed via web servers.
Systemd Drop-in Files
Use systemd drop-in directories to override service configurations without modifying the main unit file. This keeps vendor-provided units pristine while allowing site-specific configuration.
# Create override directory
sudo mkdir -p /etc/systemd/system/payment-service.service.d/
# Create environment override
sudo tee /etc/systemd/system/payment-service.service.d/env.conf <<EOF
[Service]
Environment="PAYMENT_GATEWAY_URL=https://api.npay.np"
Environment="LOG_LEVEL=info"
EnvironmentFile=-/run/secrets/payment-api-key
EOF
# Apply changes
sudo systemctl daemon-reload
sudo systemctl restart payment-service The EnvironmentFile directive with the - prefix tells systemd to ignore the file if missing, preventing startup failures in development environments where secrets might be handled differently. For production, combine this with Ubuntu security hardening practices to restrict file permissions on secret files to root-only read access.
Docker Compose for Local Parity
Development environments must mirror production injection patterns to prevent "works on my machine" bugs. Use Docker Compose's env_file directive alongside explicit environment overrides to simulate Kubernetes behavior locally.
services:
payment-service:
image: registry.example.com/payment-svc:v2.4.1
env_file:
- .env.development
environment:
- LOG_LEVEL=debug
- DB_HOST=postgres
# Never put real secrets here; use .env.development for local mocks This setup ensures developers test against the same configuration interface that production uses. The .env.development file contains safe defaults and mock endpoints, while CI pipelines inject real test credentials via runner environment variables.
How do you secure sensitive configuration across different runtimes?
Security is where 12-Factor Config Across Runtimes implementations most frequently fail. Storing secrets in plaintext environment variables exposes them to any process that can inspect /proc/<pid>/environ, including debugging tools, crash dumpers, and compromised sidecar containers. A defense-in-depth approach is mandatory for any system handling financial or personal data.
- Never log configuration: Implement structured logging filters that redact keys matching patterns like
*KEY*,*SECRET*,*TOKEN*, or*PASSWORD*. See structured logging best practices for implementation patterns. - Use short-lived credentials: Prefer dynamic secrets from HashiCorp Vault or AWS Secrets Manager over static API keys. Rotate automatically and tie credential lifetime to pod or instance lifecycle.
- Encrypt at rest and in transit: Enable KMS encryption for Kubernetes Secrets, use TLS for all config retrieval calls, and ensure etcd encryption is active.
- Restrict access via RBAC: Limit who can read Secrets in Kubernetes and who can access parameter store paths in cloud providers. Apply least-privilege principles rigorously.
- Audit all access: Log every read of sensitive configuration. Unusual access patterns often indicate compromise before data exfiltration occurs.
For highly regulated environments, consider injecting secrets via mounted volumes with 0400 permissions rather than environment variables. While less pure from a 12-Factor perspective, this prevents leakage through process inspection and allows tighter filesystem-level access controls. The trade-off between theoretical purity and practical security always favors security.
Implement Resilient Configuration Today
Adopting 12-Factor Config Across Runtimes is not about dogmatic purity; it is about building systems that survive personnel changes, infrastructure migrations, and security audits without catastrophic rewrites. Start by documenting your configuration contract, then migrate one service at a time to proper injection patterns. Validate at boot, secure your secrets, and treat configuration as a first-class engineering concern. If your team needs help auditing current practices or designing a migration path, reach out to discuss your infrastructure.