Skip to content

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
export FNOX_AGE_KEY=$(cat ~/.config/fnox/age.txt | grep "AGE-SECRET-KEY")

Step 3: Add Development Secrets

bash
# Encrypt development secrets
fnox set DATABASE_URL "postgresql://localhost/mydb" --provider age
fnox set JWT_SECRET "$(openssl rand -hex 32)" --provider age
fnox set STRIPE_KEY "sk_test_abc123" --provider age
fnox set SENDGRID_KEY "SG.test123" --provider age

Your fnox.toml now contains encrypted secrets:

toml
[providers.age]
type = "age"
recipients = ["age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"]

[secrets.DATABASE_URL]
provider = "age"
value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..."

[secrets.JWT_SECRET]
provider = "age"
value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..."

[secrets.STRIPE_KEY]
provider = "age"
value = "YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdC..."

[secrets.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 = "..."

[secrets.JWT_SECRET]
provider = "age"
value = "..."

# ... other dev secrets ...

# Staging profile
[profiles.staging.secrets.DATABASE_URL]
provider = "age"
value = "..."

[profiles.staging.secrets.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

[profiles.production.secrets.JWT_SECRET]
provider = "aws"
value = "jwt-secret"
if_missing = "error"

[profiles.production.secrets.STRIPE_KEY]
provider = "aws"
value = "stripe-key"
if_missing = "error"

[profiles.production.secrets.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

[secrets.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
    # Copy the CI age secret key (from the CI recipient's age.txt)
    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
echo 'export FNOX_AGE_KEY=$(cat ~/.config/fnox/age.txt | grep "AGE-SECRET-KEY")' >> ~/.bashrc
source ~/.bashrc

# 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 set DATABASE_URL "$(fnox get DATABASE_URL)" --provider age
# ... repeat for all secrets

# 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!

What We Achieved

Dev secrets encrypted in git → Team can clone and run immediately ✅ Staging secrets encrypted in git → Safe deployment ✅ Prod secrets in AWS → Never in git, centrally managed ✅ Shell integration → Secrets auto-load on cdLocal overrides → Each developer can customize ✅ CI/CD ready → Tests run with dev secrets, deploys use appropriate profiles ✅ Team-friendly → Simple onboarding, no manual secret sharing

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

Released under the MIT License.