From development to production
Use age-encrypted development and staging values in git, then switch to AWS Secrets Manager for production. This example shows how the pieces fit together without requiring the application to know which provider it uses.
If all environments use an existing vault, start with connect a vault instead.
Prerequisites
- fnox and age installed; see the quick start.
- A project with a development command, such as
npm start. - An age identity for each teammate and for CI.
- AWS credentials, an AWS region, and permission to read the production secrets.
The example uses two secrets, DATABASE_URL and JWT_SECRET, plus the non-sensitive LOG_LEVEL setting. Replace names and commands with those used by your application.
1. Configure development encryption
Create the project configuration with fnox init --skip-wizard, then edit fnox.toml:
#:schema https://fnox.jdx.dev/schema.json
default_provider = "age"
[providers.age]
type = "age"
recipients = ["age1..."] # Replace with the public recipients of your team and CI
[secrets]
LOG_LEVEL = { default = "debug" }Each teammate keeps their own private identity outside the repository. fnox reads age.txt from its configuration directory by default. See age team setup for other identity locations and multiple recipients.
Store the development values using hidden prompts:
fnox set DATABASE_URL
fnox set JWT_SECRET
fnox check --all
fnox exec -- npm startfnox set adds encrypted values to fnox.toml. Review the file, then commit it. LOG_LEVEL remains plaintext because it is a default.
2. Add staging values
Select the staging profile when writing:
fnox set DATABASE_URL --profile staging --provider age
fnox set JWT_SECRET --profile staging --provider agefnox writes under [profiles.staging.secrets]. Staging inherits LOG_LEVEL and the age provider from the top level.
fnox check --all --profile staging
fnox exec --profile staging -- ./deploy.shA profile changes configuration; it does not restrict who can decrypt. Use separate encryption providers and recipients if staging requires a different access boundary.
3. Reference production secrets
Create myapi/database-url and myapi/jwt-secret in AWS Secrets Manager using your normal provisioning process. Add the provider and references:
[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" }
JWT_SECRET = { provider = "aws", value = "jwt-secret", if_missing = "error" }
LOG_LEVEL = { default = "info" }These values are names, not ciphertext. No production plaintext goes into the config.
After authenticating to AWS:
fnox check --all --profile production
fnox exec --profile production --if-missing error -- ./deploy.shUse --no-defaults when you want production to include only its selected profile secrets. Otherwise it inherits any top-level secrets it does not override.
4. Keep personal overrides local
Add these patterns to the project's existing .gitignore:
fnox.local.toml
.fnox.local.toml
.envA developer can create fnox.local.toml:
[secrets]
DATABASE_URL = { default = "postgresql://localhost/alice_db" }This local example has no password. Use an encryption provider or vault reference for a sensitive override.
Inspect which files and definitions are active:
fnox config-files
fnox list --sources5. Run in CI
Install fnox and the application toolchain in the workflow before these steps. If mise manages the project tools, include fnox in mise.toml so the installation step knows to install it.
For development tests, supply the dedicated CI age identity through the CI secret store:
- name: Test
env:
FNOX_AGE_KEY: ${{ secrets.FNOX_AGE_KEY }}
run: fnox --non-interactive exec --if-missing error -- npm testThe CI public recipient must have been included when the development secrets were encrypted. Adding it later requires re-encryption by someone who can already decrypt.
For production, authenticate the runner to AWS first, then run:
- name: Deploy
run: fnox --non-interactive exec --profile production --if-missing error -- ./deploy.shThe AWS Secrets Manager guide covers credentials and read permissions. Never assume that setting AWS_REGION alone authenticates a runner.
6. Onboard a teammate
The teammate installs fnox and creates a personal age identity.
They share only the public recipient, from
age-keygen -y ~/.config/fnox/age.txt.A teammate with existing decryption access adds the recipient and re-encrypts each affected profile:
shfnox reencrypt --provider age --profile default fnox reencrypt --provider age --profile stagingCommit the new recipients and ciphertext. The new teammate pulls the changes and runs
fnox check --all.Grant production vault access separately if their role requires it.
Removing an age recipient affects future ciphertext only. Previously accessible secrets in git history remain decryptable, so rotate the underlying values when access must end.
Next steps
- Shell integration: load development secrets on directory change.
- Hierarchical configuration: split common and service-specific settings.
- Import and export: migrate existing
.envvalues. - Credential leases: use temporary credentials for supported services.