Overview
Configuration management controls how applications access settings, credentials, environment-specific values, and operational parameters. Software systems require different configurations for development, testing, staging, and production environments. Each environment needs distinct database connections, API endpoints, feature flags, and resource limits. Configuration management separates these environment-specific values from application code.
The practice emerged from the need to deploy identical code across multiple environments while varying behavior based on context. Early applications hardcoded values directly in source files, requiring code changes for each deployment target. This created maintenance problems and deployment risks. Modern configuration management externalizes settings, making applications portable and deployable without code modification.
Configuration management handles several categories of data. Environment variables control runtime behavior like debug modes and log levels. Connection strings specify database and service endpoints. Credentials include API keys, passwords, and certificates. Feature flags enable or disable functionality without code deployment. Resource limits define memory, timeout, and concurrency constraints.
The distinction between configuration and code defines the boundary. Configuration data changes between deployments of the same codebase. Code defines application logic and remains constant across environments. A database hostname is configuration; the database query logic is code. An API timeout value is configuration; the HTTP client implementation is code.
# Configuration - varies by environment
DATABASE_URL = ENV['DATABASE_URL']
MAX_CONNECTIONS = ENV.fetch('MAX_CONNECTIONS', 10).to_i
# Code - consistent across environments
class DatabasePool
def initialize(url, max_connections)
@url = url
@max_connections = max_connections
end
end
pool = DatabasePool.new(DATABASE_URL, MAX_CONNECTIONS)
Configuration management intersects with security, deployment automation, and application architecture. Insecure configuration storage exposes credentials. Manual configuration updates create deployment bottlenecks. Tightly coupled configuration complicates testing and development.
Key Principles
The twelve-factor app methodology established core configuration principles. Store configuration in environment variables, separate from code. Never commit credentials or environment-specific values to version control. Make configuration immediately visible and modifiable without code changes.
Environment parity requires identical configuration structure across all stages. Development, staging, and production use the same configuration keys with different values. This prevents configuration drift where environments diverge over time. Applications read DATABASE_URL in all environments, though the actual database servers differ.
Explicit dependency declaration ensures applications state all configuration requirements. Applications fail fast at startup when required configuration is missing rather than failing unpredictably during operation. Validation occurs before accepting traffic.
class Config
def initialize
@database_url = ENV.fetch('DATABASE_URL')
@redis_url = ENV.fetch('REDIS_URL')
@api_key = ENV.fetch('API_KEY')
@log_level = ENV.fetch('LOG_LEVEL', 'info')
rescue KeyError => e
raise ConfigurationError, "Missing required configuration: #{e.message}"
end
end
Configuration hierarchy establishes precedence when multiple sources provide values. Command-line arguments override environment variables, which override configuration files, which override application defaults. This layering allows specific deployments to override defaults without modifying base configuration.
Immutability treats configuration as read-only after application startup. Applications read configuration once during initialization and never modify values during runtime. This prevents temporal coupling where application behavior changes unexpectedly mid-execution. Configuration changes require process restart.
Type safety validates configuration values match expected types. String "true" differs from boolean true. Numeric limits require integers, not strings. Type coercion at configuration load time prevents runtime type errors.
class TypedConfig
def initialize
@port = Integer(ENV.fetch('PORT', 3000))
@debug = ENV.fetch('DEBUG', 'false').downcase == 'true'
@timeout = Float(ENV.fetch('TIMEOUT', 30.0))
@workers = Integer(ENV.fetch('WORKERS', 4))
end
rescue ArgumentError => e
raise ConfigurationError, "Invalid configuration type: #{e.message}"
end
Secret separation distinguishes sensitive credentials from non-sensitive settings. Secrets include passwords, private keys, and API tokens. Non-sensitive configuration includes timeouts, URLs, and feature flags. Secrets require encryption at rest and restricted access. Non-sensitive configuration can use simpler storage.
Configuration versioning tracks changes to configuration schemas and values over time. When applications add or remove configuration requirements, version identifiers prevent mismatches between application code and configuration. Schema evolution requires backward compatibility periods.
Implementation Approaches
File-based configuration stores settings in structured files deployed alongside application code. YAML, JSON, TOML, or custom formats define configuration hierarchies. Applications read files at startup and parse values. File-based approaches work well for non-sensitive, relatively static configuration.
# config/settings.yml
development:
database:
host: localhost
pool: 5
cache:
enabled: false
production:
database:
host: db.example.com
pool: 25
cache:
enabled: true
Files support complex nested structures and comments for documentation. Version control tracks configuration changes. However, files complicate secret management and environment-specific overrides. Each environment requires separate files or templating.
Environment variable injection provides configuration through process environment. The operating system or container runtime sets variables before starting the application. Applications read variables using standard language facilities. This approach follows twelve-factor principles and works across languages and platforms.
Environment variables handle simple key-value pairs. Complex nested configuration requires flattening or encoding. Variable naming conventions prevent collisions. Prefixing with application identifiers namespaces variables: APP_DATABASE_URL, APP_CACHE_ENABLED.
class EnvConfig
def self.load
{
database: {
url: ENV['APP_DATABASE_URL'],
pool: ENV.fetch('APP_DATABASE_POOL', 5).to_i
},
cache: {
enabled: ENV.fetch('APP_CACHE_ENABLED', 'false') == 'true',
ttl: ENV.fetch('APP_CACHE_TTL', 3600).to_i
}
}
end
end
Configuration services centralize configuration in external systems. Applications query configuration servers at startup or runtime. Services like Consul, etcd, or cloud provider parameter stores provide distributed configuration. This enables dynamic updates and centralized management.
Configuration services support multiple applications sharing configuration. Central administration updates values without touching application deployments. Service discovery integrates with configuration, providing endpoint information. However, services add dependencies and latency. Applications must handle service unavailability.
Layered configuration combines multiple sources with precedence rules. Base configuration files provide defaults. Environment variables override file values. Command-line arguments override everything. Applications merge sources into final configuration.
class LayeredConfig
def initialize
@config = load_defaults
.merge(load_file)
.merge(load_environment)
.merge(load_arguments)
end
private
def load_defaults
{ port: 3000, log_level: 'info', workers: 4 }
end
def load_file
return {} unless File.exist?('config.yml')
YAML.load_file('config.yml')
end
def load_environment
{
port: ENV['PORT']&.to_i,
log_level: ENV['LOG_LEVEL'],
workers: ENV['WORKERS']&.to_i
}.compact
end
def load_arguments
# Parse ARGV for command-line overrides
{}
end
end
Templated configuration generates environment-specific files from templates. Template engines like ERB or Mustache substitute variables into configuration file templates. Build or deployment processes render templates with environment-specific values.
Templates maintain configuration structure while varying values. Single template generates multiple environment files. However, templating adds build complexity and can obscure actual deployed values.
Ruby Implementation
Ruby applications commonly use environment variables through ENV hash. The ENV object provides read and write access to process environment variables. Reading missing variables returns nil. The fetch method raises KeyError for required variables.
# Basic environment variable access
database_url = ENV['DATABASE_URL']
port = ENV.fetch('PORT', 3000).to_i
# Raising on missing required values
api_key = ENV.fetch('API_KEY') # Raises KeyError if missing
# Type conversion
debug_mode = ENV['DEBUG'] == 'true'
max_connections = Integer(ENV.fetch('MAX_CONNECTIONS', 10))
The dotenv gem loads environment variables from .env files during development. This simplifies local development without setting system-wide environment variables. The gem reads .env files and populates ENV before application startup.
# Gemfile
gem 'dotenv-rails', groups: [:development, :test]
# .env file (not committed to version control)
DATABASE_URL=postgresql://localhost/myapp_development
REDIS_URL=redis://localhost:6379
API_KEY=dev_api_key_12345
# config/application.rb
require 'dotenv/load'
The config gem provides layered YAML-based configuration with environment support. Configuration files live in config/settings directory. The gem merges settings files, environment-specific files, and local overrides.
# Gemfile
gem 'config'
# config/settings.yml
defaults: &defaults
database:
pool: 5
cache:
ttl: 3600
development:
<<: *defaults
database:
host: localhost
production:
<<: *defaults
database:
host: <%= ENV['DATABASE_HOST'] %>
pool: 25
# Accessing configuration
Settings.database.host
Settings.cache.ttl
Settings.database.pool
Rails provides built-on configuration through config/application.rb and environment files. The Rails.configuration object holds framework and application settings. Environment-specific files override base configuration.
# config/application.rb
module MyApp
class Application < Rails::Application
config.time_zone = 'UTC'
config.active_record.default_timezone = :utc
end
end
# config/environments/production.rb
Rails.application.configure do
config.log_level = :info
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
end
# Accessing configuration
Rails.configuration.time_zone
Rails.configuration.log_level
Custom configuration classes encapsulate configuration logic and validation. These classes centralize configuration reading, type conversion, and validation.
class AppConfig
class << self
def database
@database ||= DatabaseConfig.new
end
def cache
@cache ||= CacheConfig.new
end
def api
@api ||= APIConfig.new
end
end
class DatabaseConfig
attr_reader :url, :pool, :timeout
def initialize
@url = ENV.fetch('DATABASE_URL')
@pool = Integer(ENV.fetch('DATABASE_POOL', 5))
@timeout = Integer(ENV.fetch('DATABASE_TIMEOUT', 5000))
validate!
end
private
def validate!
raise ConfigError, "Invalid pool size" if @pool < 1
raise ConfigError, "Invalid timeout" if @timeout < 0
end
end
class CacheConfig
attr_reader :url, :ttl, :enabled
def initialize
@url = ENV['REDIS_URL']
@ttl = Integer(ENV.fetch('CACHE_TTL', 3600))
@enabled = ENV.fetch('CACHE_ENABLED', 'true') == 'true'
end
end
end
# Usage
AppConfig.database.url
AppConfig.cache.enabled
The dry-configurable gem provides a declarative DSL for defining configuration schemas. Settings include types, defaults, and constructors for value processing.
require 'dry-configurable'
class AppSettings
extend Dry::Configurable
setting :database_url, constructor: ->(v) { v.to_s }
setting :database_pool, default: 5, constructor: ->(v) { Integer(v) }
setting :cache_enabled, default: false, constructor: ->(v) { v == 'true' }
setting :log_level, default: 'info'
end
AppSettings.config.database_url = ENV['DATABASE_URL']
AppSettings.config.database_pool = ENV.fetch('DATABASE_POOL', 5)
AppSettings.config.cache_enabled = ENV.fetch('CACHE_ENABLED', 'false')
AppSettings.config.database_url
AppSettings.config.database_pool
Tools & Ecosystem
Environment variable management tools simplify setting and organizing variables across environments. The direnv tool automatically loads and unloads environment variables based on directory. When entering a project directory, direnv loads variables from .envrc file. Leaving the directory unloads them.
Docker and container platforms inject environment variables through deployment manifests. Docker Compose defines environment sections in docker-compose.yml. Kubernetes uses ConfigMaps and Secrets. Container orchestration platforms provide variable management interfaces.
# docker-compose.yml
services:
web:
image: myapp:latest
environment:
- DATABASE_URL=postgresql://db:5432/myapp
- REDIS_URL=redis://redis:6379
- LOG_LEVEL=debug
env_file:
- .env.production
Secret management systems store and control access to sensitive configuration. HashiCorp Vault provides encrypted secret storage with access control policies. AWS Secrets Manager and Google Secret Manager offer cloud-native secret storage. Applications retrieve secrets at runtime through authenticated API calls.
These systems audit secret access, rotate credentials, and enforce least-privilege access. Secrets never appear in configuration files or environment variables visible to unauthorized users.
# Using Vault with vault-ruby gem
require 'vault'
Vault.address = ENV['VAULT_ADDR']
Vault.token = ENV['VAULT_TOKEN']
# Read secret
secret = Vault.logical.read('secret/data/database')
database_password = secret.data[:data][:password]
# Dynamic secrets that auto-rotate
db_creds = Vault.logical.read('database/creds/myapp')
username = db_creds.data[:username]
password = db_creds.data[:password]
Configuration management databases (CMDBs) track configuration across infrastructure and applications. Tools like Chef, Puppet, and Ansible manage server configuration as code. These tools define desired configuration state and enforce it across server fleets.
Infrastructure as Code (IaC) tools provision and configure cloud resources. Terraform defines infrastructure configuration in declarative files. CloudFormation and ARM templates configure cloud services. These tools version infrastructure configuration alongside application configuration.
Parameter store services provide hierarchical configuration storage. AWS Systems Manager Parameter Store and Azure App Configuration centralize configuration with versioning and access control. Applications read parameters through cloud SDK calls.
# AWS Parameter Store
require 'aws-sdk-ssm'
ssm = Aws::SSM::Client.new(region: 'us-east-1')
# Get single parameter
response = ssm.get_parameter(
name: '/myapp/production/database/password',
with_decryption: true
)
password = response.parameter.value
# Get parameters by path
response = ssm.get_parameters_by_path(
path: '/myapp/production/',
recursive: true,
with_decryption: true
)
config = response.parameters.each_with_object({}) do |param, hash|
key = param.name.split('/').last
hash[key] = param.value
end
Practical Examples
A Rails application separates development and production configuration using environment variables and Rails conventions. Development uses local services while production connects to cloud infrastructure.
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("DB_POOL", 5) %>
timeout: 5000
development:
<<: *default
database: myapp_development
host: localhost
test:
<<: *default
database: myapp_test
host: localhost
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
pool: <%= ENV.fetch("DB_POOL", 25) %>
# config/environments/production.rb
Rails.application.configure do
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
expires_in: 90.minutes,
namespace: 'myapp'
}
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV['SMTP_HOST'],
port: ENV.fetch('SMTP_PORT', 587),
user_name: ENV['SMTP_USER'],
password: ENV['SMTP_PASSWORD'],
authentication: :plain,
enable_starttls_auto: true
}
config.active_storage.service = :amazon
end
# config/storage.yml
amazon:
service: S3
access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
region: <%= ENV['AWS_REGION'] %>
bucket: <%= ENV['S3_BUCKET'] %>
A microservices deployment uses centralized configuration with environment-specific overrides. Base configuration provides defaults while deployment manifests inject environment-specific values.
# config/settings.yml (base configuration)
service:
name: user-service
port: 8080
timeout: 30
database:
pool: 5
timeout: 5000
cache:
ttl: 3600
enabled: true
# Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
data:
SERVICE_PORT: "8080"
DATABASE_POOL: "25"
CACHE_ENABLED: "true"
CACHE_TTL: "7200"
# Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: user-service-secrets
type: Opaque
data:
database-url: cG9zdGdyZXNxbDovL2RiLmV4YW1wbGUuY29tL3VzZXJz
api-key: c2VjcmV0X2tleV8xMjM0NQ==
# Application reads merged configuration
class ServiceConfig
def self.load
base = YAML.load_file('config/settings.yml')
{
service: {
name: base['service']['name'],
port: ENV.fetch('SERVICE_PORT', base['service']['port']).to_i,
timeout: ENV.fetch('SERVICE_TIMEOUT', base['service']['timeout']).to_i
},
database: {
url: ENV.fetch('DATABASE_URL'),
pool: ENV.fetch('DATABASE_POOL', base['database']['pool']).to_i,
timeout: ENV.fetch('DATABASE_TIMEOUT', base['database']['timeout']).to_i
},
cache: {
enabled: ENV.fetch('CACHE_ENABLED', base['cache']['enabled'].to_s) == 'true',
ttl: ENV.fetch('CACHE_TTL', base['cache']['ttl']).to_i
},
api_key: ENV.fetch('API_KEY')
}
end
end
Feature flag configuration enables runtime behavior changes without code deployment. Flags control feature rollout, A/B testing, and emergency feature disabling.
class FeatureFlags
def initialize
@flags = load_flags
end
def enabled?(flag_name, user_id: nil, percentage: nil)
flag = @flags[flag_name.to_sym]
return false unless flag
return flag[:enabled] unless percentage
# Percentage-based rollout
return false unless flag[:enabled]
hash = Digest::MD5.hexdigest("#{flag_name}:#{user_id}").to_i(16)
(hash % 100) < percentage
end
private
def load_flags
{
new_dashboard: {
enabled: ENV.fetch('FEATURE_NEW_DASHBOARD', 'false') == 'true'
},
payment_v2: {
enabled: ENV.fetch('FEATURE_PAYMENT_V2', 'false') == 'true'
},
beta_features: {
enabled: ENV.fetch('FEATURE_BETA', 'false') == 'true'
}
}
end
end
flags = FeatureFlags.new
# Simple enable/disable
if flags.enabled?(:new_dashboard)
render :new_dashboard
else
render :legacy_dashboard
end
# Percentage rollout
if flags.enabled?(:payment_v2, user_id: current_user.id, percentage: 25)
process_payment_v2
else
process_payment_v1
end
Security Implications
Credentials in version control create permanent security vulnerabilities. Committed secrets remain in repository history even after deletion. Attackers scan public repositories for exposed credentials. Never commit API keys, passwords, certificates, or private keys.
Use .gitignore to exclude configuration files containing secrets. Environment-specific files and .env files should never appear in version control.
# .gitignore
.env
.env.local
.env.*.local
config/credentials/*.key
config/master.key
config/secrets.yml
Environment variable exposure through application logs or error messages leaks credentials. Stack traces, debug output, and log messages can print environment contents. Filter sensitive variables from logs and error reports.
# config/initializers/filter_parameters.rb
Rails.application.config.filter_parameters += [
:password,
:password_confirmation,
:api_key,
:secret_key,
:access_token,
:private_key
]
# Custom logger filtering
class SecureLogger
SENSITIVE_PATTERNS = [
/password/i,
/api[_-]?key/i,
/secret/i,
/token/i
].freeze
def self.log(message)
filtered = message.dup
ENV.each do |key, value|
if SENSITIVE_PATTERNS.any? { |pattern| key.match?(pattern) }
filtered.gsub!(value, '[FILTERED]') if value
end
end
Rails.logger.info(filtered)
end
end
Insufficient access controls on configuration files allow unauthorized reads. Configuration files need restrictive permissions. Secrets require encryption at rest. Cloud provider secret services provide access control through IAM policies.
# Encrypt sensitive configuration with Rails credentials
# Generates config/credentials.yml.enc and config/master.key
rails credentials:edit
# config/credentials.yml.enc (encrypted)
aws:
access_key_id: AKIAIOSFODNN7EXAMPLE
secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
stripe:
secret_key: sk_live_51Example...
# Access in application
Rails.application.credentials.aws[:access_key_id]
Rails.application.credentials.stripe[:secret_key]
# Master key comes from environment
ENV['RAILS_MASTER_KEY'] # In production
Secrets in container images persist after deployment. Building container images with hardcoded secrets embeds them in image layers. Use multi-stage builds and runtime secret injection.
# Bad - secrets in image
FROM ruby:3.2
ENV DATABASE_PASSWORD=secretpassword123
# Good - secrets from environment
FROM ruby:3.2
# No secrets in Dockerfile
# Inject at runtime via Docker/Kubernetes
Default credentials present in configuration files create known vulnerabilities. Change all defaults before production deployment. Validate that production configuration differs from development defaults.
class ConfigValidator
INSECURE_DEFAULTS = {
'DATABASE_PASSWORD' => ['password', 'secret', 'admin'],
'API_KEY' => ['your_api_key_here', 'test_key'],
'SECRET_KEY_BASE' => ['development_secret']
}.freeze
def self.validate!
INSECURE_DEFAULTS.each do |key, insecure_values|
value = ENV[key]
if insecure_values.include?(value)
raise SecurityError, "Insecure default value for #{key}"
end
end
end
end
# Run at application startup in production
ConfigValidator.validate! if Rails.env.production?
Common Pitfalls
Type confusion occurs when string environment variables receive numeric or boolean interpretation. ENV['PORT'] returns string "3000", not integer 3000. Comparisons and arithmetic require explicit conversion.
# Wrong - string comparison
if ENV['DEBUG'] == true # Always false
enable_debugging
end
# Wrong - string arithmetic
max_connections = ENV['MAX_CONNECTIONS'] + 5 # String concatenation "105"
# Correct - explicit conversion
if ENV['DEBUG'] == 'true'
enable_debugging
end
max_connections = ENV.fetch('MAX_CONNECTIONS', 10).to_i + 5
Missing configuration validation causes runtime failures. Applications start successfully with invalid configuration and fail during request processing. Validate all required configuration at startup.
# Fails at startup with clear error
class Config
def initialize
@port = Integer(ENV.fetch('PORT'))
@database_url = ENV.fetch('DATABASE_URL')
validate_database_url!
end
private
def validate_database_url!
uri = URI.parse(@database_url)
unless uri.scheme == 'postgresql' && uri.host
raise ConfigError, "Invalid database URL"
end
rescue URI::InvalidURIError => e
raise ConfigError, "Malformed database URL: #{e.message}"
end
end
Configuration drift occurs when environments diverge over time. Production receives updates that development and staging miss. Document all configuration changes and apply consistently across environments.
Sensitive data in configuration files persists in backups and logs. Encrypted configuration files leave decryption keys vulnerable. Separate secret management from configuration management.
Circular dependencies between configuration and application code prevent initialization. Configuration depends on database schema, but database connection requires configuration. Break circular dependencies through initialization order.
# Bad - circular dependency
class Config
def database_url
# Queries database for configuration
Setting.find_by(key: 'database_url').value
end
end
# Good - staged initialization
class Config
def self.bootstrap
# Load bootstrap configuration from environment
@database_url = ENV.fetch('DATABASE_URL')
end
def self.load_runtime_config
# Load additional configuration from database after connection established
@settings = Setting.all.each_with_object({}) do |setting, hash|
hash[setting.key] = setting.value
end
end
end
Config.bootstrap
establish_database_connection(Config.database_url)
Config.load_runtime_config
Overwriting production configuration during deployment destroys working settings. Always backup configuration before deployment. Use configuration version control or immutable configuration.
Reference
Configuration Sources
| Source | Precedence | Use Case | Persistence |
|---|---|---|---|
| Command-line arguments | Highest | One-time overrides | None |
| Environment variables | High | Runtime configuration | Process lifetime |
| Configuration files | Medium | Structured settings | Permanent |
| Default values | Lowest | Fallback values | Code-defined |
Environment Variable Patterns
| Pattern | Example | Description |
|---|---|---|
| Simple key | PORT | Single value |
| Namespaced | APP_DATABASE_URL | Prevents collisions |
| Hierarchical | APP_DB_PRIMARY_URL | Structured naming |
| Prefixed boolean | ENABLE_CACHE | Clear intent |
| Numeric suffix | WORKER_1_URL | Array-like values |
Configuration File Formats
| Format | Extensions | Advantages | Disadvantages |
|---|---|---|---|
| YAML | .yml, .yaml | Human-readable, hierarchical | Indentation-sensitive |
| JSON | .json | Machine-readable, universal | No comments |
| TOML | .toml | Hierarchical, typed | Less common |
| ENV | .env | Simple key-value | No nesting |
| Ruby | .rb | Code execution, dynamic | Security risk |
Common Configuration Keys
| Key | Purpose | Type | Example |
|---|---|---|---|
| DATABASE_URL | Database connection | String | postgresql://localhost/myapp |
| REDIS_URL | Cache connection | String | redis://localhost:6379 |
| SECRET_KEY_BASE | Session encryption | String | hex-encoded-secret |
| LOG_LEVEL | Logging verbosity | String | debug, info, warn, error |
| PORT | Server port | Integer | 3000 |
| RAILS_ENV | Environment name | String | development, production |
| RACK_ENV | Rack environment | String | development, production |
Security Checklist
| Item | Status | Action |
|---|---|---|
| .env files excluded from version control | Required | Add to .gitignore |
| Production secrets rotated | Required | Regular rotation schedule |
| Secrets encrypted at rest | Required | Use secret management service |
| Configuration validation at startup | Required | Fail fast on invalid config |
| Sensitive variables filtered from logs | Required | Configure log filtering |
| Default passwords changed | Required | Validate no defaults in production |
| Least privilege access to secrets | Required | IAM policies, access control |
Ruby Configuration Gems
| Gem | Purpose | API Style |
|---|---|---|
| dotenv | Load .env files | ENV hash |
| config | YAML-based settings | Settings object |
| dry-configurable | Schema-based config | DSL with types |
| figaro | ENV management | ENV hash |
| chamber | Encrypted settings | Chamber hash |
Validation Examples
# Required value validation
ENV.fetch('DATABASE_URL') # Raises if missing
# Type validation
Integer(ENV.fetch('PORT')) # Raises on non-integer
# Range validation
port = Integer(ENV.fetch('PORT'))
raise ConfigError unless (1..65535).include?(port)
# Format validation
url = ENV.fetch('DATABASE_URL')
raise ConfigError unless url.start_with?('postgresql://')
# Enum validation
log_level = ENV.fetch('LOG_LEVEL', 'info')
valid_levels = %w[debug info warn error fatal]
raise ConfigError unless valid_levels.include?(log_level)