YAML Configuration: Use YAML for Config
Learn: YAML Configuration: Use YAML for Config
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
YAML Configuration: Human-Readable Configs
Problem
Applications often require complex configuration management. Traditional approaches suffer from:
- Verbosity: XML/JSON require excessive syntax
- Readability: Hard to parse visually, especially for non-technical users
- Maintainability: Changes are error-prone and difficult to track
- Flexibility: Limited support for comments, references, and hierarchical data
- Learning curve: Steep for configuration authors
Solution
YAML (YAML Ain't Markup Language) provides a human-centric serialization format that prioritizes readability while maintaining structural integrity. It uses indentation-based hierarchy, minimal syntax, and native support for comments.
Key Advantages
- Readability: Clean, intuitive syntax resembles natural language
- Minimal Syntax: No brackets, braces, or excessive punctuation
- Comments: Full support for inline and block comments
- Data Types: Native support for strings, numbers, booleans, lists, and maps
- Hierarchical: Indentation-based nesting is intuitive
- Portability: Language-agnostic, widely supported across ecosystems
Code Implementation
1. Basic YAML Configuration File
# application.yaml - Main application configuration
app:
name: "MyApp"
version: "1.0.0"
environment: "production"
debug: false
server:
host: "0.0.0.0"
port: 8080
timeout: 30
ssl:
enabled: true
cert_path: "/etc/ssl/certs/app.crt"
key_path: "/etc/ssl/private/app.key"
database:
primary:
driver: "postgresql"
host: "db.example.com"
port: 5432
name: "myapp_db"
username: "app_user"
password: "${DB_PASSWORD}" # Environment variable reference
pool_size: 20
timeout: 5
replica:
host: "db-replica.example.com"
port: 5432
read_only: true
logging:
level: "info" # debug, info, warn, error
format: "json"
outputs:
- type: "console"
level: "info"
- type: "file"
path: "/var/log/app.log"
level: "warn"
rotation:
max_size_mb: 100
max_backups: 5
max_age_days: 30
cache:
enabled: true
backend: "redis"
redis:
host: "cache.example.com"
port: 6379
db: 0
ttl_seconds: 3600
features:
new_ui: true
beta_api: false
analytics: true
rate_limiting:
enabled: true
requests_per_minute: 100
security:
cors:
allowed_origins:
- "https://example.com"
- "https://app.example.com"
allowed_methods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
max_age: 3600
jwt:
secret: "${JWT_SECRET}"
expiration_hours: 24
refresh_expiration_days: 7
2. Python Configuration Loader
# config.py - YAML configuration management
import os
import yaml
from typing import Any, Dict, Optional
from pathlib import Path
class ConfigLoader:
"""Load and manage YAML configurations with environment variable support."""
def __init__(self, config_path: str):
self.config_path = Path(config_path)
self.config: Dict[str, Any] = {}
self.load()
def load(self) -> None:
"""Load YAML configuration file."""
if not self.config_path.exists():
raise FileNotFoundError(f"Config file not found: {self.config_path}")
with open(self.config_path, 'r') as f:
raw_config = yaml.safe_load(f)
# Resolve environment variables
self.config = self._resolve_env_vars(raw_config)
def _resolve_env_vars(self, obj: Any) -> Any:
"""Recursively resolve ${VAR_NAME} environment variable references."""
if isinstance(obj, dict):
return {k: self._resolve_env_vars(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [self._resolve_env_vars(item) for item in obj]
elif isinstance(obj, str):
# Replace ${VAR_NAME} with environment variable value
if obj.startswith("${") and obj.endswith("}"):
var_name = obj[2:-1]
return os.getenv(var_name, obj)
return obj
return obj
def get(self, path: str, default: Any = None) -> Any:
"""Get configuration value using dot notation.
Example: config.get('database.primary.host')
"""
keys = path.split('.')
value = self.config
for key in keys:
if isinstance(value, dict):
value = value.get(key)
if value is None:
return default
else:
return default
return value
def get_section(self, section: str) -> Dict[str, Any]:
"""Get entire configuration section."""
return self.config.get(section, {})
def __getitem__(self, key: str) -> Any:
"""Dictionary-style access."""
return self.config[key]
def __repr__(self) -> str:
return f"ConfigLoader({self.config_path})"
# Usage Example
if __name__ == "__main__":
# Set environment variables
os.environ['DB_PASSWORD'] = 'secure_password_123'
os.environ['JWT_SECRET'] = 'jwt_secret_key_xyz'
# Load configuration
config = ConfigLoader('application.yaml')
# Access values
print(f"App Name: {config.get('app.name')}")
print(f"Server Port: {config.get('server.port')}")
print(f"DB Host: {config.get('database.primary.host')}")
print(f"DB Password: {config.get('database.primary.password')}")
print(f"Logging Level: {config.get('logging.level')}")
# Get entire sections
server_config = config.get_section('server')
print(f"\nServer Config: {server_config}")
3. Environment-Specific Configurations
# config.base.yaml - Base configuration
app:
name: "MyApp"
version: "1.0.0"
server:
timeout: 30
database:
pool_size: 20
# config.development.yaml - Development overrides
app:
environment: "development"
debug: true
server:
host: "localhost"
port: 3000
logging:
level: "debug"
database:
primary:
host: "localhost"
port: 5432
# config.production.yaml - Production overrides
app:
environment: "production"
debug: false
server:
host: "0.0.0.0"
port: 8080
logging:
level: "warn"
database:
pool_size: 50
# config_manager.py - Multi-environment configuration
import yaml
from pathlib import Path
from typing import Dict, Any
class ConfigManager:
"""Manage environment-specific configurations."""
def __init__(self, env: str = "development"):
self.env = env
self.config = self._load_configs()
def _load_configs(self) -> Dict[str, Any]:
"""Load base config and merge environment-specific overrides."""
base_path = Path("config.base.yaml")
env_path = Path(f"config.{self.env}.yaml")
# Load base configuration
with open(base_path) as f:
config = yaml.safe_load(f)
# Merge environment-specific configuration
if env_path.exists():
with open(env_path) as f:
env_config = yaml.safe_load(f)
config = self._deep_merge(config, env_config)
return config
@staticmethod
def _deep_merge(base: Dict, override: Dict) -> Dict:
"""Deep merge override config into base config."""
result = base.copy()
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = ConfigManager._deep_merge(result[key], value)
else:
result[key] = value
return result
def get(self, path: str, default: Any = None) -> Any:
"""Get configuration value using dot notation."""
keys = path.split('.')
value = self.config
for key in keys:
value = value.get(key) if isinstance(value, dict) else None
if value is None:
return default
return value
4. Validation Schema
# config.schema.yaml - Configuration validation schema
app:
name:
type: string
required: true
min_length: 1
version:
type: string
required: true
pattern: '^\d+\.\d+\.\d+$'
environment:
type: string
required: true
enum: [development, staging, production]
server:
port:
type: integer
required: true
minimum: 1024
maximum: 65535
timeout:
type: integer
required: false
minimum: 1
database:
primary:
host:
type: string
required: true
port:
type: integer
required: true
minimum: 1
maximum: 65535
pool_size:
type: integer
required: false
minimum: 1
maximum: 1000
Summary
YAML configurations provide a human-readable, maintainable solution for application settings. By combining YAML's intuitive syntax with programmatic loaders, environment variable resolution, and validation, you create robust configuration systems that are easy to understand, modify, and deploy across different environments.