Skip to main content

Command Palette

Search for a command to run...

YAML Configuration: Use YAML for Config

Learn: YAML Configuration: Use YAML for Config

Updated
5 min readView as Markdown
T

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

  1. Readability: Clean, intuitive syntax resembles natural language
  2. Minimal Syntax: No brackets, braces, or excessive punctuation
  3. Comments: Full support for inline and block comments
  4. Data Types: Native support for strings, numbers, booleans, lists, and maps
  5. Hierarchical: Indentation-based nesting is intuitive
  6. 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.