Configuration Management: YAML to Runtime Config Chain
In a platform composed of three processes, configuration management appears simple but is actually the epicenter of operational pain. A wrong database connection string can bring down the entire platform; a missing environment variable can make features mysteriously disappear.
Configuration Management: YAML to Runtime Config Chain
“Series: S2 Architecture Overview · Article 12 | Level: Intermediate | Reading Time: 18 min
#TL;DR
- The coomia-dip platform uses a four-layer configuration model: code defaults, YAML files, environment variables, and runtime dynamic config — each layer overrides the previous, enabling seamless configuration transitions from development to production.
- Three tech stacks (Spring Boot / Quarkus / FastAPI) each have their own config conventions; the platform unifies management through Docker Compose environment variable injection, with a single
.envfile controlling the entire cluster. - Sensitive configurations (database passwords, API keys) use Docker Secrets + environment variable separation, never hardcoded in YAML; Feature Flags use Ontology metadata-driven storage, supporting per-World granularity toggles.
#Introduction: Configuration Is the Nervous System of Distributed Systems
In a platform composed of three processes, configuration management appears simple but is actually the epicenter of operational pain. A wrong database connection string can bring down the entire platform; a missing environment variable can make features mysteriously disappear.
Configuration Management Challenges:
1. Multi-Tech-Stack Unification
onto-control: Spring Boot (application.yml)
onto-data: Quarkus (application.properties)
onto-intelligence: FastAPI (Python .env / pydantic-settings)
2. Multi-Environment Adaptation
dev -> Local development (SQLite / in-memory)
test -> CI/CD testing (Testcontainers)
staging -> Pre-production (real infrastructure, small scale)
prod -> Production (HA, multi-replica)
3. Security Requirements
Database passwords, Kafka auth, gRPC TLS certificates
-> Must not appear in code repository
-> Must not appear in Docker images
4. Dynamic Updates
Rate limits, degradation switches, Feature Flags
-> Must be modifiable without restart
This article provides a complete walkthrough of the coomia-dip configuration architecture — from static YAML to Docker Compose environment variables, from Secret management to runtime dynamic configuration.
#1. Four-Layer Configuration Model
#1.1 Configuration Priority
The platform follows the "convention over configuration" principle with four configuration layers, from lowest to highest priority:
Configuration Priority (lowest to highest):
Layer 1: Code Defaults
Location: Constants / default parameters in source code
Purpose: Ensure system starts with zero config (development mode)
Example: server.port = 8080
Layer 2: YAML / Properties Files (Static Config)
Location: src/main/resources/application.yml
Purpose: Base configuration for each environment
Example: spring.datasource.url = jdbc:postgresql://...
Layer 3: Environment Variables
Location: Docker Compose .env file / K8s ConfigMap
Purpose: Override environment-specific configuration
Example: SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/onto
Layer 4: Runtime Dynamic Config
Location: Database / config center
Purpose: Dynamic adjustments without restart
Example: rate_limit.max_requests_per_second = 500
Override order: Layer 4 > Layer 3 > Layer 2 > Layer 1
#1.2 When to Use Each Layer
Configuration Classification Decision Tree:
This config value...
|
+-- Rarely changes? (ports, protocol versions)
| +-- -> Layer 1: Code defaults
|
+-- Differs by environment? (DB address, Kafka address)
| +-- -> Layer 2 + Layer 3: YAML + env var override
|
+-- Is sensitive? (passwords, keys)
| +-- -> Layer 3: Environment variables (injected from Secrets)
|
+-- Needs dynamic adjustment? (rate limits, toggles)
+-- -> Layer 4: Runtime config
#2. Configuration Implementation by Process
#2.1 onto-control: Spring Boot Configuration
Spring Boot has the most powerful configuration capabilities among the three frameworks, supporting Profiles, YAML multi-documents, and property binding:
# onto-control/src/main/resources/application.yml
# -- Default config (all environments) --
server:
port: 8080
spring:
application:
name: onto-control
datasource:
url: jdbc:postgresql://localhost:5432/onto_control
username: onto
password: onto_dev
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 10000
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: onto-control
auto-offset-reset: earliest
producer:
acks: all
grpc:
server:
port: 9090
client:
onto-data:
address: static://localhost:9091
negotiation-type: plaintext
onto-intelligence:
address: static://localhost:9092
negotiation-type: plaintext
# Resilience4j config
resilience4j:
circuitbreaker:
instances:
data-service:
sliding-window-size: 20
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
intelligence-service:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
---
# -- Production config --
spring:
config:
activate:
on-profile: prod
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
grpc:
server:
security:
enabled: true
certificate-chain: file:/etc/certs/server.crt
private-key: file:/etc/certs/server.key
#2.2 onto-data: Quarkus Configuration
Quarkus uses MicroProfile Config, supporting both properties and YAML formats:
# onto-data/src/main/resources/application.properties
# -- Default config --
quarkus.application.name=onto-data
quarkus.http.port=8081
quarkus.grpc.server.port=9091
# Doris datasource
quarkus.datasource.db-kind=mysql
quarkus.datasource.jdbc.url=jdbc:mysql://localhost:9030/onto_data
quarkus.datasource.username=root
quarkus.datasource.password=
quarkus.datasource.jdbc.max-size=30
quarkus.datasource.jdbc.min-size=5
# Nessie config
nessie.server.uri=http://localhost:19120/api/v2
nessie.default-branch=main
# Iceberg config
iceberg.catalog.type=nessie
iceberg.catalog.uri=http://localhost:19120/api/v2
iceberg.catalog.warehouse=s3://onto-warehouse/
# Kafka
kafka.bootstrap.servers=localhost:9092
mp.messaging.incoming.ontology-events.connector=smallrye-kafka
mp.messaging.incoming.ontology-events.topic=platform.ontology.events
mp.messaging.incoming.ontology-events.group.id=onto-data
# -- Production config (via env var override or profile) --
%prod.quarkus.datasource.jdbc.url=jdbc:mysql://doris-prod:9030/onto_data
%prod.quarkus.datasource.jdbc.max-size=100
%prod.quarkus.datasource.jdbc.min-size=20
#2.3 onto-intelligence: FastAPI + Pydantic Settings
The Python ecosystem uses pydantic-settings for type-safe configuration:
# onto-intelligence/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
from typing import Optional
class DatabaseSettings(BaseSettings):
"""Database configuration"""
model_config = SettingsConfigDict(env_prefix="DB_")
host: str = "localhost"
port: int = 5432
name: str = "onto_intelligence"
user: str = "onto"
password: str = "onto_dev"
pool_size: int = 10
pool_max_overflow: int = 20
@property
def url(self) -> str:
return (
f"postgresql+asyncpg://{self.user}:{self.password}"
f"@{self.host}:{self.port}/{self.name}"
)
class GrpcSettings(BaseSettings):
"""gRPC configuration"""
model_config = SettingsConfigDict(env_prefix="GRPC_")
server_port: int = 9092
control_address: str = "localhost:9090"
data_address: str = "localhost:9091"
max_message_size: int = 50 * 1024 * 1024 # 50MB
keepalive_time_ms: int = 30000
keepalive_timeout_ms: int = 10000
class KafkaSettings(BaseSettings):
"""Kafka configuration"""
model_config = SettingsConfigDict(env_prefix="KAFKA_")
bootstrap_servers: str = "localhost:9092"
group_id: str = "onto-intelligence"
auto_offset_reset: str = "earliest"
enable_auto_commit: bool = False
class TemporalSettings(BaseSettings):
"""Temporal configuration"""
model_config = SettingsConfigDict(env_prefix="TEMPORAL_")
server_address: str = "localhost:7233"
namespace: str = "onto-intelligence"
task_queue: str = "reasoning-tasks"
max_concurrent_activities: int = 50
max_concurrent_workflows: int = 100
class AppSettings(BaseSettings):
"""Top-level application configuration"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_nested_delimiter="__",
)
environment: str = "dev"
debug: bool = False
log_level: str = "INFO"
db: DatabaseSettings = Field(default_factory=DatabaseSettings)
grpc: GrpcSettings = Field(default_factory=GrpcSettings)
kafka: KafkaSettings = Field(default_factory=KafkaSettings)
temporal: TemporalSettings = Field(default_factory=TemporalSettings)
# Global settings singleton
settings = AppSettings()
#3. Docker Compose Unified Configuration Management
#3.1 Environment Variable File
All environment variables are centralized in a .env file, automatically loaded by Docker Compose:
# deployment-Layer/.env
# ===================================================
# coomia-dip Platform Unified Environment Configuration
# ===================================================
# -- Common --
PLATFORM_ENV=dev
PLATFORM_LOG_LEVEL=INFO
# -- PostgreSQL --
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=onto_control
POSTGRES_USER=onto
POSTGRES_PASSWORD=change_me_in_production
# -- Doris --
DORIS_HOST=doris-fe
DORIS_PORT=9030
DORIS_USER=root
DORIS_PASSWORD=
# -- Kafka --
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
KAFKA_NUM_PARTITIONS=6
# -- Nessie --
NESSIE_URI=http://nessie:19120/api/v2
# -- MinIO (S3-compatible storage) --
MINIO_ENDPOINT=http://minio:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=onto-warehouse
# -- Temporal --
TEMPORAL_ADDRESS=temporal:7233
TEMPORAL_NAMESPACE=onto-intelligence
# -- gRPC Ports --
GRPC_CONTROL_PORT=9090
GRPC_DATA_PORT=9091
GRPC_INTELLIGENCE_PORT=9092
# -- onto-control specific --
SPRING_PROFILES_ACTIVE=docker
SPRING_DATASOURCE_URL=jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
SPRING_DATASOURCE_USERNAME=${POSTGRES_USER}
SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
# -- onto-data specific --
QUARKUS_PROFILE=docker
QUARKUS_DATASOURCE_JDBC_URL=jdbc:mysql://${DORIS_HOST}:${DORIS_PORT}/onto_data
# -- onto-intelligence specific --
DB_HOST=${POSTGRES_HOST}
DB_PORT=${POSTGRES_PORT}
DB_NAME=onto_intelligence
DB_USER=${POSTGRES_USER}
DB_PASSWORD=${POSTGRES_PASSWORD}
GRPC_CONTROL_ADDRESS=onto-control:${GRPC_CONTROL_PORT}
GRPC_DATA_ADDRESS=onto-data:${GRPC_DATA_PORT}
#3.2 Docker Compose Configuration Injection
# deployment-Layer/docker-compose.yml (config-related sections)
services:
onto-control:
image: coomia-dip/control:latest
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE}
- SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL}
- SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME}
- SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
- SPRING_KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS}
- GRPC_SERVER_PORT=${GRPC_CONTROL_PORT}
- GRPC_CLIENT_ONTO_DATA_ADDRESS=static://onto-data:${GRPC_DATA_PORT}
- GRPC_CLIENT_ONTO_INTELLIGENCE_ADDRESS=static://onto-intelligence:${GRPC_INTELLIGENCE_PORT}
- PLATFORM_LOG_LEVEL=${PLATFORM_LOG_LEVEL}
ports:
- "8080:8080"
- "${GRPC_CONTROL_PORT}:${GRPC_CONTROL_PORT}"
depends_on:
postgres:
condition: service_healthy
kafka:
condition: service_healthy
onto-data:
image: coomia-dip/data:latest
environment:
- QUARKUS_PROFILE=${QUARKUS_PROFILE}
- QUARKUS_DATASOURCE_JDBC_URL=${QUARKUS_DATASOURCE_JDBC_URL}
- QUARKUS_DATASOURCE_USERNAME=${DORIS_USER}
- QUARKUS_DATASOURCE_PASSWORD=${DORIS_PASSWORD}
- NESSIE_URI=${NESSIE_URI}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS}
- GRPC_SERVER_PORT=${GRPC_DATA_PORT}
- MINIO_ENDPOINT=${MINIO_ENDPOINT}
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
ports:
- "8081:8081"
- "${GRPC_DATA_PORT}:${GRPC_DATA_PORT}"
onto-intelligence:
image: coomia-dip/intelligence:latest
environment:
- ENVIRONMENT=${PLATFORM_ENV}
- LOG_LEVEL=${PLATFORM_LOG_LEVEL}
- DB_HOST=${POSTGRES_HOST}
- DB_PORT=${POSTGRES_PORT}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- GRPC_SERVER_PORT=${GRPC_INTELLIGENCE_PORT}
- GRPC_CONTROL_ADDRESS=${GRPC_CONTROL_ADDRESS}
- GRPC_DATA_ADDRESS=${GRPC_DATA_ADDRESS}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS}
- TEMPORAL_SERVER_ADDRESS=${TEMPORAL_ADDRESS}
- TEMPORAL_NAMESPACE=${TEMPORAL_NAMESPACE}
ports:
- "8082:8082"
- "${GRPC_INTELLIGENCE_PORT}:${GRPC_INTELLIGENCE_PORT}"
#3.3 Multi-Environment Overrides
Multi-Environment Configuration Strategy:
deployment-Layer/
+-- .env # Default env vars (dev)
+-- .env.staging # Staging overrides
+-- .env.prod # Production overrides
+-- docker-compose.yml # Base Compose file
+-- docker-compose.staging.yml # Staging overrides
+-- docker-compose.prod.yml # Production overrides
Start Commands:
dev: docker compose up
staging: docker compose --env-file .env.staging -f docker-compose.yml -f docker-compose.staging.yml up
prod: docker compose --env-file .env.prod -f docker-compose.yml -f docker-compose.prod.yml up
Production Override Example (docker-compose.prod.yml):
services:
onto-control:
deploy:
replicas: 2
resources:
limits:
cpus: '4'
memory: 8G
environment:
- SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=50
- JAVA_OPTS=-Xmx6g -Xms4g
#4. Sensitive Configuration (Secrets) Management
#4.1 Secret Separation Principle
Secret Management Rules:
NEVER:
- Hardcode passwords in source code
- Write passwords in YAML config files committed to Git
- Write passwords in Dockerfiles
- Write passwords in docker-compose.yml
CORRECT:
- .env file uses weak passwords, only for local development
- .env file is in .gitignore (not committed to Git)
- Provide .env.example template (passwords use change_me placeholder)
- Production uses Docker Secrets or K8s Secrets
#4.2 Docker Secrets
# docker-compose.prod.yml using Docker Secrets
version: '3.8'
secrets:
postgres_password:
file: /run/secrets/postgres_password
doris_password:
file: /run/secrets/doris_password
kafka_sasl_password:
file: /run/secrets/kafka_sasl_password
services:
onto-control:
secrets:
- postgres_password
environment:
- SPRING_DATASOURCE_PASSWORD_FILE=/run/secrets/postgres_password
onto-data:
secrets:
- doris_password
environment:
- QUARKUS_DATASOURCE_PASSWORD_FILE=/run/secrets/doris_password
onto-intelligence:
secrets:
- postgres_password
environment:
- DB_PASSWORD_FILE=/run/secrets/postgres_password
#4.3 Python Secret File Reading
# Secret file support in onto-intelligence/config.py
from pydantic_settings import BaseSettings
from pydantic import model_validator
from pathlib import Path
from typing import Optional
class DatabaseSettings(BaseSettings):
host: str = "localhost"
port: int = 5432
name: str = "onto_intelligence"
user: str = "onto"
password: str = "onto_dev"
password_file: Optional[str] = None # Docker Secret file path
@model_validator(mode="after")
def read_password_from_file(self) -> "DatabaseSettings":
"""If password_file is provided, read password from file"""
if self.password_file:
secret_path = Path(self.password_file)
if secret_path.exists():
self.password = secret_path.read_text().strip()
return self
#5. Feature Flag System
#5.1 Ontology-Driven Feature Flags
The platform's Feature Flags don't use third-party services (like LaunchDarkly) but leverage its own Ontology model for management:
Feature Flag Storage Model:
ObjectType: FeatureFlag
Properties:
+-- flag_key: String (unique identifier, e.g. "enable_derived_properties")
+-- flag_type: Enum (BOOLEAN, STRING, NUMBER, JSON)
+-- default_value: String (default value)
+-- description: String (feature description)
+-- enabled: Boolean (global toggle)
+-- created_at: Timestamp
+-- updated_at: Timestamp
ObjectType: FeatureFlagOverride
Properties:
+-- flag_key: String (associated Flag)
+-- scope_type: Enum (WORLD, USER, ROLE)
+-- scope_value: String (World ID / User ID / Role name)
+-- override_value: String (override value)
+-- priority: Integer (higher = more priority)
#5.2 Feature Flag Evaluation Logic
Feature Flag Evaluation Flow:
evaluate(flag_key, context) {
1. Look up FeatureFlag object
+-- Not found -> return null (feature undefined)
+-- Found -> continue
2. Check global toggle
+-- enabled = false -> return default_value
+-- enabled = true -> continue
3. Find Overrides (by priority descending)
+-- Match USER scope -> return override_value
+-- Match ROLE scope -> return override_value
+-- Match WORLD scope -> return override_value
+-- No match -> return default_value
}
Example:
flag_key: "enable_derived_properties"
default_value: "false"
overrides:
- scope: WORLD, value: "world-staging", override: "true" (staging World enabled)
- scope: USER, value: "admin-001", override: "true" (admin enabled)
evaluate("enable_derived_properties", {world: "world-prod", user: "user-001"})
-> "false" (default)
evaluate("enable_derived_properties", {world: "world-staging", user: "user-001"})
-> "true" (World override)
#5.3 Feature Flag Clients by Process
// Feature Flag usage in onto-control
@Service
public class FeatureFlagService {
private final FeatureFlagRepository flagRepo;
private final LoadingCache<String, FeatureFlag> cache;
public FeatureFlagService(FeatureFlagRepository flagRepo) {
this.flagRepo = flagRepo;
this.cache = Caffeine.newBuilder()
.maximumSize(200)
.expireAfterWrite(Duration.ofMinutes(5))
.build(flagRepo::findByKey);
}
public boolean isEnabled(String flagKey, EvaluationContext context) {
var flag = cache.get(flagKey);
if (flag == null) return false;
return flag.evaluate(context);
}
}
// Usage example
public class ActionServiceImpl {
public ActionResult executeAction(ActionRequest request) {
if (!featureFlags.isEnabled("enable_action_v2", context)) {
return executeActionV1(request); // old logic
}
return executeActionV2(request); // new logic
}
}
# Feature Flag usage in onto-intelligence
class FeatureFlagClient:
"""Feature Flag client - fetches flag state from onto-control"""
def __init__(self, control_stub: ControlServiceStub):
self.stub = control_stub
self._cache: dict[str, CachedFlag] = {}
self._cache_ttl = timedelta(minutes=5)
async def is_enabled(
self, flag_key: str, context: EvaluationContext | None = None
) -> bool:
cached = self._cache.get(flag_key)
if cached and not cached.is_expired:
return cached.evaluate(context)
# Fetch latest value from onto-control
response = await self.stub.GetFeatureFlag(
GetFeatureFlagRequest(key=flag_key)
)
flag = CachedFlag.from_response(response, self._cache_ttl)
self._cache[flag_key] = flag
return flag.evaluate(context)
#6. Configuration Hot-Reloading
#6.1 Which Configs Support Hot-Reload
Configuration Hot-Reload Capability Matrix:
Config Type Hot-Reload? Update Method
──────────────────────────────────────────────────────
Feature Flags Yes Ontology change -> event notification
Log level Yes Admin API / Actuator
Rate limit thresholds Yes Resilience4j runtime modification
Circuit breaker params Yes Resilience4j runtime modification
gRPC timeouts Partial Requires Channel rebuild
DB connection pool size Partial HikariCP supports runtime adjustment
Server ports No Requires restart
gRPC TLS certificates No Requires restart
Kafka Consumer Group No Requires restart
#6.2 Dynamic Log Level Adjustment
onto-control (Spring Boot Actuator):
POST /actuator/loggers/com.onto.control
Body: {"configuredLevel": "DEBUG"}
-> Takes effect immediately, no restart needed
onto-data (Quarkus):
# Quarkus doesn't natively support runtime log level changes
# Requires config change + restart
# But can be implemented via custom endpoint
onto-intelligence (Python logging):
POST /admin/log-level
Body: {"logger": "onto_intelligence", "level": "DEBUG"}
-> Calls logging.getLogger(name).setLevel(level)
-> Takes effect immediately
#6.3 Configuration Change Events
Configuration changes are broadcast to all processes via Kafka events:
Config Change Event Flow:
Admin modifies Feature Flag
|
+-- onto-control updates PostgreSQL
|
+-- Publishes Kafka event
| topic: platform.config.events
| {
| "type": "FEATURE_FLAG_CHANGED",
| "key": "enable_derived_properties",
| "old_value": "false",
| "new_value": "true",
| "scope": "WORLD:world-staging",
| "changed_by": "admin-001",
| "timestamp": "2026-03-24T14:30:00Z"
| }
|
+-- onto-data receives event
| -> Clears local Feature Flag cache
| -> Next request fetches fresh value
|
+-- onto-intelligence receives event
-> Clears local Feature Flag cache
-> Next request fetches fresh value
#7. Configuration Validation and Startup Checks
#7.1 Startup Configuration Validation
Each process executes configuration validation at startup to ensure required configs exist and are valid:
# Startup config validation in onto-intelligence
class ConfigValidator:
"""Config validator - runs at startup"""
def validate(self, settings: AppSettings) -> list[str]:
errors: list[str] = []
# Required field checks
if not settings.db.host:
errors.append("DB_HOST is required")
if not settings.grpc.control_address:
errors.append("GRPC_CONTROL_ADDRESS is required")
if not settings.kafka.bootstrap_servers:
errors.append("KAFKA_BOOTSTRAP_SERVERS is required")
# Connectivity checks
if not self._check_db_connection(settings.db):
errors.append(
f"Cannot connect to database at {settings.db.host}:{settings.db.port}"
)
if not self._check_grpc_connection(settings.grpc.control_address):
errors.append(
f"Cannot connect to onto-control at {settings.grpc.control_address}"
)
# Value range checks
if settings.db.pool_size < 1 or settings.db.pool_size > 100:
errors.append(f"DB_POOL_SIZE must be 1-100, got {settings.db.pool_size}")
if settings.grpc.max_message_size > 100 * 1024 * 1024:
errors.append("GRPC_MAX_MESSAGE_SIZE exceeds 100MB limit")
return errors
#7.2 Configuration Diagnostic Endpoints
Each process exposes diagnostic endpoints for operational troubleshooting:
Config Diagnostic APIs:
onto-control:
GET /actuator/env -- View all env vars (sensitive values masked)
GET /actuator/configprops -- View all config properties and their sources
GET /actuator/info -- View app info (version, build time)
onto-data:
GET /q/config -- View Quarkus configuration
onto-intelligence:
GET /admin/config -- View current effective config (passwords masked)
GET /admin/config/sources -- View each config item's source (code default/env/file)
#7.3 Configuration Drift Detection
Configuration Drift Detection (runs hourly):
1. Collect current effective config from each process
2. Compare against expected config (.env file)
3. Detect unexpected differences
Detection Items:
+-- Whether env vars were overridden by runtime changes
+-- Whether Feature Flags match expectations
+-- Whether connection pool params match
+-- Whether log levels were temporarily changed
Drift Report:
+----------------------------------------------------+
| Configuration Drift Report - 2026-03-24 15:00 |
+----------------------------------------------------+
| onto-control: |
| WARNING: log.level: expected=INFO, actual=DEBUG |
| (manually changed via Actuator at 14:30) |
| OK: datasource.pool-size: expected=20, actual=20 |
| |
| onto-intelligence: |
| OK: All configs match expected values |
| |
| Feature Flags: |
| WARNING: enable_derived_properties: expected=false|
| actual=true (override for WORLD:world-staging)|
+----------------------------------------------------+
#8. Configuration Best Practices
#8.1 Naming Conventions
Configuration Naming Conventions:
Environment Variable Names:
Format: {NAMESPACE}_{COMPONENT}_{KEY}
Examples:
SPRING_DATASOURCE_URL (Spring Boot standard)
QUARKUS_DATASOURCE_JDBC_URL (Quarkus standard)
DB_HOST (Python custom)
GRPC_CONTROL_ADDRESS (cross-language)
KAFKA_BOOTSTRAP_SERVERS (cross-language)
YAML Key Names:
Format: kebab-case (Spring Boot) / dot-separated (Quarkus)
Examples:
spring.datasource.hikari.maximum-pool-size
quarkus.datasource.jdbc.max-size
Python Config Names:
Format: snake_case (Pydantic fields)
Examples:
db.pool_size
grpc.control_address
#8.2 Environment Variable Override Rules
Environment Variable to Config Mapping Rules:
Spring Boot:
SPRING_DATASOURCE_URL -> spring.datasource.url
Rule: _ replaced with . , all lowercase
Quarkus:
QUARKUS_DATASOURCE_JDBC_URL -> quarkus.datasource.jdbc.url
Rule: Same as Spring Boot
Pydantic Settings:
DB_HOST -> DatabaseSettings.host (via env_prefix="DB_")
GRPC__CONTROL_ADDRESS -> GrpcSettings.control_address (via env_nested_delimiter="__")
#8.3 New Configuration Checklist
Checklist for Adding a New Configuration Item:
[ ] Define reasonable default value in code (Layer 1)
[ ] Add commented example in YAML/properties (Layer 2)
[ ] Add environment variable to .env.example (Layer 3)
[ ] If sensitive, ensure injection only via env vars/Secrets
[ ] Add config validation (startup check)
[ ] Update diagnostic endpoint masking rules
[ ] Document the new config item
[ ] Test: starts normally with defaults (no config)
[ ] Test: environment variable override works
[ ] Test: invalid values are caught by validation
#Key Takeaways
- Four-Layer Model: Code defaults, YAML, environment variables, runtime config — each overrides the previous, balancing development convenience and production security.
- Docker Compose Unification: A single
.envfile controls configuration for all three processes, eliminating scattered config files. - Secret Separation: Sensitive configs are injected via Docker Secrets, never appearing in code repositories or images.
- Self-Bootstrapping Feature Flags: Leverages the Ontology model to store Flag definitions, supporting per-World/User/Role granularity control.
- Config Validation: Automatic startup validation of required fields, connectivity, and value ranges — fail fast rather than fail mysteriously at runtime.
- Hot-Reload Boundaries: Clear distinction between hot-reloadable and restart-required configs, preventing operational mistakes.
- Drift Detection: Periodic comparison of actual vs. expected configuration, alerting on unexpected differences.
#Next Article
The next article, S2-13 AI + Human Collaboration: 10x Efficiency with Claude, will share the coomia-dip development methodology — how AI programming assistants (Claude) write 70-80% of the code, deliver a complete gRPC Service in 2-4 hours, and how CLAUDE.md guardrails ensure the quality of AI-generated code.
tags: configuration, YAML, Docker-Compose, environment-variables, secrets, feature-flags, hot-reload, pydantic-settings, Spring-Boot, Quarkus