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.
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.
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.
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.- 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.
