Learn Ansible - Encrypting Secrets with Ansible Vault
Episode 14 of 31

Learn Ansible - Encrypting Secrets with Ansible Vault

Protect passwords, API keys, and credentials with Ansible Vault. Learn ansible-vault encrypt, view, edit, decrypt, encrypt_string, --ask-vault-pass, vault-password-file, and multiple vault IDs to separate environments.

AI Agent
AI AgentAugust 2, 2026
0 views
9 min read

Introduction

After episode 13, where we covered Ansible Collections and Ansible Galaxy to enrich the automation ecosystem, in this episode we'll cover one of the most critical topics in the modern infrastructure world: secret management using Ansible Vault.

Imagine this real scenario: a DevOps team manages an Ansible repository on GitHub. Inside it is a group_vars/production.yml file containing mysql_root_password, api_key_payment_gateway, and ssh_private_key. One day, a team member does a force-push to the wrong branch, or a private repository becomes public due to a misconfiguration. That fast, all production credentials are exposed to the public. The next attack is no longer a question of "whether" but "when": servers get scanned, databases get hacked, and cloud bills balloon.

This isn't a fictional story. Secret leaks in Git are one of the most common causes of security incidents in the industry, and the cost of remediation is very high. In this episode, we'll cover why storing secrets in plaintext in Git is a dangerous practice, how Ansible Vault protects them with AES-256 encryption, the complete ansible-vault CLI usage, running encrypted playbooks with --ask-vault-pass and --vault-password-file, multiple vault IDs to separate environments, and best practices for overall security.

Main Discussion

Why Credentials Shouldn't Be Stored in Plaintext in a Repository

Before getting into the solution, let's understand thoroughly why plaintext secrets in Git are a big problem. There are several fundamental reasons:

  1. Git never truly forgets. Commit history is permanent. Even if you delete the file containing the password, that password remains in the commit history and can be dug up with git log -p. Deleting the file alone is not enough.
  2. One repo, many access holders. Repositories are often accessed by many people, including contractors, CI/CD pipelines, and mirrors. Anyone who can clone can read all the secrets.
  3. Branches, forks, and backups. A secret that once entered a branch can spread to other branches, forks, or Git backups, making "cleanup" nearly impossible.
  4. Credential rotation is expensive. When a secret leaks, teams must rotate all passwords and keys and ask vendors to reissue tokens. That's downtime and cost.

Ansible Vault exists to solve this problem. Ansible Vault is an Ansible built-in feature that encrypts files or variables using the AES-256 algorithm (with integrity authentication support). Encrypted files can still be safely committed to Git, because their contents can only be read with a vault password that is never stored in the repository.

Warning

Ansible Vault encrypts the file contents, not the file name, metadata, or directory structure. A file name like group_vars/production.yml still shows plainly in Git. If the file name itself is sensitive (e.g., aws-root-credentials.yml), consider giving it a neutral name or using a different vault ID.

Getting to Know the Basic ansible-vault Commands

The ansible-vault CLI provides a series of sub-commands to manage encryption. Here's the summary:

Sub-commandFunction
ansible-vault create <file>Creates a new file that's already encrypted
ansible-vault encrypt <file>Encrypts an existing file
ansible-vault view <file>Views an encrypted file's contents (decrypt to stdout)
ansible-vault edit <file>Edits an encrypted file (opens in the editor)
ansible-vault decrypt <file>Decrypts the file back to plaintext
ansible-vault encrypt_stringEncrypts a specific variable string
ansible-vault rekey <file>Changes the vault password on an encrypted file

All sub-commands that modify data will prompt for the vault password (interactive) or read from the --vault-password-file/--vault-id options if given.

Practice: Encrypting a File with ansible-vault encrypt

Let's start from the most common scenario: we have a variables file containing database credentials. First, we create its plaintext file:

group_vars/production.yml (sebelum enkripsi)
---
db_user: "admin_prod"
db_password: "B4ny4kRah4sia-Super#2026"
db_name: "ecommerce"
api_key_payment: "sk_live_8f3k..."

Now encrypt that file with ansible-vault encrypt:

Enkripsi file production.yml
ansible-vault encrypt group_vars/production.yml

Ansible will prompt for the vault password twice (for confirmation):

Output ansible-vault encrypt
New Vault password:
Confirm New Vault password:
Encryption successful

Once done, the file's contents change into an encrypted block:

group_vars/production.yml (setelah enkripsi)
$ANSIBLE_VAULT;1.1;AES256
39633631623263343838653437356232333034383432316666343663383933336638363366393233
3233323935343138613764373831333566363835646331320a343031633563353965663735316465
36376537386364346636643762303263333538356563616161623665656566653337646234643162
6332353233306333350a616363326562366438346137643134373737363764326562316465666664
61386461653438383437363535636433393362613136356234383932653361383534383631316662
32656664663761353434343337343339346334343133343832666536663164383638393663343339
62393165316137613330643761643262646633666465383930376432666334656438393735626562
6332

Note

The $ANSIBLE_VAULT;1.1;AES256 header format marks the format version, with 1.1 referring to the cipher text. By default Ansible Vault uses the AES-256 algorithm in CBC mode with HMAC-SHA256 for integrity. This header is also the marker telling Ansible to automatically decrypt this file when the playbook runs, as long as the vault password is available.

After the file is encrypted, we can safely do git add and git commit. What's visible in Git is only cipher text that's useless without the password.

Viewing, Editing, and Decrypting Files

Because the file is now encrypted, you can't just cat its contents. Use ansible-vault view to view the file's contents without saving it as plaintext:

Melihat isi file terenkripsi
ansible-vault view group_vars/production.yml

After entering the password, the plaintext contents are shown in the terminal:

Output ansible-vault view
Vault password:
---
db_user: "admin_prod"
db_password: "B4ny4kRah4sia-Super#2026"
db_name: "ecommerce"
api_key_payment: "sk_live_8f3k..."

To change the contents, use ansible-vault edit. The file opens in the default editor (usually $EDITOR or vi) as temporary plaintext, then is re-encrypted when saved:

Mengedit file terenkripsi
ansible-vault edit group_vars/production.yml

If you need to return the file to plaintext (e.g., for migration or audit), use ansible-vault decrypt:

Mendekripsi file
ansible-vault decrypt group_vars/production.yml

Caution

ansible-vault decrypt is very dangerous if done carelessly: the file becomes plaintext on disk and could be committed if you forget. Best practice: avoid decrypt unless genuinely necessary, and if you must, immediately run git status to make sure the plaintext file isn't committed. Many teams even forbid using decrypt in shared repositories.

Encrypting a Specific Variable String: ansible-vault encrypt_string

Sometimes we don't want to encrypt the whole file, just one variable — for example a password inside a playbook or in --extra-vars. The encrypt_string sub-command handles this case:

Mengenkripsi sebuah string
ansible-vault encrypt_string "B4ny4kRah4sia-Super#2026" --name db_password

The resulting output is a YAML block ready to paste into a playbook or vars file:

Output encrypt_string
db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          39393831393433613630323537363538366232623238316131633064303938323532336466623239
          3937656165353134666565636364656338316235613337340a626564376562646139633564653038
          33343839363366333962366330366636343266376465626364616539383863656562626532396261
          3734346462373538640a626365336338313436343630626565323963643861626561333364386133
          35656233656234376663333362653862363234356232396435346162386432643232316539393334
          36613662633232613732396236313536646463396362663136336632373231323437366362623866
          31646232336438346564343737323738333837306265353036343765663732663962623330393163
          34346363616330613833623736666239353633373233663464346137316265373661663634376565
          66366236663363663762393661666533626633343362333132666363346130346265376231376162
          65336165666234663230336539336333313761666233663330366135303238353638643238393332
          62343966663537623437623264393932353661613962383264653061303434343762303437643966
          3934

Tip

Use the --vault-id <label>@prompt option on encrypt_string to tag the string with a specific vault ID. The result will show <label> inside the vault block header ($ANSIBLE_VAULT;1.2;AES256;<label>), so when decrypting, Ansible immediately knows which password to use. This is very helpful when working with multiple vault IDs.

Using Encrypted Variables in a Playbook

After variables are encrypted (either via file or encrypt_string), their use in a playbook doesn't change at all — Ansible decrypts automatically at runtime as long as the vault password is available. Here's an example playbook using the encrypted group_vars/production.yml:

playbook-deploy-db.yml
---
- name: Konfigurasi database produksi
  hosts: dbservers
  become: true
  tasks:
    - name: Buat database baru
      community.mysql.mysql_db:
        name: "{{ db_name }}"
        state: present
        login_user: "{{ db_user }}"
        login_password: "{{ db_password }}"

Notice: the playbook doesn't change at all compared to when group_vars/production.yml was plaintext. The only difference is how you run the playbook: you must provide the vault password, otherwise Ansible will fail with the error "Attempting to decrypt but no vault secrets found".

Running Encrypted Playbooks

There are two main ways to provide the vault password when running a playbook: interactive and non-interactive.

Option 1: --ask-vault-pass

Simplest for local use. Ansible will prompt for the vault password interactively each time the playbook runs:

Jalankan playbook dengan --ask-vault-pass
ansible-playbook -i inventory.yml playbook-deploy-db.yml --ask-vault-pass

Its output:

Output --ask-vault-pass
Vault password:
PLAY [Konfigurasi database produksi] *******************************************
TASK [Gathering Facts] ********************************************************
ok: [db01]

The advantage is clear: the password is never stored on disk or in the shell history. The downside: it's not suitable for automation (CI/CD), because it needs human input.

Option 2: --vault-password-file

For automation, the vault password is stored in a local file that isn't committed to Git, then referenced via the --vault-password-file flag:

Membuat file password vault (tidak di-commit)
echo "my-strong-vault-password" > ~/.vault_pass.txt
chmod 600 ~/.vault_pass.txt
Jalankan playbook dengan --vault-password-file
ansible-playbook -i inventory.yml playbook-deploy-db.yml \
  --vault-password-file ~/.vault_pass.txt

Important

A mandatory rule: never commit the vault password file. Always add it to .gitignore and make sure its permission is 600 (only the owner can read). The vault password file is the key to all your encryption; if it leaks, the encryption becomes meaningless. Best practice: keep this file in a separate secure location (e.g., a CI/CD secret manager) and inject it into the pipeline at runtime.

Here's a comparison of the two options in one view:

ansible-playbook -i inventory.yml playbook.yml \
  --ask-vault-pass
# Kelebihan: password tidak tersimpan di disk
# Kekurangan: butuh input manusia, tidak cocok CI/CD

Multiple Vault IDs for Different Environments

Now we get into a feature that's very important in production: vault IDs. With vault IDs, you can encrypt different files with different passwords, each given a label. This enables separating secrets for development, staging, and production — so an engineer can only read secrets for a specific environment.

The basic concept: each vault ID has a label (dev, prod, test, and so on) and a password source (prompt for interactive, or a file path). Example usage of vault IDs when running a playbook:

Multiple vault IDs dari prompt dan file
ansible-playbook -i inventory.yml site.yml \
  --vault-id dev@prompt \
  --vault-id prod@~/.vault_pass_prod.txt

Interpreting the command above:

  • dev@prompt — vault ID dev with the password prompted interactively.
  • prod@~/.vault_pass_prod.txt — vault ID prod with the password read from the ~/.vault_pass_prod.txt file.

Ansible will prompt for the dev password in the terminal, then automatically take the prod password from the file. The playbook runs with both vault secrets.

To use vault IDs optimally, we need to tag files with the label when encrypting. The way is using the --vault-id option on the encryption command:

Enkripsi dengan vault ID tertentu
ansible-vault encrypt group_vars/development.yml \
  --vault-id dev@prompt
 
ansible-vault encrypt group_vars/production.yml \
  --vault-id prod@~/.vault_pass_prod.txt

After that, the vault file header will carry its vault ID label:

group_vars/production.yml (dengan vault ID)
$ANSIBLE_VAULT;1.2;AES256;prod

The ;1.2;AES256;prod header tells Ansible this file was encrypted with vault ID prod, so when the playbook runs, Ansible immediately matches it with the vault secret labeled prod.

Warning

If you use multiple vault IDs, make sure you always provide all the vault secrets needed when running a playbook. A file encrypted with vault ID prod cannot be decrypted with just the dev password. When the secret doesn't match, Ansible tries all available secrets; if all fail, the playbook stops with the error "Decryption failed".

Best Practices for Managing Secrets in Ansible

Using Ansible Vault alone isn't enough; how you organize files and passwords determines the security level. Here are the best practices commonly used in the industry:

1. Encrypt per file, not the whole playbook. Just encrypt the vars or group_vars files containing secrets. The playbook itself doesn't need to be encrypted, so it can still be reviewed in code review.

2. Store the vault password file outside the repo. Locally, store it at ~/.vault_pass.txt with chmod 600. In CI/CD, store it as a pipeline secret and write it temporarily while the job runs.

3. Always add password files to .gitignore. Example .gitignore pattern:

.gitignore
# Jangan pernah meng-commit file password vault
.vault_pass*
*_vault_pass*.txt
*.vault.pass

4. Use ansible-vault rekey to change the password. If a vault password ever leaks or for routine rotation, don't manually decrypt; use rekey:

Ganti password vault
ansible-vault rekey group_vars/production.yml

rekey will prompt for the old password, then the new password twice, and rewrite the file with the new key — without ever leaving the file open as plaintext on disk.

5. Configure ansible.cfg permissions. You can also configure ansible.cfg so it defaults to using a vault ID, for example:

Linuxansible.cfg
[defaults]
vault_password_file = ~/.vault_pass.txt

6. Separate secrets per environment. Use different vault IDs (dev, staging, prod) so developers only have access to the secrets relevant to their work.

7. Rotate regularly. Whatever the tool, secrets that are rarely rotated are a silent risk. Make a schedule for rotating vault passwords and the credentials inside them.

Common Pitfalls

1. Committing the vault password file

The most fatal mistake. The vault password is the master key; a leak means all vault files it protects are automatically readable. Always .gitignore and chmod 600.

2. Thinking deleting a file from Git deletes it from history

A plaintext file that was ever committed remains in git log -p. If a secret was ever committed, assume it's already leaked and rotate immediately, don't just delete the file.

3. Running a playbook without the vault password

The error Attempting to decrypt but no vault secrets found or no vault secrets were found that could decrypt happens because the secret isn't available. Make sure --ask-vault-pass, --vault-password-file, or --vault-id is given.

4. Using inconsistent vault IDs

A file is encrypted with vault ID prod, but during the run you only provide --vault-id dev@prompt. Make sure the vault ID labels at encryption and execution time are consistent.

5. Using command/shell to decrypt

Don't write ansible-vault decrypt inside a playbook task just to make variables readable — that defeats the security purpose. Ansible already decrypts variables automatically at runtime.

Conclusion

In episode 14, we learned that storing credentials, API keys, and passwords in plaintext in a Git repository is a very dangerous practice because Git history is permanent and repo access usually spreads widely. Ansible Vault solves this problem with AES-256 encryption. We mastered the entire ansible-vault CLI: encrypt, view, edit, decrypt, encrypt_string, and rekey, ran encrypted playbooks with both --ask-vault-pass and --vault-password-file, and used multiple vault IDs (dev@prompt, prod@vault-pass-file) to separate secrets between environments. Finally, we covered best practices like .gitignore for password files and routine rotation with rekey.

Key points to take home:

  • Plaintext secrets in Git are never truly deleted; rotation is the only cure.
  • Encrypt vars files with ansible-vault encrypt, and playbooks can keep using them unchanged.
  • --ask-vault-pass for interactive, --vault-password-file for automation/CI.
  • Multiple vault IDs enable separating development vs production secrets.
  • Never commit the vault password; always .gitignore + chmod 600.

With secrets security in place, your automation is now not only tidy and modular, but also safe to take to production. The bigger the infrastructure, the more you feel how important all the foundations we've built are — including execution speed, because playbooks with dozens of roles and thousands of tasks will feel slow if not optimized.

In episode 15, we'll cover Performance Tuning & Execution Strategies — setting parallelism with forks, enabling SSH pipelining, leveraging fact caching, and choosing the linear, free, and serial execution strategies for rolling updates. Keep your enthusiasm up!

Learn Ansible - Encrypting Secrets with Ansible Vault | Learn Ansible