Ansible Vault: Encrypting Secrets in Your Automation
Why Encrypt Secrets
Ansible playbooks and variable files often contain database passwords, API keys, TLS certificates, OAuth tokens, SSH private keys, and other credentials. Committing these in plaintext to version control is a security incident waiting to happen. Even in private repositories, a single leaked credential can compromise your entire infrastructure. GitHub's own data shows that thousands of secrets are accidentally pushed to public repositories every day, and private repositories are only one misconfigured permission away from exposure.
Ansible Vault solves this by encrypting sensitive data with AES-256 symmetric encryption. The encrypted files remain in your repository alongside everything else, but they are unreadable without the vault password. You get the benefits of version control (history, diffs via ansible-vault diff, pull requests, blame, and bisect) without exposing secrets. The encryption is transparent to Ansible at runtime: when you run a playbook with the vault password, encrypted values are decrypted in memory and never written to disk in plaintext.
What Ansible Vault Is and What It Is Not
It is important to understand the boundaries of Ansible Vault:
| Ansible Vault Does | Ansible Vault Does Not |
|---|---|
| Encrypt files and strings at rest | Manage secret rotation automatically |
| Decrypt transparently during playbook runs | Provide access control per secret |
| Support multiple passwords (vault IDs) | Offer an API for applications to query |
| Integrate with version control workflows | Replace a full secrets manager (like HashiCorp Vault) |
| Use AES-256 encryption | Encrypt data in transit (SSH handles that) |
For small to medium teams, Ansible Vault is often sufficient on its own. For larger organizations with compliance requirements, dynamic secret generation, or lease-based access, pair Ansible Vault with an external secrets manager.
Core Vault Commands
Creating an Encrypted File
ansible-vault create group_vars/production/vault.yml
This opens your default editor ($EDITOR). When you save and close, the file is encrypted on disk. The content on disk looks like this:
$ANSIBLE_VAULT;1.1;AES256
36653632333438303834303339633035623036363636346434353631306663653766643438363134
62636437336530616139303031346237393834303636376132316365626261653362316134623830
...
The header identifies the file as vault-encrypted and specifies the encryption version and algorithm.
Editing an Encrypted File
ansible-vault edit group_vars/production/vault.yml
Ansible decrypts the file into a temporary location (in a secure temp directory with restrictive permissions), opens your editor, and re-encrypts when you save. If you close without saving, the encrypted file remains unchanged.
Encrypting an Existing File
ansible-vault encrypt group_vars/production/secrets.yml
This encrypts the file in place. The plaintext version is overwritten with the encrypted content.
Decrypting a File (Permanent)
ansible-vault decrypt group_vars/production/vault.yml
This writes the plaintext back to disk permanently. Use this sparingly and never commit the decrypted file. A common use case is decrypting temporarily for a migration and re-encrypting with a new password.
Viewing Encrypted Content Without Editing
ansible-vault view group_vars/production/vault.yml
This prints the decrypted content to stdout without modifying the file. Useful for quick inspections.
Changing the Vault Password (Rekeying)
ansible-vault rekey group_vars/production/vault.yml
You will be prompted for the old password and then the new one. To rekey multiple files at once:
ansible-vault rekey group_vars/production/vault.yml group_vars/staging/vault.yml
To rekey using password files (essential for automation):
ansible-vault rekey \
--old-vault-password-file ~/.vault_pass_old \
--new-vault-password-file ~/.vault_pass_new \
group_vars/production/vault.yml
Comparing Encrypted Files
ansible-vault diff group_vars/production/vault.yml
This is particularly useful when reviewing changes to encrypted files before committing.
Full Command Reference
| Command | Purpose | Example |
|---|---|---|
create | Create a new encrypted file | ansible-vault create secrets.yml |
edit | Edit an encrypted file in place | ansible-vault edit secrets.yml |
encrypt | Encrypt an existing plaintext file | ansible-vault encrypt secrets.yml |
decrypt | Permanently decrypt a file | ansible-vault decrypt secrets.yml |
view | View contents without editing | ansible-vault view secrets.yml |
rekey | Change the encryption password | ansible-vault rekey secrets.yml |
encrypt_string | Encrypt a single string value | ansible-vault encrypt_string 'secret' |
Encrypting Entire Files vs Inline Variables
Strategy 1: Encrypting Entire Files
The simplest and most common approach is to encrypt entire variable files. A proven pattern is to split variables into two files per environment group:
group_vars/
all/
vars.yml # Shared non-sensitive variables
production/
vars.yml # Non-sensitive production variables (plaintext)
vault.yml # Sensitive production variables (encrypted)
staging/
vars.yml # Non-sensitive staging variables (plaintext)
vault.yml # Sensitive staging variables (encrypted)
In group_vars/production/vault.yml (before encryption):
vault_db_password: "s3cret_pr0duction_p@ss"
vault_db_replication_password: "r3pl_s3cret_p@ss"
vault_api_key: "ak_live_abc123def456"
vault_smtp_password: "smtp_s3cret"
vault_redis_password: "r3d1s_auth_t0ken"
vault_jwt_secret: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
vault_tls_private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQE...
-----END PRIVATE KEY-----
vault_oauth_client_secret: "oauth_cl13nt_s3cret_v4lue"
vault_backup_encryption_key: "b4ckup_3ncrypt10n_k3y"
In group_vars/production/vars.yml, reference vault variables with a clear naming convention:
# Database
db_host: db-primary.internal.example.com
db_port: 5432
db_name: myapp_production
db_user: myapp
db_password: "{{ vault_db_password }}"
db_replication_password: "{{ vault_db_replication_password }}"
# API
api_base_url: https://api.example.com
api_key: "{{ vault_api_key }}"
# Email
smtp_host: smtp.example.com
smtp_port: 587
smtp_user: noreply@example.com
smtp_password: "{{ vault_smtp_password }}"
# Redis
redis_host: redis.internal.example.com
redis_port: 6379
redis_password: "{{ vault_redis_password }}"
# Authentication
jwt_secret: "{{ vault_jwt_secret }}"
jwt_expiry: 3600
# TLS
tls_certificate: /etc/ssl/certs/server.crt
tls_private_key: "{{ vault_tls_private_key }}"
This pattern has several advantages. When someone reads vars.yml, they can see every variable the application needs and understand that db_password comes from the vault without needing to decrypt anything. The vault_ prefix convention makes it immediately obvious which values are secrets. You can grep for vault_ across your entire codebase to audit secret usage.
Strategy 2: Encrypting Inline Variables
If you prefer to keep everything in one file, encrypt individual values with encrypt_string:
# Encrypt a string with a name
ansible-vault encrypt_string 's3cret_pr0duction_p@ss' --name 'db_password'
Output:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
62313365396662343061393464336163383764316534363531323563633839393539343932373233
...
Paste this directly into your variable file. The rest of the file remains plaintext while this specific value is encrypted.
To encrypt from stdin (avoids the secret appearing in shell history):
echo -n 's3cret_pr0duction_p@ss' | ansible-vault encrypt_string --stdin-name 'db_password'
To encrypt with a vault ID:
ansible-vault encrypt_string --vault-id prod@~/.vault_pass_prod 's3cret' --name 'db_password'
Reading from a file to avoid shell history entirely:
ansible-vault encrypt_string --stdin-name 'tls_key' < /path/to/private.key
Strategy Comparison
| Aspect | Encrypted Files | Inline Encryption |
|---|---|---|
| Simplicity | Simpler to manage | More complex per variable |
| Grep-ability | Cannot grep encrypted files | Can grep variable names in plaintext |
| Diff in PRs | Entire file diff is opaque | Only encrypted values are opaque |
| Editor workflow | Must use ansible-vault edit | Edit normally, only values encrypted |
| Team preference | More common in practice | Preferred by some for mixed files |
| Rekeying | Rekey one file per group | Must re-encrypt each string |
Most teams choose the split-file approach (Strategy 1) for its simplicity and clear separation.
Vault Password Management
Password Files
Typing the password every time gets tedious. Use a password file:
# Create a password file with a strong random password
openssl rand -base64 32 > ~/.vault_pass
chmod 600 ~/.vault_pass
# Use it with a playbook
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Set it permanently in ansible.cfg:
[defaults]
vault_password_file = ~/.vault_pass
Never commit the password file. Add it to .gitignore:
# Vault password files
.vault_pass
*.vault_pass
.vault_pass_*
vault_password*
Password File as an Executable Script
The password file can be an executable script. Ansible runs it and uses stdout as the password. This is the recommended approach for teams because it integrates with your existing secret management:
#!/bin/bash
# .vault_pass.sh - fetch from macOS Keychain
security find-generic-password -a "ansible-vault" -s "production" -w
#!/bin/bash
# .vault_pass.sh - fetch from 1Password CLI
op item get "Ansible Vault Production" --field password
#!/bin/bash
# .vault_pass.sh - fetch from AWS Secrets Manager
aws secretsmanager get-secret-value \
--secret-id ansible/vault-password \
--query SecretString \
--output text
#!/bin/bash
# .vault_pass.sh - fetch from HashiCorp Vault
vault kv get -field=password secret/ansible/vault
#!/bin/bash
# .vault_pass.sh - fetch from Azure Key Vault
az keyvault secret show \
--vault-name my-keyvault \
--name ansible-vault-password \
--query value \
--output tsv
#!/bin/bash
# .vault_pass.sh - fetch from GNOME Keyring (Linux)
secret-tool lookup service ansible-vault environment production
Make the script executable:
chmod +x .vault_pass.sh
ansible-playbook site.yml --vault-password-file .vault_pass.sh
Multi-Vault IDs
Large organizations often need different passwords for different environments. If a staging password leaks, production secrets must remain safe. Vault IDs let you use multiple passwords in a single playbook run.
Encrypting with a Vault ID
# Encrypt production secrets with the "prod" vault ID
ansible-vault encrypt --vault-id prod@~/.vault_pass_prod group_vars/production/vault.yml
# Encrypt staging secrets with the "staging" vault ID
ansible-vault encrypt --vault-id staging@~/.vault_pass_staging group_vars/staging/vault.yml
# Encrypt shared secrets with a "shared" vault ID
ansible-vault encrypt --vault-id shared@~/.vault_pass_shared group_vars/all/vault.yml
The encrypted file header now includes the vault ID:
$ANSIBLE_VAULT;1.2;AES256;prod
Running with Multiple Vault IDs
ansible-playbook site.yml \
--vault-id prod@~/.vault_pass_prod \
--vault-id staging@~/.vault_pass_staging \
--vault-id shared@~/.vault_pass_shared
Ansible matches each encrypted value to the correct password using the vault ID label. This lets your production and staging teams have separate passwords while sharing the same playbook repository.
Vault IDs with Scripts
ansible-playbook site.yml \
--vault-id prod@./get_vault_pass.sh \
--vault-id staging@./get_vault_pass.sh
The script receives the vault ID as its first argument, so a single script can handle multiple environments:
#!/bin/bash
# get_vault_pass.sh - single script for all vault IDs
VAULT_ID="${1}"
case "${VAULT_ID}" in
prod)
aws secretsmanager get-secret-value \
--secret-id ansible/vault-password-prod \
--query SecretString --output text
;;
staging)
aws secretsmanager get-secret-value \
--secret-id ansible/vault-password-staging \
--query SecretString --output text
;;
*)
echo "Unknown vault ID: ${VAULT_ID}" >&2
exit 1
;;
esac
Vault IDs in ansible.cfg
[defaults]
vault_identity_list = prod@~/.vault_pass_prod, staging@~/.vault_pass_staging, shared@~/.vault_pass_shared
Using Vault in Playbooks
Vault-encrypted variables work transparently in playbooks. No special syntax is needed when referencing them:
---
- name: Deploy application
hosts: webservers
become: true
tasks:
- name: Configure database connection
template:
src: database.yml.j2
dest: /opt/myapp/config/database.yml
owner: deploy
group: deploy
mode: "0600"
no_log: true
- name: Set application environment variables
copy:
content: |
DB_HOST={{ db_host }}
DB_PORT={{ db_port }}
DB_NAME={{ db_name }}
DB_USER={{ db_user }}
DB_PASSWORD={{ db_password }}
REDIS_URL=redis://:{{ redis_password }}@{{ redis_host }}:{{ redis_port }}/0
JWT_SECRET={{ jwt_secret }}
SMTP_PASSWORD={{ smtp_password }}
dest: /opt/myapp/.env
owner: deploy
group: deploy
mode: "0600"
no_log: true
notify: Restart Application
- name: Deploy TLS certificate
copy:
content: "{{ tls_private_key }}"
dest: /etc/ssl/private/server.key
owner: root
group: ssl-cert
mode: "0640"
no_log: true
notify: Reload Nginx
- name: Configure application secrets in systemd override
copy:
content: |
[Service]
Environment="SECRET_KEY={{ vault_secret_key }}"
Environment="API_KEY={{ vault_api_key }}"
dest: /etc/systemd/system/myapp.service.d/secrets.conf
owner: root
group: root
mode: "0600"
no_log: true
notify:
- Reload systemd
- Restart Application
handlers:
- name: Restart Application
service:
name: myapp
state: restarted
- name: Reload Nginx
service:
name: nginx
state: reloaded
- name: Reload systemd
systemd:
daemon_reload: true
The Jinja2 template database.yml.j2:
production:
adapter: postgresql
host: {{ db_host }}
port: {{ db_port }}
database: {{ db_name }}
username: {{ db_user }}
password: {{ db_password }}
pool: 25
timeout: 5000
ssl_mode: require
Run the playbook:
ansible-playbook deploy.yml --ask-vault-pass
# or
ansible-playbook deploy.yml --vault-password-file ~/.vault_pass
Integrating Vault with CI/CD Pipelines
In CI/CD pipelines, vault passwords should come from the CI system's secret management, not from files committed to the repository. The vault password should be a CI/CD secret that is injected at runtime.
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy Application
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options: [staging, production]
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment || 'staging' }}
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Ansible
run: pip install ansible
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
echo "${{ secrets.SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
- name: Run Ansible Playbook
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
ANSIBLE_HOST_KEY_CHECKING: "False"
run: |
echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
chmod 600 .vault_pass
ansible-playbook -i inventory/ site.yml \
--vault-password-file .vault_pass \
--limit ${{ inputs.environment || 'staging' }}
rm -f .vault_pass
GitLab CI
# .gitlab-ci.yml
stages:
- lint
- deploy
variables:
ANSIBLE_HOST_KEY_CHECKING: "False"
.ansible_setup: &ansible_setup
before_script:
- pip install ansible
- echo "$VAULT_PASSWORD" > .vault_pass
- chmod 600 .vault_pass
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
- chmod 600 ~/.ssh/deploy_key
after_script:
- rm -f .vault_pass
lint:
stage: lint
image: python:3.12
script:
- pip install ansible-lint
- ansible-lint site.yml
deploy_staging:
stage: deploy
image: python:3.12
<<: *ansible_setup
script:
- ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --limit staging
environment:
name: staging
only:
- main
deploy_production:
stage: deploy
image: python:3.12
<<: *ansible_setup
script:
- ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --limit production
environment:
name: production
when: manual
only:
- main
Jenkins Pipeline
pipeline {
agent any
environment {
VAULT_PASSWORD = credentials('ansible-vault-password')
}
stages {
stage('Deploy') {
steps {
sh '''
echo "$VAULT_PASSWORD" > .vault_pass
chmod 600 .vault_pass
ansible-playbook -i inventory/ site.yml \
--vault-password-file .vault_pass \
--limit ${ENVIRONMENT}
rm -f .vault_pass
'''
}
}
}
post {
always {
sh 'rm -f .vault_pass'
}
}
}
Using Environment Variables Directly
Ansible supports ANSIBLE_VAULT_PASSWORD_FILE as an environment variable:
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
ansible-playbook site.yml
Or use a script that reads from an environment variable:
#!/bin/bash
# vault_pass_from_env.sh
echo "$ANSIBLE_VAULT_PASSWORD"
chmod +x vault_pass_from_env.sh
export ANSIBLE_VAULT_PASSWORD="my-secret-password"
export ANSIBLE_VAULT_PASSWORD_FILE=./vault_pass_from_env.sh
ansible-playbook site.yml
Lookup Plugins for External Secret Managers
For organizations that centralize secrets outside of Ansible, lookup plugins pull values at runtime without ever writing them to Ansible variable files. This is the recommended approach for large-scale deployments where secrets must have audit trails, automatic rotation, and fine-grained access control.
HashiCorp Vault
Install the collection:
ansible-galaxy collection install community.hashi_vault
pip install hvac
Use the lookup in your playbook:
- name: Retrieve database password from HashiCorp Vault
set_fact:
db_password: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/production/database:password',
url='https://vault.example.com:8200',
token=lookup('env', 'VAULT_TOKEN')) }}"
no_log: true
With AppRole authentication (recommended for CI/CD):
- name: Authenticate with AppRole and retrieve secrets
set_fact:
db_creds: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/production/database',
auth_method='approle',
role_id=lookup('env', 'VAULT_ROLE_ID'),
secret_id=lookup('env', 'VAULT_SECRET_ID'),
url='https://vault.example.com:8200') }}"
no_log: true
- name: Use the credentials
template:
src: database.yml.j2
dest: /opt/myapp/config/database.yml
vars:
db_user: "{{ db_creds.data.username }}"
db_password: "{{ db_creds.data.password }}"
no_log: true
With environment-based authentication:
# Export these before running:
# export VAULT_ADDR=https://vault.example.com:8200
# export VAULT_TOKEN=s.xxxxxxxxxxxxxxxx
- name: Get all database credentials
set_fact:
db_creds: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/production/database') }}"
no_log: true
AWS Systems Manager Parameter Store
ansible-galaxy collection install amazon.aws
pip install boto3
- name: Get parameter from SSM
set_fact:
api_key: "{{ lookup('amazon.aws.ssm_parameter', '/production/api_key', region='us-east-1') }}"
no_log: true
- name: Get encrypted parameter
set_fact:
db_password: "{{ lookup('amazon.aws.ssm_parameter', '/production/db_password', decrypt=true, region='us-east-1') }}"
no_log: true
- name: Get multiple parameters by path
set_fact:
app_secrets: "{{ lookup('amazon.aws.ssm_parameter', '/production/', bypath=true, decrypt=true, region='us-east-1') }}"
no_log: true
AWS Secrets Manager
- name: Get secret from Secrets Manager
set_fact:
db_creds: "{{ lookup('amazon.aws.secretsmanager_secret', 'production/database', region='us-east-1') | from_json }}"
no_log: true
- name: Use credentials
template:
src: database.yml.j2
dest: /opt/myapp/config/database.yml
vars:
db_user: "{{ db_creds.username }}"
db_password: "{{ db_creds.password }}"
db_host: "{{ db_creds.host }}"
no_log: true
Azure Key Vault
ansible-galaxy collection install azure.azcollection
pip install azure-keyvault-secrets azure-identity
- name: Get secret from Azure Key Vault
set_fact:
db_password: "{{ lookup('azure.azcollection.azure_keyvault_secret',
'db-password',
vault_url='https://my-keyvault.vault.azure.net') }}"
no_log: true
External Secrets Comparison
| Feature | Ansible Vault | HashiCorp Vault | AWS SSM | AWS Secrets Manager |
|---|---|---|---|---|
| Encryption | AES-256 at rest | AES-256 + transit | AWS KMS | AWS KMS |
| Access control | Password-based | Policies + tokens | IAM policies | IAM policies |
| Rotation | Manual rekey | Automatic leases | Manual | Automatic rotation |
| Audit trail | Git history | Detailed audit log | CloudTrail | CloudTrail |
| Dynamic secrets | No | Yes (databases, AWS, etc.) | No | Limited |
| Cost | Free | Free (OSS) or paid | Free (standard) | $0.40/secret/month |
| Setup effort | Minimal | Moderate to high | Low | Low |
Automating Vault Password Rotation
Regular password rotation is a security best practice. Automate it with a script that rekeys all encrypted files:
#!/bin/bash
# rotate-vault-password.sh
set -euo pipefail
OLD_PASS_FILE="$HOME/.vault_pass"
NEW_PASS_FILE="$HOME/.vault_pass_new"
# Generate new password
openssl rand -base64 32 > "$NEW_PASS_FILE"
chmod 600 "$NEW_PASS_FILE"
# Find and rekey all vault-encrypted files
echo "Finding vault-encrypted files..."
VAULT_FILES=$(grep -rl '^\$ANSIBLE_VAULT' . --include="*.yml" --include="*.yaml" || true)
if [ -z "$VAULT_FILES" ]; then
echo "No vault-encrypted files found."
exit 0
fi
echo "Rekeying files..."
for file in $VAULT_FILES; do
echo " Rekeying: $file"
ansible-vault rekey \
--old-vault-password-file "$OLD_PASS_FILE" \
--new-vault-password-file "$NEW_PASS_FILE" \
"$file"
done
# Replace old password with new
cp "$NEW_PASS_FILE" "$OLD_PASS_FILE"
rm "$NEW_PASS_FILE"
echo "Vault password rotation complete."
echo "Remember to update the password in your CI/CD secrets and team password manager."
Best Practices for Managing Vault Passwords in Teams
Use a password manager for the vault password. Store the vault password in 1Password, Bitwarden, HashiCorp Vault, or AWS Secrets Manager. Write a vault password script that retrieves it:
#!/bin/bash
# Fetch from 1Password
op item get "Ansible Vault Production" --field password
Rotate vault passwords regularly. Set a rotation schedule (quarterly at minimum) and use the automated rotation script above. After rotation, update the password in all CI/CD systems and team password managers.
Separate vault passwords by environment. Production and staging must use different vault passwords. If a staging password leaks, production secrets remain safe. Use vault IDs to manage this cleanly.
Review encrypted file changes in pull requests. You cannot see the diff of an encrypted file in a standard PR. Set up a Git diff driver to enable plaintext diffs:
# .gitattributes
*vault*.yml diff=ansible-vault
# .git/config (or global .gitconfig)
[diff "ansible-vault"]
textconv = ansible-vault view
cachetextconv = false
This lets git diff and git log -p show plaintext diffs of encrypted files (assuming you have the vault password available). The diffs are generated locally and never stored in plaintext.
Minimize what you encrypt. Only encrypt actual secrets. Configuration values like port numbers, package names, hostnames, and feature flags do not need encryption. Encrypting everything makes debugging harder, slows down development, and creates unnecessary cognitive overhead. Use the split-file pattern (vars.yml + vault.yml) to keep the boundary clear.
Never echo or debug secrets in output. Use no_log: true on every task that handles sensitive data:
- name: Set database password
lineinfile:
path: /opt/myapp/.env
regexp: "^DB_PASSWORD="
line: "DB_PASSWORD={{ db_password }}"
no_log: true
- name: Create database user
community.postgresql.postgresql_user:
name: myapp
password: "{{ db_password }}"
state: present
no_log: true
Without no_log, the password appears in Ansible's stdout when the task runs, which is especially dangerous in CI/CD logs that are often accessible to many team members.
Use ansible-lint to catch unencrypted secrets. Configure ansible-lint to warn about potential plaintext secrets:
# .ansible-lint
warn_list:
- no-log-password
Pre-commit hooks for vault files. Use a pre-commit hook to prevent accidentally committing decrypted vault files:
#!/bin/bash
# .git/hooks/pre-commit
# Prevent committing unencrypted vault files
FILES=$(git diff --cached --name-only | grep -E "vault\.yml$" || true)
for f in $FILES; do
if ! head -1 "$f" | grep -q '^\$ANSIBLE_VAULT'; then
echo "ERROR: $f appears to be an unencrypted vault file!"
echo "Run: ansible-vault encrypt $f"
exit 1
fi
done
Or use the pre-commit framework:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/ansible-community/ansible-lint
rev: v24.2.0
hooks:
- id: ansible-lint
- repo: local
hooks:
- id: check-vault-encrypted
name: Check vault files are encrypted
entry: bash -c 'for f in "$@"; do if echo "$f" | grep -q "vault" && ! head -1 "$f" | grep -q "^\$ANSIBLE_VAULT"; then echo "ERROR: $f is not encrypted"; exit 1; fi; done'
language: system
files: "vault.*\\.yml$"
Troubleshooting Vault Issues
Common Errors and Solutions
"Decryption failed" -- Wrong vault password. Verify you are using the correct password file or vault ID.
# Test which vault ID a file uses
head -1 group_vars/production/vault.yml
# Output: $ANSIBLE_VAULT;1.2;AES256;prod
"ERROR! input is not vault encrypted data" -- The file is not encrypted or has been corrupted.
# Check if a file is vault-encrypted
file group_vars/production/vault.yml
head -1 group_vars/production/vault.yml
"Vault password client script had non-zero exit" -- Your password script is failing. Test it manually:
bash -x .vault_pass.sh
Performance with many encrypted files -- If you have many vault-encrypted files, consider consolidating secrets into fewer files. Each encrypted file requires a separate decryption operation.
"no vault secrets were found that could decrypt" -- You are using vault IDs, but the vault ID on the encrypted file does not match any provided vault IDs:
# Check the vault ID in the file header
head -1 encrypted_file.yml
# Provide the matching vault ID
ansible-playbook site.yml --vault-id prod@~/.vault_pass_prod
Related Articles
Ansible Dynamic Inventory: Automating Cloud Infrastructure
Use dynamic inventories to automatically discover and manage cloud infrastructure — AWS EC2, Azure VMs, and GCP instances with Ansible inventory plugins.
Ansible Fundamentals: Your First Playbook to Production
Learn Ansible from scratch — install Ansible, write your first playbook, understand modules, manage inventories, and automate server configuration.
Ansible Roles and Galaxy: Structuring Automation at Scale
Structure your Ansible automation with roles for reusability. Learn role directory structure, Galaxy usage, dependencies, and testing with Molecule.