Foundry provides a flexible secret management system with support for multiple sources and instance-scoped secrets.
Foundry resolves secrets from multiple sources in order:
- Environment variables
~/.foundryvarsfile- OpenBAO (planned for Phase 2)
Secrets are resolved at deployment time, not during configuration validation.
In configuration files, reference secrets using:
${secret:path/to/secret:key}Components:
path/to/secret: The path to the secret (can have multiple levels)key: The specific key within that secret
Examples:
${secret:database/main:password}
${secret:api/github:token}
${secret:ssl/wildcard:cert}Secrets are scoped to instances, allowing the same configuration to be used for multiple deployments (e.g., production, staging).
When Foundry resolves a secret:
- Config contains:
${secret:database/main:password} - Instance context is provided:
myapp-prod - Foundry looks for:
myapp-prod/database/main:password
This allows:
# Same config, different secrets
foundry deploy myapp --instance myapp-prod
foundry deploy myapp --instance myapp-stagingSet secrets as environment variables:
# Format: FOUNDRY_SECRET_<instance>_<path>_<key>
# Slashes, hyphens, and colons become underscores
export FOUNDRY_SECRET_myapp_prod_database_main_password="prod-secret-123"
export FOUNDRY_SECRET_myapp_staging_database_main_password="staging-secret-456"Example:
- Instance:
myapp-prod - Path:
database/main - Key:
password - Env var:
FOUNDRY_SECRET_myapp_prod_database_main_password
Create ~/.foundryvars for local development:
# Format: instance/path:key=value
# One secret per line
# Comments start with #
# Production secrets
myapp-prod/database/main:password=prod-secret-123
myapp-prod/api/github:token=ghp_prod_token
# Staging secrets
myapp-stable/database/main:password=staging-secret-456
myapp-stable/api/github:token=ghp_staging_token
# Foundry core secrets
foundry-core/openbao:token=root-tokenSecurity:
# Set restrictive permissions
chmod 600 ~/.foundryvars
# Never commit to git
echo ".foundryvars" >> ~/.gitignoreOpenBAO integration is planned for Phase 2. It will follow the same instance-scoped path structure:
openbao kv put myapp-prod/database/main password=secret123
openbao kv put myapp-staging/database/main password=different-secret
Foundry tries each source in order and returns the first match:
- Environment Variables - Highest priority
- ~/.foundryvars - Development/local use
- OpenBAO - Production use (Phase 2)
If all sources fail, deployment fails with a clear error message.
During foundry config validate, Foundry:
- ✅ Validates secret reference syntax
- ✅ Ensures references are well-formed
- ❌ Does NOT resolve actual secrets
This allows you to validate configs without having secrets available.
# This validates syntax only
foundry config validate
# Output:
# ✓ Config structure valid
# ✓ Secret references syntax valid
# ✓ Configuration is validSecrets are resolved during deployment when instance context is known:
# During deployment, instance context is provided
foundry deploy myapp --instance myapp-prod
# Foundry resolves:
# ${secret:database/main:password}
# → myapp-prod/database/main:password
# → "actual-secret-value"Organize secrets with clear paths:
✅ Good:
database/postgres:password
database/postgres:username
api/github:token
api/slack:webhook
❌ Bad:
db:pass
secret1:value
prod:key
# Add to .gitignore
echo ".foundryvars" >> ~/.gitignore
echo "*.env" >> ~/.gitignore
# Check before committing
git diff
grep -r "password" .Separate secrets by instance:
# ~/.foundryvars
myapp-prod/database/main:password=prod-secret
myapp-staging/database/main:password=staging-secret
myapp-dev/database/main:password=dev-secret# Update secret in all sources
vi ~/.foundryvars
export FOUNDRY_SECRET_myapp_prod_api_token="new-token"
# Redeploy to apply
foundry deploy myapp --instance myapp-prodFor CI/CD pipelines, use environment variables:
# .github/workflows/deploy.yml
env:
FOUNDRY_SECRET_myapp_prod_database_password: ${{ secrets.DB_PASSWORD }}
FOUNDRY_SECRET_myapp_prod_api_token: ${{ secrets.API_TOKEN }}Use ~/.foundryvars for local development:
# 1. Create secrets file
cat > ~/.foundryvars <<EOF
myapp-dev/database/main:password=dev-password
myapp-dev/api/key:value=dev-api-key
EOF
chmod 600 ~/.foundryvars
# 2. Test secret resolution
foundry deploy myapp --instance myapp-dev --dry-runUse environment variables in CI/CD:
# In CI/CD environment
export FOUNDRY_SECRET_myapp_prod_database_password="${DB_PASSWORD}"
export FOUNDRY_SECRET_myapp_prod_api_token="${API_TOKEN}"
foundry deploy myapp --instance myapp-prodUse OpenBAO (Phase 2):
# Store in OpenBAO
openbao kv put myapp-prod/database/main password="${DB_PASSWORD}"
# Foundry automatically retrieves from OpenBAO
foundry deploy myapp --instance myapp-prod# config.yaml
components:
postgres:
config:
host: "db.example.com"
port: 5432
username: "${secret:database/postgres:username}"
password: "${secret:database/postgres:password}"
database: "${secret:database/postgres:dbname}"# ~/.foundryvars
myapp-prod/database/postgres:username=dbuser
myapp-prod/database/postgres:password=secure-password
myapp-prod/database/postgres:dbname=myapp_production# config.yaml
components:
api-service:
config:
github_token: "${secret:api/github:token}"
slack_webhook: "${secret:api/slack:webhook}"
stripe_key: "${secret:api/stripe:secret_key}"# ~/.foundryvars
myapp-prod/api/github:token=ghp_xxxxxxxxxxxxx
myapp-prod/api/slack:webhook=https://hooks.slack.com/xxxxx
myapp-prod/api/stripe:secret_key=sk_live_xxxxxx# config.yaml
components:
ingress:
config:
tls_cert: "${secret:tls/wildcard:cert}"
tls_key: "${secret:tls/wildcard:key}"Check resolution order:
# 1. Check environment variables
env | grep FOUNDRY_SECRET
# 2. Check .foundryvars
cat ~/.foundryvars | grep "path:key"
# 3. Verify instance context matches
# If deploying with --instance myapp-prod
# Secret must be: myapp-prod/path:keyEnsure instance context is correct:
# Check which instance you're deploying
foundry deploy myapp --instance myapp-prod
# Ensure secrets match instance
# myapp-prod/database:password ← Correct
# myapp-staging/database:password ← Wrong instance# Fix permissions
chmod 600 ~/.foundryvars
# Verify
ls -la ~/.foundryvars
# Should show: -rw------- (600)- Never commit secrets to git
- Use restrictive permissions (
chmod 600for.foundryvars) - Rotate secrets regularly
- Use different secrets per environment
- Use OpenBAO for production (when available)
- Audit secret access
Phase 2 will add:
- ✅ OpenBAO integration
- ✅ Dynamic secret rotation
- ✅ Secret encryption at rest
- ✅ Audit logging for secret access
- ✅ Role-based access control
- Read the Configuration Guide
- Read the Getting Started Guide
- Review example configs in
test/fixtures/