Ansible Course
Ansible Vault
In this lesson
Ansible Vault is the built-in encryption system that allows sensitive data — passwords, API keys, SSL private keys, database credentials — to be stored safely in version-controlled files alongside the rest of your automation. Without Vault, every engineer who needs to run a playbook must either store secrets in plaintext files (a security incident waiting to happen) or supply them manually at the command line (which breaks automation). Vault solves both problems: secrets are encrypted with AES-256, committed to Git alongside the playbooks that use them, and decrypted automatically at playbook runtime using a password that lives only in secure, non-committed storage.
The Problem Vault Solves
Before Ansible Vault, teams commonly stored credentials in one of three dangerous ways. Each has caused real incidents.
❌
Plaintext in group_vars
Credentials visible to everyone with repo access. One mis-scoped permission exposes every secret in the project.
❌
Excluded from version control
Secrets on one engineer's laptop only. Engineer leaves — secrets leave with them and no one else can run automation.
❌
Passed with -e at runtime
Credentials visible in shell history and process lists. Breaks CI/CD pipelines that must run unattended.
Ansible Vault — the correct solution
Secrets
are AES-256 encrypted and committed to Git. Ansible decrypts them automatically
at runtime. The only secret kept outside version control is the vault password
itself — and it can be stored in a CI environment variable or a
.vault_pass file listed in .gitignore.
The Safe Deposit Box Analogy
Ansible Vault is like a bank safe deposit box inside your Git repository. The box (encrypted file) is kept in the bank (the repo) where everyone can see it exists — but only someone with the right key (vault password) can open it. You can commit the box to version control, share it with the whole team, and even make it public — because without the key, the contents are meaningless ciphertext. The key is the only thing you protect carefully.
Encrypting and Managing Files
The most common Vault workflow is a
dedicated secrets file — encrypted, committed to Git, and loaded via
vars_files. All ansible-vault subcommands follow the same
pattern: they operate on a file and use the vault password to encrypt or decrypt.
ansible-vault subcommands
create
Create a new
encrypted file in $EDITOR. Save and close to encrypt.
encrypt
Encrypt an
existing plaintext file in place. Safe to run multiple times — detects
already-encrypted files.
decrypt
Decrypt to
plaintext permanently. Use with caution — do not commit the result. Useful
for password rotation workflows.
view
Display decrypted
contents without permanently decrypting. The file on disk stays encrypted.
edit
Decrypt to a
temp file, open in $EDITOR, re-encrypt on save. The everyday
command for updating secrets — never writes plaintext permanently.
rekey
Re-encrypt with
a new vault password. The command for rotating vault credentials after a
team member departs.
encrypt_string
Encrypt a single
string and output a !vault YAML snippet suitable for inline
use in any variable file.
# Create and encrypt a new secrets file
ansible-vault create vars/secrets.yml
# Encrypt an existing plaintext file in place
ansible-vault encrypt vars/secrets.yml
# View without decrypting permanently
ansible-vault view vars/secrets.yml
# Edit — decrypts to temp, opens $EDITOR, re-encrypts on save
ansible-vault edit vars/secrets.yml
# Change the vault password across all encrypted files
ansible-vault rekey vars/secrets.yml vars/other_secrets.yml
# What vars/secrets.yml looks like after encryption: $ANSIBLE_VAULT;1.1;AES256 38643865363532643137643932336439373633623430343766303839366135663066633 66561663737346437326262376261656530376666323230656530383733353564636564 3365353738383733333466666630353164636536336333330a643866633634323832376 63639366231376566303231646430376232363061613339303061343035363232633061 38643334303230373264636133633162353738363838313032643464393734353135373 3134... # Ansible detects the $ANSIBLE_VAULT header automatically at runtime. # The file is safe to commit to any Git repository.
What just happened?
The $ANSIBLE_VAULT;1.1;AES256
header tells Ansible this file is vault-encrypted. The ciphertext below it is
hex-encoded AES-256. Ansible detects this header automatically when the file is
loaded via vars_files or include_vars — no extra
flags or syntax needed in the playbook. Run the playbook and Ansible decrypts
it transparently using the configured vault password.
Contents of vars/secrets.yml before encryption
---
# vars/secrets.yml — ENCRYPT THIS FILE before committing
# Run: ansible-vault encrypt vars/secrets.yml
vault_db_password: "s3cr3tP@ssw0rd!"
vault_api_key: "sk-prod-abc123xyz789"
vault_smtp_password: "mailpass456"
vault_ssl_private_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAE...
-----END OPENSSH PRIVATE KEY-----
Encrypting Individual Strings
Sometimes you want to embed an encrypted
value inline within an otherwise readable variable file — rather than having a
completely separate, opaque secrets file.
ansible-vault encrypt_string
produces a !vault YAML snippet you paste directly into any variable
file. The rest of the file stays human-readable.
# Encrypt a string — prompts for vault password then the value
ansible-vault encrypt_string 's3cr3tP@ssw0rd!' --name 'db_password'
# Non-interactive — pipe the value in (useful in CI scripts)
echo -n 's3cr3tP@ssw0rd!' | ansible-vault encrypt_string --stdin-name 'db_password'
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
62313030386234336665653736653539643735363538643538616539363534396662303865613164
6566363336346165303038663432386661393031636338310a363533373830373264363531373761
36363630363535333934363630636337393164336432376633323933313731623163396637353036
3736353338326564640a626164323165353965363665326163363339343437376630623866343765
6265What just happened?
The output is a YAML-ready snippet — copy the
entire block starting from db_password: !vault | and paste it into
any variable file. The !vault tag tells Ansible to decrypt this
value at runtime. Everything else in the file stays readable plaintext.
Inline encrypted value in an otherwise readable variable file
# group_vars/databases.yml — mostly readable, one encrypted value
---
postgresql_version: "15"
postgresql_port: 5432
postgresql_max_connections: 100
# Only this value is encrypted — everything else is readable in Git
postgresql_app_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
62313030386234336665653736653539643735363538643538616539363534396662303865613164
6566363336346165303038663432386661393031636338310a363533373830373264363531373761
36363630363535333934363630636337393164336432376633323933313731623163396637353036
3736353338326564640a626164323165353965363665326163363339343437376630623866343765
6265
Managing the Vault Password
The vault password is the one secret that must never be committed to version control. It must be secure but also accessible enough for automated pipelines to use it unattended. Three standard approaches cover every common scenario.
Password file
Store the
password in .vault_pass, add to .gitignore, and
reference it in ansible.cfg. Simple and effective for small
teams and local development.
Environment variable
Set
ANSIBLE_VAULT_PASSWORD_FILE in the shell or CI pipeline. The
password is injected by the pipeline without touching the filesystem —
standard for GitHub Actions, GitLab CI, and Jenkins.
Vault password script
An executable script that retrieves the password from a secrets manager (HashiCorp Vault, AWS Secrets Manager) and prints it to stdout. The most secure option — no password ever touches the filesystem.
# ansible.cfg — configure vault password source
[defaults]
# Method 1 — password file (add .vault_pass to .gitignore)
vault_password_file = .vault_pass
# Set up the password file
echo "my-strong-vault-password" > .vault_pass
chmod 600 .vault_pass # restrict permissions to owner only
# Verify .vault_pass is in .gitignore — CRITICAL before first commit
grep ".vault_pass" .gitignore || echo ".vault_pass" >> .gitignore
# Confirm git does not see it as a file to stage
git status .vault_pass # should show: nothing to commit
Using Vault in Playbooks
With vault configured in
ansible.cfg, encrypted variables are referenced in playbooks exactly
like any other variable. Ansible detects the $ANSIBLE_VAULT header or
!vault tag and decrypts automatically at runtime — no special syntax
required in tasks.
---
- name: Configure database server
hosts: databases
become: true
vars_files:
- vars/app.yml # plaintext — readable in Git
- vars/secrets.yml # vault-encrypted — safe in Git
tasks:
- name: Create application database user
community.postgresql.postgresql_user:
name: "{{ app_db_user }}"
password: "{{ vault_db_password }}" # decrypted automatically at runtime
state: present
become_user: postgres
no_log: true # never print task output — would expose the password
- name: Configure application with API key
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/config.conf
# In app.conf.j2: api_key = {{ vault_api_key }}
# vault_password_file in ansible.cfg — no extra flags needed
ansible-playbook site.yml -i inventory/production/
# Override: prompt interactively (useful when testing on a new machine)
ansible-playbook site.yml --ask-vault-pass
# Override: specify password file at the command line
ansible-playbook site.yml --vault-password-file .vault_pass
Vault IDs — Multiple Passwords
Large projects often need different vault passwords per environment — production credentials should not be decryptable with the same password as staging. Vault IDs allow multiple passwords to coexist within one project, each labelled so Ansible knows which key to use for which content.
# Encrypt files with labelled Vault IDs
ansible-vault encrypt vars/prod_secrets.yml --vault-id prod@.vault_pass_prod
ansible-vault encrypt vars/staging_secrets.yml --vault-id staging@.vault_pass_staging
# Encrypt a string with a specific vault ID
ansible-vault encrypt_string 'prod-password' --name 'db_password' \
--vault-id prod@.vault_pass_prod
# Run playbook supplying both vault passwords
ansible-playbook site.yml \
--vault-id prod@.vault_pass_prod \
--vault-id staging@.vault_pass_staging
# In ansible.cfg — configure multiple vault password files
# vault_identity_list = prod@.vault_pass_prod, staging@.vault_pass_staging
Vault Variable Naming Convention
Using a consistent naming convention for vault variables makes it immediately obvious which variables are encrypted and where their values come from — both when reading a playbook and when debugging a variable resolution problem.
Generic names with no vault signal
db_password: !vault | $ANSIBLE_VAULT... api_key: !vault | $ANSIBLE_VAULT...
Is
db_password encrypted or plaintext? Impossible to tell
without opening the file.
Prefix encrypted variables with vault_
vault_db_password: !vault | $ANSIBLE_VAULT... vault_api_key: !vault | $ANSIBLE_VAULT...
Immediately
clear that these values are encrypted. Reference with
{{ vault_db_password }} in tasks.
Always Verify .vault_pass Is in .gitignore Before the First Commit
The most catastrophic Vault
mistake is committing the vault password file to version control — which exposes
every secret in every encrypted file in the repository, present and historical.
Before your first commit in any Vault-enabled project, run
git status and confirm .vault_pass is untracked but
not staged. Then verify it is in .gitignore. A single accidental
commit means rotating every secret in the repository — a painful process that
is completely avoidable with one check.
Key Takeaways
encrypt_string for inline secrets —
embed a single encrypted value in an otherwise readable variable file,
keeping the file navigable while protecting the sensitive value.
vault_password_file in ansible.cfg
— makes Vault fully transparent at runtime. Playbooks decrypt secrets
automatically with no extra flags needed.
vault_ prefix
— vault_db_password makes it immediately clear which
variables are encrypted and where their values come from.
Teacher's Note
Take any variable file in your
project that contains a plaintext password, encrypt it with
ansible-vault encrypt, run the playbook, and confirm it still works.
Then use ansible-vault edit to change a value and run it again.
This two-step exercise makes Vault part of your muscle memory rather than a
procedure you look up when you need it.
Practice Questions
1. Which ansible-vault
subcommand encrypts a single string value and outputs a !vault YAML
snippet you can paste inline into any variable file?
2. Which subcommand decrypts a vault file to a temporary location, opens it in your editor, then automatically re-encrypts it on save — without ever writing plaintext permanently to disk?
3. In which file must you list
.vault_pass to ensure the vault password is never accidentally
committed to version control?
Quiz
1. A variable
vault_db_password in group_vars/databases.yml is
encrypted with !vault |. How is it referenced in a task?
2. A team member who knew the vault password has left the company. What is the correct remediation?
3. A company wants developers to run playbooks against staging but not be able to decrypt production secrets. What Vault feature enables this?
Up Next · Lesson 29
Managing Secrets Securely
Go beyond Vault — external secrets managers, HashiCorp Vault integration, AWS Secrets Manager, environment-specific secret strategies, and a complete secrets management reference for production Ansible projects.