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.
Explore the Netflix architecture interactively
Try it →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 componentsContinue learning
Go deeper on infrastructure and delivery
As an Amazon Associate I earn from qualifying purchases. Codelit may receive a commission at no extra cost to you.
The DevOps Handbook
Gene Kim, Jez Humble · 2021
Case studies from Etsy, Amazon, and Target on rewiring deployment pipelines and org structure.
Site Reliability Engineering
Betsy Beyer, Niall Richard Murphy · 2016
Google's on-call postmortems, error-budget math, and staffing models across 550 pages of practice.
4.7 (1.2k)#1 in Network Disaster & Recovery AdministrationLearning Domain-Driven Design
Vlad Khononov · 2021
Turns bounded contexts and aggregates into decision rules for where service boundaries actually go.
4.6 (400)Designing Data-Intensive Applications
Martin Kleppmann, Chris Riccomini · 2026
Traces replication, consistency and consensus end to end, with the failure modes behind each choice.
3.6 (196)#1 in Data Mining