This is the fourth post in our Ansible fundamentals series, building on variables and facts.
Every Ansible codebase eventually needs to manage a secret — a database password, an API key, a TLS private key — as a variable, the same way it manages everything else. The problem is that "the same way it manages everything else" usually means a plaintext value in a YAML file, and YAML files get committed to git. Ansible Vault is the built-in answer: it encrypts a file, or a specific value, so it can live in version control safely.
Encrypting a whole file
ansible-vault create group_vars/production/vault.ymlThis opens an editor, and whatever you save is encrypted on disk:
# group_vars/production/vault.yml (before encryption, as you type it)
vault_db_password: "S3cur3-Actual-Password"
vault_api_key: "sk-abc123..."What's actually written to disk is unreadable without the vault password:
$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653063336164663966303231363934653561363964
3633626534306535646432653766386130663534366231610a626438346336353833
...
To edit it again later:
ansible-vault edit group_vars/production/vault.ymlAnd to view it without opening an editor:
ansible-vault view group_vars/production/vault.ymlThe pattern that actually matters: vault variables vs. regular variables
Encrypting the entire file that also contains your non-secret configuration means every trivial change — bumping a worker count, adjusting a timeout — requires touching an encrypted file, which makes diffs unreadable in a pull request and makes "what actually changed" much harder to review.
The pattern most Ansible codebases converge on instead: keep an encrypted file containing only secrets, with a consistent naming convention (a vault_ prefix is the common one), and a plaintext file that references those encrypted values by name.
# group_vars/production/vault.yml (encrypted)
vault_db_password: "S3cur3-Actual-Password"
vault_api_key: "sk-abc123..."# group_vars/production/vars.yml (plaintext, committed as-is)
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"Tasks reference db_password, never vault_db_password directly. The result: the plaintext file is fully readable and diffable in every pull request — you can see exactly which variable names changed — while the actual secret values stay encrypted in a separate file that rarely needs to change and rarely needs reviewing beyond "did the right person update it."
Running a playbook that needs Vault-encrypted variables
ansible-playbook -i inventory.ini site.yml --ask-vault-passThis prompts for the vault password interactively — fine for a human running it locally, unworkable for CI. Two more practical options:
# A password file (add this file to .gitignore — never commit it)
ansible-playbook -i inventory.ini site.yml --vault-password-file ~/.vault_pass.txt# A script that fetches the password from a secrets manager at run time
ansible-playbook -i inventory.ini site.yml --vault-password-file get-vault-pass.shThat second form is what most real CI pipelines actually use: --vault-password-file accepts any executable, not just a static file, so the vault password itself can come from AWS Secrets Manager, HashiCorp Vault, or your CI platform's own secret store — meaning the Ansible Vault password is never itself sitting in a file on disk anywhere. This is the exact same pattern we used for injecting AWS credentials into GitHub Actions as repository secrets in our own CI/CD migration: the actual secret lives in a dedicated secrets store, and the automation fetches it at run time rather than storing it alongside the code that uses it.
Multiple vault passwords: vault IDs
A single organization often needs different vault passwords for different sensitivity levels or teams — a dev vault password that's shared more broadly, and a production vault password held by a smaller group. Vault IDs support exactly this:
ansible-vault create --vault-id dev@prompt group_vars/dev/vault.yml
ansible-vault create --vault-id production@prompt group_vars/production/vault.ymlansible-playbook site.yml \
--vault-id dev@~/.vault_pass_dev.txt \
--vault-id production@get-vault-pass-prod.shEach encrypted file is tagged internally with which vault ID encrypted it, so a single ansible-playbook run can decrypt files protected by different passwords, sourced differently (a local file for dev, a secrets-manager-backed script for production), in one invocation. This is worth adopting deliberately once "everyone on the team has the one vault password" stops being an acceptable answer for your organization's actual access-control requirements.
Encrypting a single value, not a whole file
For a secret embedded inside an otherwise-plaintext file, ansible-vault encrypt_string encrypts just that one value inline:
ansible-vault encrypt_string 'S3cur3-Actual-Password' --name 'db_password'db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653063336164663966303231363934653561
...That block can be pasted directly into an otherwise-normal, plaintext YAML file — most of the file stays readable, and only the specific encrypted values are unreadable without the vault password. This is a reasonable middle ground for a file that's mostly non-sensitive configuration with one or two genuinely sensitive values mixed in.
Rotating a vault password
Rotating the password that protects Vault-encrypted files — after someone leaves a team, for instance — doesn't require decrypting and re-encrypting every file by hand:
ansible-vault rekey --new-vault-password-file new_pass.txt group_vars/production/vault.ymlrekey decrypts with the current password and re-encrypts with the new one in a single step, across as many files as you point it at. Treat a vault password rotation the same way you'd treat rotating any other credential after a team change — it's a real operational task, not a one-time setup step.
What Vault does not solve
Vault encrypts secrets at rest in your repository. It does not:
- Protect a decrypted value in memory or in
debugoutput during a run. A task that printsdb_passwordfor debugging purposes prints the real, decrypted value — Vault has already done its job of protecting the file by the time a task runs. - Replace a dedicated secrets manager for rotation and access control. Vault has no concept of automatic rotation, per-user access grants, or an audit log of who read which secret when. For an organization with real secrets-management maturity requirements, Ansible Vault is usually the mechanism that hands off to a real secrets manager at run time (as in the CI example above), not a full replacement for one.
- Make the vault password itself safe to lose. Whoever holds that one password can decrypt everything. Treat it exactly like the master credential it is — the same category of thing as the AWS credentials we discussed protecting in our own CI/CD case study.
Integrating Vault with a real external secrets manager directly
Beyond a script-based --vault-password-file, the community.hashi_vault collection lets a playbook fetch secrets directly from HashiCorp Vault or AWS Secrets Manager at run time, without an intermediate Ansible Vault-encrypted file at all:
- name: Look up database password from AWS Secrets Manager
ansible.builtin.set_fact:
db_password: "{{ lookup('amazon.aws.aws_secret', 'prod/db/password') }}"This removes Ansible Vault from the picture entirely for that specific value — the secret lives only in the external manager, fetched fresh on every run, with no encrypted copy in the repository at all. Worth adopting once an organization already runs a real secrets manager for other purposes, since it avoids maintaining two separate secret stores (Vault-encrypted files and the external manager) for what's ultimately the same class of value.
What actually happens if a vault password is lost
Losing the password that protects a Vault-encrypted file means everything encrypted with it is permanently unrecoverable — there's no backdoor, no reset mechanism, by design. The practical mitigation is the same one covered for a lost Terraform state file's recovery earlier in this blog: a genuinely important vault password belongs in an organization's own secrets manager (or a password manager with proper access controls and backup), not solely in one person's memory or a single laptop's keychain, so its loss doesn't mean permanently losing access to every secret it protects.
What to actually remember from this post
ansible-vault create/edit/viewmanage whole encrypted files;encrypt_stringencrypts a single value inline.- Keep secrets and regular configuration in separate files, linked by a naming convention — it's the difference between a readable pull request and an opaque one.
--vault-password-fileaccepts a script, which is what makes Vault usable from CI without a human typing a password interactively, and lets the vault password itself come from a real secrets manager.- Vault IDs support multiple passwords for different sensitivity levels — worth adopting once one shared password stops matching your actual access-control needs.
rekeyrotates a vault password across files in one step — a real operational task after any relevant team change, not a one-time setup step.- Vault protects secrets at rest in git — it's not a substitute for a real secrets manager if your organization needs rotation, granular access control, or an audit trail.
Next in the series: Idempotency and Testing Ansible Playbooks with Molecule, where we cover how to actually verify a playbook does what you think it does, before it runs against real infrastructure.
