Migration Guide
Bruno v4 changes where secret manager configuration lives. This guide explains what changed, how migration works, and what to watch for.
What changed
| Before v4 | v4+ | |
|---|---|---|
| Config location | secrets.json at the collection root |
externalSecrets block inside environment files (.bru / environment.yml) |
| Configured from | Collection Settings → Secrets tab | Environment UI → External Secrets |
| Variable syntax | {{$secrets.name.keyname}} |
{{name.keyname}} (recommended) |
| Legacy syntax | — | {{$secrets.name.keyname}} still resolves, shown with a deprecation underline |
| CLI migration | Not needed | CLI warns if secrets.json exists but environment file has no externalSecrets — open the app to migrate |
| Providers | HashiCorp Vault only (CLI) | HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager |
How migration works
In the app (automatic)
- Open any collection that still has a
secrets.jsonfile. - Bruno detects the old format and migrates the configuration to the environment file in the background.
- The
externalSecretsblock appears in the relevant environment file. The originalsecrets.jsonis no longer used. - Collection Settings → Secrets tab shows a notice that Secret Manager has moved to the Environment UI, with a button to open it directly.
No manual steps are needed. The migration runs once and is non-destructive.
secrets.json is marked read-only on disk, the migration will fail. Bruno currently has no fallback for this scenario. You will see a "start a fresh" state and will need to manually re-enter your secret configuration in the Environment UI. This situation is rare but worth checking if migration does not complete as expected: verify that secrets.json is writable before opening the collection.
In the CLI (manual trigger via app)
The CLI does not auto-migrate. If it finds a secrets.json but no externalSecrets block in the environment file, it prints a warning:
[warn] secrets.json found but no externalSecrets in environment file.
Open the collection in the Bruno app to migrate.
Fix: Open the collection in the Bruno app, let the automatic migration run, commit the updated environment file, then re-run the CLI.
Before and after
secrets.json (pre-v4)
{
"type": "vault",
"cli": {
"type": "vault-server",
"vaultServerConfig": {
"url": "http://localhost:8200",
"namespace": "bruno",
"auth": {
"method": "token",
"token": "{{authToken}}"
}
}
},
"data": [
{
"environment": "Production",
"secrets": [
{ "name": "db", "path": "secret/db", "enabled": true }
]
}
]
}
Configuration lives at the collection root. All environments share the same vault connection; per-environment secret paths are listed in data[].secrets.
Environment file (v4+)
Configuration is embedded directly in the environment file. Each environment can point to a different vault, region, or key vault independently.
```yaml production.yml name: Production variables: - name: baseUrl value: https://api.example.com externalSecrets: type: aws-secrets-manager variables: - name: db secretName: prod/db/credentials enabled: true
```bru production.bru
vars {
baseUrl: https://api.example.com
}
vars:externalsecrets:aws-secrets-manager {
db: prod/db/credentials
}
Variable reference in requests
Before: {{$secrets.db.password}}
After: {{db.password}}
Both work in v4. The old syntax is shown with a deprecation underline in the editor.
Step-by-step migration (app)
secrets.json.
secrets.json on open and silently migrates the configuration into the correct environment files. A notice appears in Collection Settings → Secrets confirming the move.
{{$secrets.name.keyname}} usage in your request URLs, headers, bodies, and scripts. Replace them with the new {{name.keyname}} syntax. The old syntax still works and will continue to do so until the next major release, but Bruno marks it with a deprecation underline in the editor.
**From v4.2.0:** Bruno provides a one-click in-app migration tool that automatically rewrites all `{{$secrets.*}}` references across your collection to the new syntax. You do not need to do this manually if you can wait for v4.2.0.
externalSecrets block is now in your environment .yml or .bru file. Commit it to version control so the CLI and other team members can use it.
secrets.json from the collection root. Bruno no longer reads it in v4.
Step-by-step migration (CLI / CI)
externalSecrets block appears in the environment file, then commit and push.
**HashiCorp Vault (token)**
```bash
# pass via --env-var at runtime, no credential file needed
```
**AWS Secrets Manager**
```bash
BRUNO_AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
BRUNO_AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
BRUNO_AWS_REGION=us-east-1
```
**Azure Key Vault (Service Principal)**
```bash
BRUNO_AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BRUNO_AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BRUNO_AZURE_CLIENT_SECRET=your-client-secret
```
**Google Cloud Secret Manager (service account)**
```bash
BRUNO_GCP_SECRETS_MANAGER_PROJECT_ID=my-project
BRUNO_GCP_SECRETS_MANAGER_CLIENT_EMAIL=service-account@my-project.iam.gserviceaccount.com
BRUNO_GCP_SECRETS_MANAGER_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
```
bash
bru run collection/ --env Production --env-var authToken=my-token
New command (v4+):
```bash
bru run collection/ --env Production --secrets-env-file ./secrets.env
```
secrets.json injection steps with --secrets-env-file. Store credentials in your CI secret store (GitHub Actions secrets, GitLab CI variables, etc.) and write them to a temp file before the bru run step.
Tips
The CLI will not run if secrets.json exists without a migrated environment file.
The CLI prints a warning and continues without secrets — it does not fail hard, but requests that depend on secrets will receive empty values. Fix: open the collection in the app first.
Each environment has its own externalSecrets block.
In v3, all environments shared one secrets.json. In v4, each environment file declares its own secrets. If you have multiple environments (Dev, Staging, Production) pointing to different vaults or regions, update each one independently in the Environment UI.
secrets.json is not automatically deleted.
After migration Bruno stops reading it, but the file stays on disk. Clean it up manually once you have verified the migration and confirmed nothing else depends on it.
Variable syntax change is not auto-applied to request files (until v4.2.0).
The old {{$secrets.name.keyname}} syntax still resolves in v4 and will continue to work until the next major release. Bruno marks usages with a deprecation underline in the editor. You can update references manually by searching for $secrets across your collection, or wait for the v4.2.0 in-app migration tool which will rewrite all occurrences automatically with one click.
runtimeVariables overrides do not bypass secret resolution.
Secret variables are resolved first. If a secret and a runtime variable share the same name, the secret value takes precedence (external secrets sit above runtime vars in the precedence chain).
The --secrets-env-file flag is required for AWS, Azure, and GCP in CI.
Unlike HashiCorp Vault (which can use --env-var for token auth), AWS, Azure, and Google Cloud credentials must be passed via --secrets-env-file. Flat --env-var flags are not read by those credential resolvers.