Profiles
Profiles let you manage secrets for different environments (dev, staging, production) in a single fnox.toml file.
Basic Usage
Define environment-specific secrets using profiles:
# Default profile (development)
[secrets]
API_URL = { default = "http://localhost:3000" }
DATABASE_URL = { provider = "age", value = "encrypted-dev-db..." }
# Staging profile
[profiles.staging.secrets]
API_URL = { default = "https://staging.example.com" }
DATABASE_URL = { provider = "age", value = "encrypted-staging-db..." }
# Production profile
[profiles.production.secrets]
API_URL = { default = "https://api.example.com" }
DATABASE_URL = { provider = "aws", value = "prod-database-url" } # Stored in AWS Secrets ManagerUsing Profiles
Via Command Line
# Use default profile
fnox get API_URL
# Use specific profile
fnox get API_URL --profile staging
fnox exec --profile production -- ./deploy.shVia Environment Variable
# Set once for the session
export FNOX_PROFILE=production
# All commands use production profile
fnox get DATABASE_URL
fnox exec -- node server.jsWith Shell Integration
# Enable shell integration
eval "$(fnox activate bash)"
# Switch profiles
export FNOX_PROFILE=production
cd my-app # Loads production secrets
export FNOX_PROFILE=staging
# fnox detects the change on the next prompt automaticallyComposing Multiple Profiles
You can activate multiple profiles at the same time as an ordered overlay stack. Later profiles override earlier ones on key conflicts, with the top-level config as the base.
# Repeatable flags or comma-separated
fnox -P aws -P prod exec -- ./app
fnox -P aws,prod exec -- ./app
# Via environment variable
export FNOX_PROFILE=aws,prodWhen multiple profiles are active, write commands (set, remove, import, sync, provider add/remove) require an explicit --write-profile <NAME> to choose the write target:
# Reads from aws + prod overlay, writes to prod
fnox -P aws -P prod --write-profile prod set DATABASE_URL "value"With a single active profile, the write target defaults to that profile and --write-profile is not needed.
When to Use Composition
Composition is useful when concerns are split across profiles:
awsprovides cloud provider configurationprodprovides production secret mappingsciadds CI-only secretslocaloverrides a few values for local development
Profile Inheritance
Profiles automatically inherit secrets from the top level:
# Define once - all profiles inherit
[secrets]
LOG_LEVEL = { default = "info" }
API_TIMEOUT = { default = "30" }
DATABASE_URL = { provider = "age", value = "encrypted-dev-db..." }
# Staging inherits all top-level secrets
[profiles.staging]
# Automatically gets: LOG_LEVEL, API_TIMEOUT, DATABASE_URL
# Production overrides specific secrets, inherits the rest
[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "prod-db" } # Overrides DATABASE_URL
LOG_LEVEL = { default = "warn" } # Overrides LOG_LEVEL
# Still inherits API_TIMEOUT="30" from top levelThis reduces duplication for secrets shared across environments.
Profile-Specific Providers
Each profile can have its own providers:
# Default providers (for development)
[providers]
age = { type = "age", recipients = ["age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"] }
# Production profile with AWS providers
[profiles.production]
[profiles.production.providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapp/" }
[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "database-url" }Secret References in Provider Config
Provider configuration properties can reference secrets using { secret = "NAME" }. This enables bootstrap scenarios where provider credentials are themselves managed as secrets:
[providers.age]
type = "age"
recipients = ["age1..."]
[providers.vault]
type = "vault"
address = "http://vault.example.com:8200"
token = { secret = "VAULT_TOKEN" } # Resolved from secrets or env var
[secrets]
VAULT_TOKEN = { provider = "age", value = "AGE-ENCRYPTED-TOKEN..." }
DATABASE_URL = { provider = "vault", value = "database/creds/myapp" }Resolution order: config secrets first, then environment variables. fnox detects circular dependencies and errors if found.
List Profiles
See all available profiles:
fnox profilesOutput:
default (active)
staging
productionCommon Patterns
Development + Production
# Development (default): encrypted in git
[providers]
age = { type = "age", recipients = ["age1..."] }
[secrets]
DATABASE_URL = { provider = "age", value = "encrypted..." }
# Production: AWS Secrets Manager
[profiles.production.providers]
aws = { type = "aws-sm", region = "us-east-1" }
[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "database-url" }Multi-Region Production
[profiles.production-us.providers]
aws = { type = "aws-sm", region = "us-east-1" }
[profiles.production-eu.providers]
aws = { type = "aws-sm", region = "eu-west-1" }Per-Developer Profiles
[profiles.alice]
[profiles.alice.secrets]
DATABASE_URL = { default = "postgresql://localhost/alice_db" }
[profiles.bob]
[profiles.bob.secrets]
DATABASE_URL = { default = "postgresql://localhost/bob_db" }export FNOX_PROFILE=alice
fnox exec -- npm startCI/CD Example
# .github/workflows/deploy.yml
jobs:
deploy-staging:
runs-on: ubuntu-latest
steps:
- run: fnox exec --profile staging -- ./deploy.sh
deploy-production:
runs-on: ubuntu-latest
environment: production
steps:
- run: fnox exec --profile production -- ./deploy.shNext Steps
- Hierarchical Config - Organize configs across directories (includes local overrides)
- Real-World Example - Complete multi-environment setup