Development to production
This walkthrough builds a setup many teams end up with:
- Development and staging secrets are age-encrypted in
fnox.toml, so cloning the repo is enough to start (given a key). - Production secrets live in AWS Secrets Manager, readable only by deployers and CI.
- The application never knows the difference: it reads
DATABASE_URLandJWT_SECRETeverywhere.
If every environment already lives in a vault, use an existing vault instead.
1. Development
Follow the quick start to create a key and an age provider, adding the public keys of your teammates and a CI key to recipients. Then:
#:schema https://fnox.jdx.dev/schema.json
default_provider = "age"
[providers.age]
type = "age"
recipients = ["age1...", "age1...", "age1..."] # you, a teammate, CI
[secrets]
LOG_LEVEL = { default = "debug" }fnox set DATABASE_URL
fnox set JWT_SECRET
fnox exec -- npm start2. Staging
Write staging values into the staging profile:
fnox set DATABASE_URL --profile staging
fnox set JWT_SECRET --profile staging
fnox exec --profile staging -- npm startThey go under [profiles.staging.secrets]. Staging inherits the age provider and LOG_LEVEL from the top level.
3. Production
Create the production secrets in AWS (named myapp/database-url and myapp/jwt-secret here) with your usual tooling, then reference them:
[profiles.production.providers.aws]
type = "aws-sm"
region = "us-east-1"
prefix = "myapp/"
[profiles.production.secrets]
LOG_LEVEL = { default = "info" }
DATABASE_URL = { provider = "aws", value = "database-url", if_missing = "error" }
JWT_SECRET = { provider = "aws", value = "jwt-secret", if_missing = "error" }fnox.toml contains only names here. Who can read production is decided by AWS IAM, not by the age recipients. With AWS credentials:
fnox check --all --profile production
fnox exec --profile production -- ./deploy.shAdd --no-defaults if production must not inherit any top-level secret it doesn't override.
4. Ignore personal files
printf 'fnox.local.toml\n.fnox.local.toml\n' >> .gitignore
git add fnox.toml .gitignore
git commit -m "chore: add fnox"Developers can now override values in fnox.local.toml, such as pointing DATABASE_URL at a local database.
5. CI
Tests use the development values with the CI age key. Deploys use the production profile with an AWS role:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: jdx/mise-action@v5
- run: fnox exec --if-missing error -- npm test
env:
FNOX_AGE_KEY: ${{ secrets.FNOX_AGE_KEY }}
FNOX_NON_INTERACTIVE: "true"
deploy:
needs: test
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v7
- uses: jdx/mise-action@v5
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy
aws-region: us-east-1
- run: fnox exec --profile production --if-missing error -- ./deploy.sh
env:
FNOX_NON_INTERACTIVE: "true"jdx/mise-action installs fnox if it is in mise.toml (mise use fnox). See CI/CD for other providers and log masking.
6. Onboarding
A new teammate:
- Installs fnox and creates a key:
mkdir -p ~/.config/fnox && age-keygen -o ~/.config/fnox/age.txt. - Sends you the public key.
You add it to recipients, run fnox reencrypt --provider age for the default and staging profiles, and commit. They pull, run fnox check --all, and they're set. Grant AWS access separately if they deploy.
When someone leaves, remove their key, re-encrypt, and rotate the development and staging secrets. See team setup.
See also
- Profiles: inheritance and stacking.
- AWS Secrets Manager: permissions and reference formats.
- Shell integration: load development secrets on
cd.