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

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 -v or --diff unless you set no_log: true.
Goal: Put this to work in the ansible-vault-secrets lab. Open /labs/ansible, pick ansible-vault-secrets, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.