Ansible Lesson 28 – Ansible Vault | Dataplexa
Section III · Lesson 28

Ansible Vault

In this lesson

What Vault solves Encrypting files Encrypting strings Vault passwords Vault IDs

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
          6265

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

Method 1

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.

Method 2

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.

Method 3

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.

Without Vault IDs
One vault password for everything
Staging and production secrets share the same key
Anyone who can decrypt staging can also decrypt production
With Vault IDs
Separate password per environment or category
Production secrets only decryptable with the production vault password
Least-privilege applied to secrets — developers get staging, ops get production
# 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.

Avoid

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.

Use vault_ prefix

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

Ansible Vault uses AES-256 encryption — encrypted files are safe to commit to any Git repository. The only secret that must stay out of version control is the vault password itself.
Use 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.
Set vault_password_file in ansible.cfg — makes Vault fully transparent at runtime. Playbooks decrypt secrets automatically with no extra flags needed.
Name vault variables with a vault_ prefixvault_db_password makes it immediately clear which variables are encrypted and where their values come from.
Use Vault IDs for separate staging and production passwords — prevent anyone with staging access from decrypting production secrets, enforcing a basic security boundary from day one.

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.