# Real-World Example
Let's build a complete setup for a typical web application with development, staging, and production environments.
## The Scenario
You're building an API that needs:
- Database URL
- API keys (Stripe, SendGrid)
- JWT secret
- External service URLs
**Requirements:**
- **Development:** Secrets in git (encrypted) so team can clone and run
- **Staging:** Secrets in git (encrypted) with staging values
- **Production:** Secrets in AWS Secrets Manager (never in git)
## Step 1: Initialize
```bash
cd my-api
fnox init
git init
```
## Step 2: Set Up Age Encryption (for Dev/Staging)
```bash
# Generate age key
age-keygen -o ~/.config/fnox/age.txt
# Get your public key
grep "public key:" ~/.config/fnox/age.txt
# Output: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# For teams: collect everyone's public keys and add them all
```
Add to `fnox.toml`:
```toml
# Shared age provider for dev and staging
[providers.age]
type = "age"
recipients = [
"age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p", # alice
"age1pr3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqabc123", # bob
"age1zr3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqdxf456" # ci
]
```
Set decryption key:
```bash
# Add to ~/.bashrc or ~/.zshrc
```toml
[providers]
age = { type = "age", recipients = ["age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"] }
[secrets]
DATABASE_URL = { provider = "age", value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..." }
JWT_SECRET = { provider = "age", value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..." }
STRIPE_KEY = { provider = "age", value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..." }
SENDGRID_KEY = { provider = "age", value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..." }
```
**Commit this!** It's encrypted and safe.
```bash
git add fnox.toml
git commit -m "Add encrypted development secrets"
```
## Step 4: Add Staging Profile
Add staging secrets (also encrypted):
```bash
# Switch to staging profile
fnox set DATABASE_URL "postgresql://staging.db.example.com/mydb" \
--provider age \
--profile staging
fnox set JWT_SECRET "$(openssl rand -hex 32)" \
--provider age \
--profile staging
fnox set STRIPE_KEY "sk_test_staging_xyz" \
--provider age \
--profile staging
fnox set SENDGRID_KEY "SG.staging456" \
--provider age \
--profile staging
```
Your `fnox.toml` now has a staging profile:
```toml
[providers]
age = { type = "age", recipients = ["age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"] }
# Development (default profile)
[secrets]
DATABASE_URL = { provider = "age", value = "..." }
JWT_SECRET = { provider = "age", value = "..." }
# ... other dev secrets ...
# Staging profile
[profiles.staging.secrets]
DATABASE_URL = { provider = "age", value = "..." }
JWT_SECRET = { provider = "age", value = "..." }
# ... other staging secrets ...
```
## Step 5: Add Production Profile (AWS Secrets Manager)
Add production configuration (secrets stored in AWS):
```toml
# Add to fnox.toml
[profiles.production.providers]
aws = { type = "aws-sm", region = "us-east-1", prefix = "myapi/" }
[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "database-url", if_missing = "error" } # Critical secret
JWT_SECRET = { provider = "aws", value = "jwt-secret", if_missing = "error" }
STRIPE_KEY = { provider = "aws", value = "stripe-key", if_missing = "error" }
SENDGRID_KEY = { provider = "aws", value = "sendgrid-key", if_missing = "error" }
```
Create secrets in AWS:
```bash
aws secretsmanager create-secret \
--name "myapi/database-url" \
--secret-string "postgresql://prod.rds.amazonaws.com/mydb"
aws secretsmanager create-secret \
--name "myapi/jwt-secret" \
--secret-string "$(openssl rand -base64 64)"
aws secretsmanager create-secret \
--name "myapi/stripe-key" \
--secret-string "sk_live_REAL_KEY_HERE"
aws secretsmanager create-secret \
--name "myapi/sendgrid-key" \
--secret-string "SG.REAL_KEY_HERE"
```
Commit the production references:
```bash
git add fnox.toml
git commit -m "Add production profile (AWS Secrets Manager)"
```
## Step 6: Local Overrides
Create `.gitignore`:
```bash
cat > .gitignore << 'EOF'
fnox.local.toml
.env
EOF
```
Each developer can create personal overrides:
```toml
# fnox.local.toml (not committed)
[secrets]
DATABASE_URL = { default = "postgresql://localhost/alice_db" } # Personal DB
DEBUG_MODE = { default = "true" } # Enable debugging
```
## Step 7: Use It
### Development
```bash
# Enable shell integration
eval "$(fnox activate bash)"
echo 'eval "$(fnox activate bash)"' >> ~/.bashrc
# Navigate to project (secrets auto-load)
cd my-api
# fnox: +4 DATABASE_URL, JWT_SECRET, STRIPE_KEY, SENDGRID_KEY
# Run the app
npm run dev
```
Or explicitly:
```bash
fnox exec -- npm run dev
```
### Staging
```bash
# Deploy to staging
fnox exec --profile staging -- ./deploy.sh
# Or set profile for session
export FNOX_PROFILE=staging
fnox exec -- ./deploy.sh
```
### Production
```bash
# Ensure AWS credentials are set
export AWS_REGION=us-east-1
# Deploy to production
fnox exec --profile production -- ./deploy.sh
```
## Step 8: CI/CD Setup
### GitHub Actions
```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3 # Installs fnox via mise
# Decrypt dev secrets for testing
- name: Setup fnox
env:
FNOX_AGE_KEY: ${{ secrets.FNOX_AGE_KEY }}
run: |
# Use the CI age key to decrypt secrets
echo "FNOX_AGE_KEY is already set from secrets"
- name: Run tests
run: |
fnox exec -- npm test
deploy-staging:
if: github.ref == 'refs/heads/develop'
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- name: Deploy to staging
env:
FNOX_AGE_KEY: ${{ secrets.FNOX_AGE_KEY }}
run: |
fnox exec --profile staging -- ./deploy.sh
deploy-production:
if: github.ref == 'refs/heads/main'
needs: test
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- name: Deploy to production
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: us-east-1
FNOX_IF_MISSING: error # Fail if any secret is missing
run: |
fnox exec --profile production -- ./deploy.sh
```
### Set GitHub Secrets
1. Go to your repo → Settings → Secrets → Actions
2. Add `FNOX_AGE_KEY`:
```bash
cat ~/.config/fnox/ci-age.txt | grep "AGE-SECRET-KEY"
```
3. Add `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` for production
## Step 9: Team Onboarding
New team member joins:
```bash
# 1. Clone the repo
git clone https://github.com/myorg/my-api
cd my-api
# 2. Install fnox (via mise)
mise install
# 3. Generate age key
age-keygen -o ~/.config/fnox/age.txt
# 4. Share public key with team
grep "public key:" ~/.config/fnox/age.txt
# Send to team lead to add to fnox.toml recipients
# 5. Set decryption key
# 6. Enable shell integration
echo 'eval "$(fnox activate bash)"' >> ~/.bashrc
# 7. Team lead updates fnox.toml with new recipient
# Then re-encrypts all secrets:
fnox reencrypt -p age
# 8. New team member pulls and runs
git pull
cd my-api
# fnox: +4 DATABASE_URL, JWT_SECRET, STRIPE_KEY, SENDGRID_KEY
npm run dev # Just works!
```
## File Structure
```
my-api/
├── .gitignore # fnox.local.toml, .env
├── fnox.toml # Committed (encrypted dev/staging, AWS refs for prod)
├── fnox.local.toml # Gitignored (personal overrides)
├── package.json
├── src/
└── .github/
└── workflows/
└── ci.yml # CI/CD with fnox
```
## Next Steps
- [Providers](/providers/overview) - Explore other providers
- [Shell Integration](/guide/shell-integration) - Advanced shell setup
- [Hierarchical Config](/guide/hierarchical-config) - Organize larger projects