Ansible Course
Introduction to 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 one has caused real production incidents.
❌
Plaintext in group_vars
Credentials visible to everyone with repo access. One leaked token or mis-scoped repository permission exposes every secret in the project.
❌
Excluded from version control
Secrets on one engineer's laptop. Anyone else who needs to run the playbook cannot. Engineer leaves — secrets leave with them.
❌
Passed with -e at runtime
Credentials visible in shell history and process lists. Breaks CI/CD pipelines that need to run unattended.
Ansible Vault: the right answer
Secrets are
AES-256 encrypted, committed to Git alongside the playbooks that use them,
and decrypted automatically at runtime. The vault password is the only secret
that must be kept out of version control — and it can be stored in a secrets
manager, CI/CD environment variable, or a .vault_pass file listed
in .gitignore.
The Safe Deposit Box Analogy
Ansible Vault is like a bank's safe deposit box inside your Git repository. The box (encrypted vault file) is kept in the bank (the repository) where everyone can see it exists — but only someone with the right key (vault password) can open it and read the contents. You can commit the box to version control, share it with your 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 Editing Files
The most common Vault workflow is to
create a dedicated secrets file, encrypt it, and reference it in your playbook via
vars_files. All ansible-vault subcommands follow the same
pattern — they operate on files or strings and use the vault password to
encrypt/decrypt.
# Create and encrypt a new file in one step
ansible-vault create vars/secrets.yml
# Encrypt an existing plaintext file
ansible-vault encrypt vars/secrets.yml
# View the decrypted contents without opening an editor
ansible-vault view vars/secrets.yml
# Edit the file — decrypts to a temp file, opens your $EDITOR, re-encrypts on save
ansible-vault edit vars/secrets.yml
# Decrypt permanently to plaintext (be careful — do not commit the result)
ansible-vault decrypt vars/secrets.yml
# Re-encrypt with a new password
ansible-vault rekey vars/secrets.yml
# What vars/secrets.yml looks like after encryption: $ANSIBLE_VAULT;1.1;AES256 38643865363532643137643932336439373633623430343766303839366135663066633 66561663737346437326262376261656530376666323230656530383733353564636564 3365353738383733333466666630353164636536336333330a643866633634323832376 63639366231376566303231646430376232363061613339303061343035363232633061 38643334303230373264636133633162353738363838313032643464393734353135373 3134... # The file is unreadable without the vault password. # It is safe to commit to Git.
What just happened?
The AES-256 encrypted file starts with
$ANSIBLE_VAULT;1.1;AES256 — a header that tells Ansible this is
a vault-encrypted file. Every subsequent line is hex-encoded ciphertext.
Ansible automatically detects this header at runtime and decrypts the file
before loading the variables — no manual decryption step required.
Contents of vars/secrets.yml before encryption
---
# vars/secrets.yml — ENCRYPT THIS FILE before committing
# ansible-vault encrypt vars/secrets.yml
vault_db_password: "s3cr3tP@ssw0rd!"
vault_api_key: "sk-prod-abc123xyz789"
vault_ssl_private_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAE...
-----END OPENSSH PRIVATE KEY-----
vault_smtp_password: "mailpass456"
Encrypting Individual Strings
Sometimes you do not want a completely
separate secrets file — you want to embed an encrypted value inline within an
otherwise readable variable file. The
ansible-vault encrypt_string
command produces an encrypted string that can be pasted directly into any YAML file.
The rest of the file remains human-readable.
# Encrypt a string value — prompts for vault password then the string to encrypt
ansible-vault encrypt_string 's3cr3tP@ssw0rd!' --name 'db_password'
# Non-interactive — pipe the value in (useful in 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 snippet ready to paste
directly into a variable file. The !vault tag tells Ansible this
value is vault-encrypted. Copy this entire block and paste it into your
group_vars/all.yml or any other variable file — the surrounding
variables remain readable while only the sensitive value is encrypted.
Using inline encrypted strings in a variable file
# group_vars/databases.yml — mostly readable, one encrypted value
---
postgresql_version: "15"
postgresql_port: 5432
postgresql_max_connections: 100
# This value is encrypted with ansible-vault encrypt_string
# The rest of the file is readable in plain text
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 kept secure but also accessible enough for automated pipelines to use it unattended. There are three standard approaches — choose based on your security requirements and team setup.
Password file
Store the
password in .vault_pass in the project root. Add to
.gitignore. Set vault_password_file = .vault_pass
in ansible.cfg. Simple and effective for small teams.
Environment variable
Set
ANSIBLE_VAULT_PASSWORD_FILE to a path, or use a vault password
script that reads from an environment variable. Standard for CI/CD — the
password is injected by the pipeline without touching the filesystem.
Vault password script
An executable script that retrieves the password from a secrets manager (HashiCorp Vault, AWS Secrets Manager, 1Password CLI) 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 (simple, add .vault_pass to .gitignore)
vault_password_file = .vault_pass
# Method 2 — environment variable (no config needed, set in shell or CI)
# export ANSIBLE_VAULT_PASSWORD_FILE=/path/to/vault_pass
# Method 3 — password script (reads from secrets manager)
# vault_password_file = ./scripts/get_vault_password.py
# Create the vault password file
echo "my-strong-vault-password" > .vault_pass
chmod 600 .vault_pass # restrict to owner only
# Verify .vault_pass is in .gitignore — CRITICAL
grep ".vault_pass" .gitignore # should return .vault_pass
# Add it if missing
echo ".vault_pass" >> .gitignore
Using Vault in Playbooks
Once vault is configured in
ansible.cfg, using encrypted variables in a playbook requires no
special syntax — reference them exactly like any other variable. Ansible detects
the $ANSIBLE_VAULT header or !vault tag and decrypts
automatically at runtime.
---
- 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:
# vault_db_password comes from the encrypted secrets.yml
# Reference it exactly like any other variable — no special syntax
- name: Create application database user
community.postgresql.postgresql_user:
name: "{{ app_db_user }}"
password: "{{ vault_db_password }}" # decrypted automatically
state: present
become_user: postgres
no_log: true # prevent password from appearing in output
- 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 }}
# Running the playbook — vault is decrypted automatically using .vault_pass
ansible-playbook site.yml -i inventory/production/
# If vault_password_file is not in ansible.cfg, prompt interactively
ansible-playbook site.yml --ask-vault-pass
# Specify vault 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 for different environments or secret categories — production credentials should not be decryptable with the same password used for staging. Vault IDs allow multiple passwords to coexist in one project.
# Encrypt with a specific vault ID
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 vault ID
ansible-vault encrypt_string 'prod-password' --name 'db_pass' \
--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
Vault Command Reference
ansible-vault subcommands
create
Create a new
encrypted file and open it in $EDITOR. Save and close to
encrypt automatically.
encrypt
Encrypt an
existing plaintext file in place. The original is replaced with ciphertext.
Safe to run multiple times — detects already-encrypted files.
decrypt
Decrypt a file to
plaintext permanently. Use with caution — the result should not be committed.
Useful for inspecting content or rotating to a new password.
view
Display the
decrypted contents without permanently decrypting the file. Safe to use
at any time — the file on disk remains encrypted.
edit
Decrypt to a
temporary file, open in $EDITOR, re-encrypt on save. The
everyday command for updating secrets — the file is never decrypted on disk
beyond the temporary editing session.
rekey
Change the vault
password for an encrypted file. Prompts for the old password then the new
one. Use when rotating the vault password across the project.
encrypt_string
Encrypt a single
string value and output a YAML snippet suitable for inline use in variable
files. The surrounding file remains readable plaintext.
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 listed as
untracked but not staged. Then verify .gitignore contains the entry.
Do this check every time you add a new vault password file to a project. A single
accidental commit means rotating every secret in the repository — a painful,
time-consuming process that is completely avoidable.
Key Takeaways
encrypt_string for inline secrets —
encrypt a single value and embed it in an otherwise readable variable
file. The file stays navigable; only the sensitive value is opaque.
vault_password_file in ansible.cfg
— this makes Vault fully transparent at runtime. Playbooks decrypt secrets
automatically with no extra flags needed.
vault_ prefix
— vault_db_password rather than db_password.
This makes it immediately clear which variables are encrypted and where
their values come from when reading a playbook.
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 one value and run it again.
This two-step exercise makes Vault part of your muscle memory rather than a special
procedure you look up when you need it.
Practice Questions
1. Which ansible-vault
subcommand encrypts a single string value and outputs a 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, and automatically re-encrypts it when you save and close — without ever writing plaintext to disk permanently?
3. In which project file must you
list .vault_pass to ensure the vault password is never accidentally
committed to version control?
Quiz
1. A variable
db_password in group_vars/databases.yml is encrypted
with !vault |. How do you reference it in a task?
2. A team member who knew the vault password has left the company. What is the correct procedure?
3. A company wants developers to be able to run playbooks against staging but not be able to decrypt production secrets. What Vault feature enables this?
Up Next · Lesson 27
Advanced Vault Techniques
Go deeper with Vault — partial encryption strategies, vault password scripts that integrate with HashiCorp Vault and AWS Secrets Manager, and CI/CD pipeline patterns for unattended secret management.