Encrypting Secrets That Live in Git
Scenario: A security review finds a production database password in clear text in group_vars/dbservers/vars.yml, committed two years ago and present in every clone of the repository.
New words, in plain English
- Ansible Vault - built-in encryption for variable files and single values, using AES-256.
- Vault password - the passphrase that decrypts a vault. Never committed.
--vault-password-file- a file containing the passphrase, so runs are non-interactive.vault_password_file- the same thing configured in ansible.cfg, so no flag is needed.- Encrypted-at-rest - the file in Git is ciphertext; only a holder of the passphrase can read it.
Vault encrypts files (or individual values) with AES-256, so the ciphertext is safe to commit while the passphrase is not. Ansible decrypts transparently at run time, and encrypted variables behave exactly like ordinary ones inside a play.
The workflow is four commands: ansible-vault create to make a new encrypted file, edit to change one (it decrypts to a temporary file, opens your editor, re-encrypts on save), view to read one without editing, and encrypt/decrypt to convert an existing file in place.
Supplying the passphrase has three forms, and only one is right for automation. --ask-vault-pass prompts, which blocks CI. --vault-password-file /path reads it from a file. Setting vault_password_file in ansible.cfg does the same thing without a flag, so ansible-playbook site.yml simply works - which is what you want, because a flag people must remember is a flag people will forget.
The passphrase itself must come from outside the repository: a file with mode 0600 outside the project, an environment variable, or - best - a script that fetches it from a real secret manager at run time. vault_password_file accepts an executable, and Ansible uses its stdout.
Analogy: Vault is a locked strongbox you can safely leave in a shared office. The box travels with the paperwork; the key does not. Committing the key to the same repository is taping it to the lid.
A worked example
# Create an encrypted variables file
ansible-vault create group_vars/dbservers/vault.yml
# Its contents (before encryption):
vault_db_password: "Pa55w0rd-rotate-2026"
# What Git actually stores:
# $ANSIBLE_VAULT;1.1;AES256
# 66336434653366383462373...
# ansible.cfg - so plain `ansible-playbook site.yml` just works
[defaults]
vault_password_file = ~/.vault_pass
# Everyday operations
ansible-vault view group_vars/dbservers/vault.yml
ansible-vault edit group_vars/dbservers/vault.yml
ansible-vault encrypt_string 'sekrit' --name 'api_token'
encrypt_string produces a single encrypted value you can paste inline into an otherwise readable YAML file. It is convenient for one-off secrets, but it makes the file a mixture of readable and unreadable content, and rotating such a value means finding every occurrence. Whole encrypted files are easier to manage at scale.
One important limitation: Vault protects secrets at rest. During a run they are decrypted in memory and can be rendered into files on the target. no_log: true on tasks that handle secrets keeps them out of the Ansible log and out of --diff; file permissions on the target keep them out of other users' reach. Vault is one layer, not the whole answer.
Warning: Vault protects the file in Git, not the run. A task that renders a secret into a config file will print it under-vor--diffunless you setno_log: true.
Goal: Put this to work in the ansible-vault-secrets lab. Open/labs/ansible, pickansible-vault-secrets, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.