docs: annotate spec.yml config layering conventions

Compose file owns application defaults. spec.yml config: section is for
deployment-specific overrides only (hostnames, IPs, secrets). Start
scripts should not have their own defaults — they read what the compose
file provides.

Annotations added:
- CLAUDE.md: config layering table and anti-pattern callout
- spec.py: Spec class docstring with good/bad config examples
- deployment_create.py: _write_config_file docstring

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pull/740/head
A. F. Dudley 2026-03-07 08:47:12 +00:00
parent 26dea540e9
commit d090f2064e
3 changed files with 68 additions and 0 deletions

View File

@ -114,6 +114,33 @@ One Kind cluster per host by design. Never request or expect separate clusters.
- `helpers.py`: `create_cluster()`, etcd cleanup, kind operations - `helpers.py`: `create_cluster()`, etcd cleanup, kind operations
- `cluster_info.py`: K8s resource generation (Deployment, Service, Ingress) - `cluster_info.py`: K8s resource generation (Deployment, Service, Ingress)
## spec.yml: Config Layering
**The compose file is the single source of truth for application defaults.**
The configuration chain is: compose defaults → spec.yml overrides → container env.
| Layer | Owns | Example |
|-------|------|---------|
| **compose file** | All env vars and their defaults | `RPC_PORT: ${RPC_PORT:-8899}` |
| **spec.yml config:** | Deployment-specific overrides only | `GOSSIP_HOST: 10.0.0.1` |
| **start script** | Reads env vars, no defaults of its own | `${RPC_PORT}` |
**What goes in spec.yml config:**
- Values unique to this deployment (hostnames, IPs, endpoints)
- Secrets (`$generate:hex:32$`)
- Overrides that differ from the compose default for this specific deployment
**What does NOT go in spec.yml config:**
- Application defaults (ports, log levels, intervals, feature flags)
- Values that would be the same across all deployments of this stack
- Every env var the service accepts — that's the compose file's job
**Anti-pattern:** Dumping all env vars from the compose file into spec.yml.
This creates three sources of truth (compose, spec, start script) that
inevitably diverge. If someone changes the default in the compose file,
spec.yml still has the old value and silently overrides it.
## Insights and Observations ## Insights and Observations
### Design Principles ### Design Principles

View File

@ -639,6 +639,18 @@ def create_registry_secret(spec: Spec, deployment_name: str) -> Optional[str]:
def _write_config_file( def _write_config_file(
spec_file: Path, config_env_file: Path, deployment_name: Optional[str] = None spec_file: Path, config_env_file: Path, deployment_name: Optional[str] = None
): ):
"""Write spec.yml config: entries to config.env.
The config: section in spec.yml should contain only deployment-specific
overrides values that differ between deployments (hostnames, endpoints,
credentials, secrets via $generate:...$).
Application defaults (ports, log levels, feature flags, tuning params)
belong in the compose file's environment section. The compose file is
the single source of truth for what env vars a service accepts and
their default values. spec.yml overrides those defaults for a specific
deployment.
"""
spec_content = get_parsed_deployment_spec(spec_file) spec_content = get_parsed_deployment_spec(spec_file)
config_vars = spec_content.get("config", {}) or {} config_vars = spec_content.get("config", {}) or {}

View File

@ -73,6 +73,35 @@ class Resources:
class Spec: class Spec:
"""Deployment spec (spec.yml) — describes WHERE and HOW to deploy a stack.
A spec.yml contains deployment-specific infrastructure configuration:
- stack: path to the stack definition
- deploy-to: target platform (k8s-kind, k8s, compose)
- network: ports, http-proxy, acme-email
- resources: CPU/memory limits and reservations
- security: privileged, capabilities, memlock
- volumes: host path mappings for persistent data
- configmaps: directories mounted as k8s ConfigMaps
- config: deployment-specific env var OVERRIDES (see below)
The config: section is for deployment-specific values only things
that differ between deployments (hostnames, endpoints, secrets).
Application defaults belong in the compose file's environment section,
not here. If a value would be the same across all deployments of this
stack, it belongs in the compose file, not in spec.yml.
Good config: entries (deployment-specific):
VALIDATOR_ENTRYPOINT: my-cluster.example.com:8001
PUBLIC_RPC_ADDRESS: my-node.example.com:8899
GOSSIP_HOST: 10.0.0.1
Bad config: entries (these are application defaults):
RPC_PORT: '8899' # same everywhere, belongs in compose
LIMIT_LEDGER_SIZE: '50000000' # same everywhere, belongs in compose
RUST_LOG: info # same everywhere, belongs in compose
"""
obj: typing.Any obj: typing.Any
file_path: Optional[Path] file_path: Optional[Path]