Configuration Management — From Environment Variables to GitOps
Configuration is the silent architecture decision#
Every system has two kinds of knowledge baked in: code (what to do) and configuration (how to do it in this environment). Getting configuration wrong causes outages, security breaches, and deployment nightmares. Getting it right means you can deploy the same artifact to dev, staging, and production without changing a single line of code.
Config vs secrets — know the difference#
Configuration and secrets are both "things that change per environment," but they require very different handling.
Configuration includes:
- Database connection strings (host, port, database name)
- Feature flag states
- Timeout values and retry counts
- Log levels and output formats
- Third-party API base URLs
Secrets include:
- Database passwords and API keys
- TLS certificates and private keys
- Encryption keys and tokens
- OAuth client secrets
The critical rule: secrets must never appear in version control, environment variables visible to logs, or config files on disk in plaintext. Use dedicated secret managers like HashiCorp Vault, AWS Secrets Manager, or sealed secrets in Kubernetes.
The 12-factor config principle#
The Twelve-Factor App methodology states: store config in the environment. This means:
- Config varies between deploys (dev, staging, production) — code does not
- Config should be strictly separated from code
- Environment variables are the recommended transport mechanism
- No config files checked into the repo that contain environment-specific values
# Good — environment-specific, injected at runtime
DATABASE_URL=postgres://db.prod.internal:5432/myapp
CACHE_TTL=300
LOG_LEVEL=warn
# Bad — hardcoded in source
const DB_HOST = "db.prod.internal" // Don't do this
This principle enables build once, deploy anywhere — the same Docker image runs in every environment with different config injected.
Environment variables — the simplest transport#
Environment variables are the lowest-common-denominator config mechanism. Every language, every OS, every container runtime supports them.
Advantages:
- Universal support across all platforms
- Easy to set in CI/CD pipelines, Docker, Kubernetes
- No file parsing or library dependencies
- Clear separation from code
Limitations:
- Flat key-value only — no nesting or complex structures
- No type safety — everything is a string
- No change notification — process must restart to pick up changes
- Can leak into logs, child processes, and crash dumps
Best practice: Use a config library that reads environment variables, applies defaults, validates types, and exposes a typed config object. Libraries like dotenv, convict (Node.js), pydantic-settings (Python), or viper (Go) handle this well.
Config servers — centralized configuration#
When environment variables aren't enough, config servers provide centralized, versioned, and dynamic configuration.
HashiCorp Consul#
Consul stores config as key-value pairs in a distributed, highly available cluster. Services watch for changes and react in real-time.
- KV store with hierarchical keys (
app/production/db/host) - Watch mechanism for real-time config change notifications
- ACLs for fine-grained access control
- Multi-datacenter replication built in
etcd#
The backbone of Kubernetes, etcd is a distributed key-value store optimized for config and coordination.
- Strong consistency via Raft consensus
- Watch API for streaming changes
- Lease mechanism for ephemeral config (feature flags, circuit breakers)
- Small footprint — designed for config, not bulk data
Spring Cloud Config#
For JVM ecosystems, Spring Cloud Config provides a Git-backed config server.
- Git backend — config files live in a Git repo, versioned automatically
- Environment-specific overrides —
application-production.ymloverridesapplication.yml - Encryption support — encrypt sensitive values at rest
- Refresh scope — beans can reload config without restarting the app
Feature flags as configuration#
Feature flags are a special category of configuration that controls what code paths execute rather than how they execute.
{
"flags": {
"new-checkout-flow": {
"enabled": true,
"rollout_percentage": 25,
"allowed_users": ["beta-group"],
"kill_switch": false
}
}
}
Feature flags enable:
- Progressive rollouts — ship to 1% of users, then 10%, then 100%
- A/B testing — different experiences for different cohorts
- Kill switches — instantly disable a broken feature without deploying
- Trunk-based development — merge incomplete features behind flags
Tools like LaunchDarkly, Unleash, Flagsmith, and open-source Flipt provide flag management with SDKs, audit trails, and targeting rules. At scale, feature flags become a critical piece of your configuration architecture.
Config versioning and auditability#
Configuration changes cause outages just like code changes. Apply the same rigor:
- Version control — every config change should be tracked with who, when, and why
- Audit trails — config servers should log every read and write
- Diff visibility — before applying a config change, show what changed
- Rollback capability — revert to the previous config version in seconds
Git-backed config (Spring Cloud Config, GitOps) gives you versioning for free. KV stores like Consul and etcd require explicit versioning strategies or integration with change management tools.
Hot reload — config without restarts#
Restarting services to pick up config changes is slow and causes downtime. Hot reload patterns:
Polling: Service periodically checks the config source for changes. Simple but introduces latency (polling interval) and unnecessary load.
Watching: Service opens a long-lived connection to the config server and receives push notifications. Real-time but requires a config server that supports watches (Consul, etcd, Zookeeper).
Signal-based: Send a SIGHUP to the process to trigger a config reload. Common in traditional Unix daemons (nginx, HAProxy).
Sidecar pattern: A sidecar container watches the config source and writes updated files to a shared volume. The application watches the filesystem for changes. This decouples config fetching from application logic.
# Kubernetes ConfigMap with auto-reload via sidecar
volumes:
- name: config
configMap:
name: app-config
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/app-config
readOnly: true
Config validation — catch errors before they hit production#
Invalid configuration is a top cause of production incidents. Validate early:
Schema validation: Define a schema for your config and validate at startup. If validation fails, refuse to start — a loud failure is better than silent misconfiguration.
from pydantic import BaseSettings, validator
class AppConfig(BaseSettings):
database_url: str
cache_ttl: int = 300
log_level: str = "info"
max_connections: int = 50
@validator("log_level")
def valid_log_level(cls, v):
allowed = {"debug", "info", "warn", "error"}
if v not in allowed:
raise ValueError(f"log_level must be one of {allowed}")
return v
@validator("max_connections")
def reasonable_connections(cls, v):
if v < 1 or v > 1000:
raise ValueError("max_connections must be between 1 and 1000")
return v
CI validation: Run config validation in your CI pipeline. Catch misconfigurations before they reach any environment.
Dry-run mode: Apply the config change in a dry-run mode that validates without actually changing behavior. Terraform does this with plan, and the same principle applies to application config.
GitOps configuration management#
GitOps takes the "config in Git" principle to its logical conclusion: Git is the single source of truth for all configuration, and automated controllers reconcile the live state to match.
The GitOps workflow:
- Developer opens a PR to change a config value in the Git repo
- CI validates the config change (schema check, policy check)
- PR is reviewed and merged
- A GitOps controller (ArgoCD, Flux) detects the change
- Controller applies the new config to the target environment
- Controller reports drift if the live state doesn't match Git
Benefits:
- Full audit trail via Git history
- Peer review for config changes via PRs
- Automated rollback by reverting a Git commit
- Declarative — you describe the desired state, the controller enforces it
- Same workflow for code and config changes
Tools: ArgoCD and Flux are the dominant Kubernetes GitOps controllers. For non-Kubernetes environments, tools like Atlantis (Terraform) and env0 apply GitOps principles to infrastructure config.
Visualize your config architecture#
See how config servers, environment variables, and GitOps pipelines fit together — try Codelit to generate an interactive diagram showing your configuration flow from source control to running services.
Key takeaways#
- Separate config from code — 12-factor principles are non-negotiable
- Separate config from secrets — different sensitivity, different tools
- Use config servers at scale — Consul, etcd, or Spring Cloud Config for centralized management
- Validate early — schema validation at startup prevents silent misconfigurations
- Hot reload saves uptime — watch-based patterns eliminate restart-driven downtime
- GitOps is the gold standard — Git as the source of truth, automated reconciliation
This is article #173 on the Codelit engineering blog — we publish in-depth guides on system design, infrastructure, and software architecture. Explore all of them at codelit.io.
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
Cost Estimator
See estimated AWS monthly costs for every component in your architecture
GitHub Integration
Paste a repo URL and generate architecture from your actual codebase
Related articles
Batch API Endpoints — Patterns for Bulk Operations, Partial Success, and Idempotency
8 min read
system designCircuit Breaker Implementation — State Machine, Failure Counting, Fallbacks, and Resilience4j
7 min read
testingAPI Contract Testing with Pact — Consumer-Driven Contracts for Microservices
8 min read
Try these templates
Headless CMS Platform
Headless content management with structured content, media pipeline, API-first delivery, and editorial workflows.
8 componentsProject Management Platform
Jira/Linear-like tool with issues, sprints, boards, workflows, and real-time collaboration.
8 componentsWarehouse & Inventory Management
WMS with receiving, bin locations, pick/pack/ship, real-time stock tracking, and barcode scanning.
8 componentsBuild this architecture
Generate an interactive architecture for Configuration Management in seconds.
Try it in Codelit →
Comments