Using Ansible Vault IDs to Isolate Environment Secrets
A single Ansible Vault password shared across dev and prod creates a wide blast radius. Vault-ids let you label encrypted files by environment and unlock only the secrets a playbook needs.
24 Jan 2026, 12:53 UTC

The shared-password bottleneck
If your team protects every Ansible variable file with the same vault password, you have created a single point of failure. Rotating a database credential means re-encrypting files across development, staging, and production at the same time. Onboarding a contractor means handing over the keys to production because there is no narrower credential to give. The blast radius is the entire repository.
Ansible vault-ids offer a more granular model. Instead of one universal password, you label each encrypted file with an identity such as dev or prod. A playbook run supplies only the identities it needs, and Ansible decrypts matching files at runtime. Variables remain ordinary YAML once decrypted, so your tasks do not change.
How vault-ids change the model
A vault-id is simply a label attached to ciphertext. When you encrypt a file, you choose the label:
ansible-vault encrypt --encrypt-vault-id dev group_vars/dev/vault.ymlWhen you run a playbook, you map that label to a password source—either a file on the control node or an interactive prompt:
ansible-playbook site.yml --vault-id dev@~/.vault/dev_password
ansible-playbook site.yml --vault-id prod@promptThe feature has been stable since Ansible 2.4 and is part of modern ansible-core distributions. Because the label travels with the encrypted file, you can commit ciphertext for all environments to version control and unlock only the subset relevant to a given run.
A worked example: dev and prod in one repository
Assume an inventory with two groups, dev and prod. Store environment secrets in separate files so the labels stay obvious:
group_vars/
├── dev/
│ └── vault.yml
└── prod/
└── vault.ymlEncrypt each file with its own identity. Run these commands on your control node; the password files should exist before you start and be readable only by the user executing playbooks.
# Create password files with restrictive permissions
umask 077
echo 'correct horse battery staple' > ~/.vault/dev_password
echo 'another unique prod secret' > ~/.vault/prod_password
# Encrypt environment-specific files
ansible-vault encrypt --encrypt-vault-id dev group_vars/dev/vault.yml
ansible-vault encrypt --encrypt-vault-id prod group_vars/prod/vault.ymlTo avoid repeating flags, define the development identity once in ansible.cfg:
[defaults]
vault_identity_list = dev@~/.vault/dev_passwordWith that entry, a developer can run the playbook against dev without any extra arguments:
ansible-playbook site.yml -i inventory/devFor production, supply the identity explicitly so the password is never stored in a shared config file:
ansible-playbook site.yml -i inventory/prod --vault-id prod@promptVerifying before you trust it
Confirm that the correct password decrypts the intended file. This command prints plaintext to your terminal, so run it only on a trusted control node:
ansible-vault view --vault-id dev@~/.vault/dev_password group_vars/dev/vault.ymlTo test decryption end-to-end without changing managed nodes, run a targeted playbook in check mode against a staging copy of the inventory:
ansible-playbook site.yml -i inventory/staging --vault-id prod@prompt --checkIf Ansible cannot locate a matching identity, it will raise a decryption error before tasks execute. That failure usually means the label used at encryption time does not match the label supplied at runtime.
What vault-ids do not solve
Vault-ids narrow the blast radius, but they introduce their own operational edges.
| Limitation | Practical impact |
|---|---|
| Password distribution | If the production password is written to a CI variable or a shared file, the isolation benefit shrinks. The password still has to live somewhere the pipeline can read. |
| Variable names remain visible | Vault-ids encrypt values, not keys. A file containing api_key: !vault | still reveals that an API key exists. |
| Logging leaks | Not every module honors no_log by default. Decrypted secrets can surface in verbose output or callback plugins unless you explicitly suppress them. |
| Multiple identities in one run | Supplying several --vault-id flags does not make every password available to every file. A file encrypted with the dev label is decrypted only by the dev identity, even if prod is also provided. |
Adopting vault-ids safely
Start by inventorying which variables actually differ between environments. Split those into per-environment files, encrypt them with distinct labels, and commit only the ciphertext. Keep password files out of version control and restrict them with chmod 600 on the control node.
Test the boundary before you rely on it: confirm that a run with only the dev identity fails cleanly when it encounters a prod-encrypted file, and that adding the correct identity resolves it. That check tells you the labels are wired correctly and that no stray plaintext is hiding in your repository.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.