cfgstack is a production-focused CLI for building a final configuration from layered files and environment variable overrides.
I built it to solve a common problem in real systems: config drift across dev, staging, and prod, plus hard-to-debug implicit overrides.
The goals are straightforward:
- deterministic behavior in local runs, CI, and production;
- explicit merge rules (no hidden magic);
- fast root-cause analysis when config is wrong.
cfgstack is designed around those constraints.
This tool is useful for teams that:
- run the same service across multiple environments;
- need profile-based config (
prod,dev, etc.); - rely on env overrides but still want control and traceability;
- require schema validation and strong diagnostics.
Typical use cases: backend services, workers, bots, game servers, internal CLIs.
- Loads layered config files:
default.*local.*<profile>.*(for exampleprod.*,dev.*)
- Supports
JSON,YAML,TOML. - Deep-merges objects using deterministic rules.
- Applies hierarchical env overrides.
- Tracks provenance (the last source that wrote each JSON Pointer).
- Validates the final config against JSON Schema.
- Provides debugging commands:
explainfor pointer-level inspection;doctorfor directory diagnostics and conflict analysis.
Layers are always applied in this order:
default.*local.*<profile>.*- env overrides
If multiple files match a layer, files are applied in lexicographic filename order.
- object + object: recursive merge
- arrays: full replacement by later layer
- scalars (
string,bool,number,null): replacement by later layer - type mismatch: later layer wins
This behavior is intentionally simple and predictable.
Defaults:
--env-prefix APP--env-delim __--env-case lower
Examples:
APP__DB__HOST=localhost->{ "db": { "host": "localhost" } }APP__SERVERS__0__HOST=10.0.0.1->{ "servers": [ { "host": "10.0.0.1" } ] }
Value typing:
true/false/null-> bool/null- integer ->
int64 - float ->
float64 - values starting with
{or[-> JSON parse - otherwise -> string
go install github.com/etherinus/cfgstack/cmd/cfgstack@latestgo build -o cfgstack ./cmd/cfgstackBuild with metadata:
go build -ldflags "-X github.com/etherinus/cfgstack/internal/buildinfo.Version=v0.1.0 -X github.com/etherinus/cfgstack/internal/buildinfo.Commit=$(git rev-parse HEAD) -X github.com/etherinus/cfgstack/internal/buildinfo.Date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o cfgstack ./cmd/cfgstackExample layout:
config/
default.yaml
local.yaml
prod.yaml
dev.yaml
Build merged config:
cfgstack build --in config --profile prod --out merged.jsonWrite to stdout:
cfgstack build --in config --profile prod --out - --format jsonDry run without writing:
cfgstack build --in config --allow-empty-profile --out merged.json --dry-runDry run with pointer-level diff against existing output file:
cfgstack build --in config --allow-empty-profile --out merged.json --dry-run --diffBuilds final config from file layers and env overrides.
Important flags:
--profileis required unless--allow-empty-profileis set.--fail-on-missing-defaultfails when nodefault.*files are found.--print-sourcesoutputs provenance map JSON.--dry-runcomputes result without writing output file.--diff(with--dry-run) prints pointer-level diff vs current--out.--schemaand--schema-strictenable JSON Schema validation.
Notes:
--print-sourcesrequires--outnot-.--diffrequires--dry-runand--outnot-.
Inspect a JSON Pointer in resolved config:
cfgstack explain --in config --profile prod --at /db/host
cfgstack explain --in config --profile prod --at / --sources
cfgstack explain --in config --profile prod --at /db/host --jsonIncludes:
- value;
- local context (parent/type/key/index);
- nearest provenance source;
- optional child sources (
--sources).
Diagnose config directory health:
cfgstack doctor --in config --profile prod --fail-on-missing-default --max-conflicts 100
cfgstack doctor --in config --allow-empty-profile --jsonChecks:
- discovered files per layer;
- supported vs unsupported extensions;
- effective apply order;
- pointer-level override conflicts.
Exit behavior:
- non-zero if conflicts exist;
- non-zero if
--fail-on-missing-defaultand nodefault.*.
--json returns structured scan/sources/conflict data and final ok status.
cfgstack version
cfgstack --version
cfgstack version --jsonJSON fields:
nameversioncommitdate
Enable validation:
cfgstack build --in config --profile prod --out merged.json --schema schema.jsonSupported schema refs:
- local path:
schema.json - file URL:
file:///.../schema.json - HTTP(S):
https://...
Validation errors include:
- JSON Pointer;
- nested causes;
- value context;
- nearest provenance source.
0success1runtime/validation/doctor failure2CLI argument error
- CI:
.github/workflows/ci.ymlgo mod tidycheckgo vet ./...go test ./...- release-build smoke check
- Release:
.github/workflows/release.yml- triggered by tags
v* - builds Linux/macOS/Windows binaries
- publishes artifacts to GitHub Release
- triggered by tags
cmd/cfgstack/main.go - entrypoint
internal/cli - commands and flags
internal/config - layer discovery and parsing
internal/env - env override application
internal/merge - deep merge
internal/output - output read/write/encode
internal/schema - schema compile/validate
internal/inspect - JSON Pointer utilities and diff
internal/prov - provenance map/history
internal/doctor - diagnostics/reporting
internal/errx - structured errors
Design priorities:
- deterministic behavior;
- explicit override semantics;
- traceability of final values.
If you need a controlled and reproducible configuration pipeline, cfgstack is built for exactly that.