The Problem with Clear-text Secrets
A core principle of modern Operations is that everything lives in Git. However, you often need sensitive data to perform automation:
- Database administrator passwords (to create schemas)
- API Keys (to talk to Datadog or AWS)
- Private SSH Keys
- SSL/TLS Certificates
If you commit these secrets to a Git repository in plain text, you create a massive security vulnerability. Anyone with read access to the repository—or anyone who compromises it in the future—can extract those production passwords.
The solution is encryption at rest: Ansible Vault.
What is Ansible Vault?
Ansible Vault allows you to securely encrypt strings, files, or entire Variable dictionaries using AES-256 encryption.
The workflow operates like this:
- You use the
ansible-vaultCLI command to encrypt a file. You provide a master password. - The file becomes an unreadable block of ciphertext.
- You commit this ciphertext file safely to Git.
- When you execute an
ansible-playbookcommand, you supply the master password. - Ansible dynamically decrypts the contents in memory (never writing the unencrypted secret back to the hard drive), uses the variable to complete the task, and drops it from memory.
Using ansible-vault
1. Creating a new encrypted file
To create a new encrypted file from scratch:
ansible-vault create vars/db_secrets.ymlYou will be prompted to essentially invent a new master password. Ensure you remember it (or better, store it in your team's password manager, like 1Password or BitWarden).
After confirming the password, it will dump you into a vi editor.
Type your YAML secrets:
# Inside the editor
db_root_password: "SuperSecretPassword123!"
datadog_api_key: "abc123def456"Save and exit. If you immediately run cat vars/db_secrets.yml, you will NOT see your variables. You will see:
$ANSIBLE_VAULT;1.1;AES256
38363765383566373733363339366635393539346638363462613565346332613337373733366366
...You can now safely git commit this file!
2. Editing an encrypted file
To modify variables in the future, do not decrypt and re-encrypt the file manually. Just use the edit command:
ansible-vault edit vars/db_secrets.yml
# Prompts for Vault password, opens editor, saves ciphertext automatically.3. Encrypting an existing plain-text file
If you already have a YAML file with secrets, encrypt it in place:
ansible-vault encrypt vars/my_existing_file.ymlExecuting Playbooks with Vaults
When your playbook references an encrypted vars_files document, it has no native way to decrypt it. Trying to run the playbook normally will trigger a hard crash:
“ERROR! Attempting to decrypt but no vault secrets found”
You must provide the master password at runtime.
Method A: Interactive Prompt
The easiest method for local execution is to tell Ansible to intentionally pause and ask you to type the password.
ansible-playbook site.yml --ask-vault-passMethod B: Password File (For CI/CD)
CI/CD pipelines (like Jenkins or GitHub Actions) cannot pause to wait for a human to interactively type a password. You must configure them to read the password from a designated file on the build server.
# This file contains the master password in plain text.
# Do NOT commit the vault_pass.txt file to Git!
echo "my_master_vault_password" > ~/.vault_pass.txt
# Run the playbook utilizing the password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass.txtIn a professional CI/CD pipeline, the string my_master_vault_password is injected directly by the pipeline's native Secret Management tool (like GitHub Secrets).
Encrypting Individual Strings (Advanced)
Encrypting entire files causes minor annoyances: You can't see the Git diff when a peer modifies a variable, because the whole file is encrypted garbage!
Ansible Vault allows you to encrypt individual strings while leaving the rest of the YAML file in plain text. This is highly recommended for readability.
- Generate the encrypted string:
ansible-vault encrypt_string 'SuperSecretPassword123!' --name 'db_root_password'- The output will look like a massive block of YAML. Copy the entire output.
- Paste it directly into your regular, unencrypted
vars/main.ymlfile:
# vars/main.yml (Plain text file)
database_port: 5432
database_user: postgres
# The encrypted string variable (Safe to commit to Git)
db_root_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
36373866383664653531393630653036666138616131343734616238646633653166663233376536
3537386164393366363237666236303233383030306161340a646430336236616630353664323265
64636662326162336338373738306362306263623838323638356133616630303862356534353235
3565353564636239630a663965616663643764663734326561633532353361666338396531393036
35366535616164316130323061386131666530666531633333333465313531383035Now, anyone can read the file and see that database_port is 5432, but they cannot read the database password. When Ansible runs, it will attempt to decrypt only the variables tagged with !vault.