Ansible in depth · Lesson 3 · 30 min

Secrets with Ansible Vault

Your playbooks live in git, and so do their variables. But some variables are passwords: the database password the app needs, an API key, a TLS private key. In the DevOps path you kept secrets out of git with .env files. Ansible offers another answer: keep them in git, encrypted. Ansible Vault encrypts files or single values with a password, and playbooks decrypt them on the fly.

You will learn

  • ansible-vault encrypt, view, edit, decrypt, create and rekey
  • Running playbooks with --ask-vault-pass or a vault password file
  • The vars.yml + vault.yml pattern that keeps secrets findable
  • encrypt_string for a single value inside a normal file
  • no_log, and why secrets must not leak into output

Encrypt a file

ansible-vault encrypt group_vars/web/vault.yml    # asks for a new vault password, twice
cat group_vars/web/vault.yml                      # unreadable now
ansible-vault view group_vars/web/vault.yml       # read it (asks for the password)
ansible-vault edit group_vars/web/vault.yml       # change it: decrypt → editor → re-encrypt
$ANSIBLE_VAULT;1.1;AES256
65326638653261316438323230316631333466616235366435393835653730643338656466386431
3362366464616131303939393731303566326366666338350a646562303436636630356438396331
…

The real Vault uses AES-256, so without the password the file is just noise, and it's safe to commit. The practice terminal copies the file format, not the real cryptography.

The vars.yml + vault.yml pattern

If every secret hides inside an encrypted file, nobody can grep for where a variable is set. The common fix: two files, side by side.

group_vars/web/vars.yml (plain text)
db_password: "{{ vault_db_password }}"
group_vars/web/vault.yml (encrypted)
vault_db_password: Tickets-2026-orange-kayak

Everyone can see that db_password exists and where it comes from. Only people with the vault password can see its value. The vault_ prefix makes encrypted variables obvious.

Running playbooks with a vault

ansible-playbook site.yml                     # ERROR! Attempting to decrypt but no vault secrets found
ansible-playbook site.yml --ask-vault-pass    # type it each time

# or: a password file, never in git, only readable by you
echo 'the-vault-password' > ~/.vault_pass
chmod 600 ~/.vault_pass
# in ansible.cfg, under [defaults]:
vault_password_file = ~/.vault_pass

In CI, the vault password comes from the CI system's secret store and is written to a temporary file for the run. The encrypted files stay in git, and the one key that opens them does not.

One value: encrypt_string

ansible-vault encrypt_string --prompt --name api_key   # --prompt keeps it out of shell history
api_key: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          3663353437316537353465306531346436…

Paste that into any vars file. The rest of the file stays readable, and only this value is encrypted.

Don't leak it in the output

Ansible prints task results, and --diff prints file changes. For a task that handles a secret, that could print the password into a CI log. Add no_log: true to such tasks:

- name: Write the app's config
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/cht/app.conf
    mode: "0600"            # root only: the file holds a password
  no_log: true              # results (and errors) are hidden
Vault is only as safe as its password

Use a long random vault password, keep the password file out of git (.gitignore), and share it through a password manager. If it leaks, run ansible-vault rekey to change it, and rotate the secrets inside too: anyone who had the old password could already read them.

Vault or a secret manager?

Vault is perfect for small teams: nothing extra to run. Bigger setups often keep secrets in HashiCorp Vault/OpenBao or a cloud secret manager, and let Ansible fetch them at run time with a lookup plugin. The idea from the DevOps secrets lesson is the same either way: secrets never sit in plain text in git.

Practice: lock up the database password 🔏

The web role now also writes the app's config, which needs a database password. Someone left it in plain text in group_vars/web/vault.yml. Encrypt it, run the playbook both ways, and set up a password file.

Quick check

1. Is it OK to commit a vault-encrypted file to git?

2. Why keep db_password: "{{ vault_db_password }}" in a plain vars.yml?

3. What does no_log: true do?

Finished the missions and the quiz? Mark it done to track your progress.