Hierarchical Configuration
fnox searches parent directories for fnox.toml files and merges them. This is perfect for monorepos and multi-service projects.
How It Works
fnox walks up the directory tree from your current location and merges all fnox.toml files it finds:
project/
├── fnox.toml # Root config
└── services/
├── api/
│ └── fnox.toml # API config
└── worker/
└── fnox.toml # Worker configWhen you run fnox from project/services/api/:
- Loads
project/fnox.toml(parent) - Loads
project/services/api/fnox.toml(current) - Merges them (child overrides parent)
Example Setup
Root Config (Common Secrets)
toml
# project/fnox.toml
# Shared age provider
[providers.age]
type = "age"
recipients = ["age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"]
# Shared secrets
[secrets.LOG_LEVEL]
default = "info"
[secrets.ENVIRONMENT]
default = "development"
[secrets.JWT_SECRET]
provider = "age"
value = "encrypted-shared-jwt..."API Service Config
toml
# project/services/api/fnox.toml
# API-specific secrets
[secrets.API_PORT]
default = "3000"
[secrets.DATABASE_URL]
provider = "age"
value = "encrypted-api-db..."
# Override shared secret for API
[secrets.LOG_LEVEL]
default = "debug" # More verbose for API during devWorker Service Config
toml
# project/services/worker/fnox.toml
# Worker-specific secrets
[secrets.QUEUE_URL]
provider = "age"
value = "encrypted-queue-url..."
[secrets.WORKER_CONCURRENCY]
default = "4"Resulting Secrets
From project/services/api/:
bash
fnox list
# ENVIRONMENT=development (from root)
# JWT_SECRET=*** (from root)
# LOG_LEVEL=debug (from api, overrides root)
# API_PORT=3000 (from api)
# DATABASE_URL=*** (from api)From project/services/worker/:
bash
fnox list
# ENVIRONMENT=development (from root)
# JWT_SECRET=*** (from root)
# LOG_LEVEL=info (from root)
# QUEUE_URL=*** (from worker)
# WORKER_CONCURRENCY=4 (from worker)Monorepo Pattern
A typical monorepo setup:
monorepo/
├── fnox.toml # Global: age provider, shared secrets
├── apps/
│ ├── web/
│ │ └── fnox.toml # Web app secrets
│ └── mobile/
│ └── fnox.toml # Mobile app secrets
└── services/
├── api/
│ └── fnox.toml # API service secrets
└── workers/
├── fnox.toml # Shared worker config
├── email/
│ └── fnox.toml # Email worker secrets
└── analytics/
└── fnox.toml # Analytics worker secretsRoot Config
toml
# monorepo/fnox.toml
[providers.age]
type = "age"
recipients = [
"age1alice...",
"age1bob...",
"age1charlie..."
]
# Shared across all services
[secrets.SENTRY_DSN]
provider = "age"
value = "encrypted-sentry..."
[secrets.LOG_LEVEL]
default = "info"Each subdirectory inherits the age provider and shared secrets, then adds its own.
Local Config with Hierarchy
Both fnox.toml and fnox.local.toml are merged at each level:
project/
├── fnox.toml
├── fnox.local.toml # Local overrides for root
└── services/
└── api/
├── fnox.toml
└── fnox.local.toml # Local overrides for apiMerge order (lowest to highest priority):
project/fnox.tomlproject/fnox.local.tomlproject/services/api/fnox.tomlproject/services/api/fnox.local.toml
Imports vs Hierarchy
Hierarchy (automatic):
- Walks up directory tree
- Merges all
fnox.tomlfiles found - Child overrides parent
Imports (explicit):
toml
# Explicit file imports
imports = ["./shared/secrets.toml", "./envs/dev.toml"]Use hierarchy for location-based config (monorepos). Use imports for cross-cutting concerns (shared secret bundles).
Tips
- Keep root config minimal: Only shared providers and secrets
- Service-specific secrets in subdirectories: Each service manages its own
- Use local overrides for development: Personal config without affecting team
- Profile inheritance works too: Each level can define profile-specific overrides
Next Steps
- Local Overrides - Per-developer customization
- Profiles - Multi-environment management
- Real-World Example - See it all together