Ansible Vault: Encrypting Secrets in Playbooks
Secure your Ansible playbooks by encrypting sensitive data with Ansible Vault, managing vault passwords, and integrating with CI/CD pipelines.
Storing passwords, API keys, and certificates in plain text is a security nightmare. Ansible Vault lets you encrypt sensitive data while keeping it version-controlled alongside your playbooks. Here’s how to use it properly.
Vault Basics
Creating Encrypted Files
# Create a new encrypted file
ansible-vault create secrets.yml
# Opens your default editor, then encrypts on save
# Example content:
# db_password: supersecret123
# api_key: sk-xxxxxxxxxxxxxxxx
# Encrypt an existing file
ansible-vault encrypt vars/prod-secrets.yml
# Decrypt a file (use carefully!)
ansible-vault decrypt vars/prod-secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# View encrypted file without editing
ansible-vault view secrets.yml
Encrypted File Format
# secrets.yml (encrypted)
$ANSIBLE_VAULT;1.1;AES256
61626364656667686970717273747576777879306132333435363738393061626364656667686970
71727374757677787930613233343536373839306162636465666768697071727374757677787930
...
Using Vault in Playbooks
Include Encrypted Variables
# playbook.yml
---
- name: Deploy application
hosts: webservers
vars_files:
- vars/common.yml
- vars/secrets.yml # Encrypted file
tasks:
- name: Configure database connection
template:
src: db-config.j2
dest: /etc/app/database.yml
mode: '0600'
# vars/secrets.yml (unencrypted content)
db_password: "supersecret123"
api_key: "sk-xxxxxxxxxxxxxxxx"
ssl_private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASC...
-----END PRIVATE KEY-----
Run Playbook with Vault
# Prompt for vault password
ansible-playbook playbook.yml --ask-vault-pass
# Use password file
ansible-playbook playbook.yml --vault-password-file ~/.vault_pass
# Use environment variable (for CI/CD)
ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass ansible-playbook playbook.yml
Encrypting Single Values
Instead of encrypting entire files, encrypt individual values:
# Encrypt a single string
ansible-vault encrypt_string 'supersecret123' --name 'db_password'
# Output:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
61626364656667686970717273747576777879306132333435363738393061626364656667686970
Mixed Plain and Encrypted Variables
# vars/database.yml
db_host: db.example.com
db_port: 5432
db_name: production
db_user: app_user
# Only the password is encrypted
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
61626364656667686970717273747576777879306132333435363738393061626364656667686970
71727374757677787930613233343536373839306162636465666768697071727374757677787930
Multiple Vault Passwords
Use different passwords for different environments:
Vault IDs
# Create vault with ID
ansible-vault create --vault-id dev@prompt secrets-dev.yml
ansible-vault create --vault-id prod@~/.vault_pass_prod secrets-prod.yml
# Encrypt string with vault ID
ansible-vault encrypt_string --vault-id prod@~/.vault_pass_prod 'secret' --name 'api_key'
# secrets-prod.yml header shows vault ID
$ANSIBLE_VAULT;1.2;AES256;prod
Using Multiple Vault IDs
# Run playbook with multiple vault passwords
ansible-playbook site.yml \
--vault-id dev@~/.vault_pass_dev \
--vault-id prod@~/.vault_pass_prod
# ansible.cfg - configure vault IDs
[defaults]
vault_identity_list = dev@~/.vault_pass_dev, prod@~/.vault_pass_prod
Password Management
Password File
# Create password file (600 permissions!)
echo 'your-vault-password' > ~/.vault_pass
chmod 600 ~/.vault_pass
Script for Password
#!/bin/bash
# vault-password.sh - fetch from secrets manager
# AWS Secrets Manager
aws secretsmanager get-secret-value \
--secret-id ansible-vault-password \
--query SecretString \
--output text
# HashiCorp Vault
vault kv get -field=password secret/ansible/vault
# Make executable and use
chmod +x vault-password.sh
ansible-playbook playbook.yml --vault-password-file ./vault-password.sh
GPG-Encrypted Password
# Encrypt vault password with GPG
echo 'vault-password' | gpg --encrypt --recipient [email protected] > .vault_pass.gpg
# Script to decrypt
#!/bin/bash
gpg --quiet --decrypt .vault_pass.gpg
Rekeying Vaults
Change vault passwords without decrypting files:
# Rekey single file
ansible-vault rekey secrets.yml
# Rekey with vault IDs
ansible-vault rekey --vault-id old@prompt --new-vault-id new@prompt secrets.yml
# Rekey multiple files
ansible-vault rekey vars/*.yml
CI/CD Integration
GitHub Actions
# .github/workflows/ansible.yml
name: Deploy with Ansible
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Ansible
run: pip install ansible
- name: Create vault password file
run: echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass
- name: Run playbook
run: |
ansible-playbook \
-i inventory/production \
--vault-password-file .vault_pass \
playbooks/deploy.yml
env:
ANSIBLE_HOST_KEY_CHECKING: "false"
- name: Cleanup
if: always()
run: rm -f .vault_pass
GitLab CI
# .gitlab-ci.yml
deploy:
stage: deploy
image: ansible/ansible-runner:latest
script:
- echo "$VAULT_PASSWORD" > .vault_pass
- chmod 600 .vault_pass
- ansible-playbook -i inventory/prod --vault-password-file .vault_pass site.yml
after_script:
- rm -f .vault_pass
only:
- main
Jenkins Pipeline
pipeline {
agent any
environment {
ANSIBLE_VAULT_PASSWORD = credentials('ansible-vault-password')
}
stages {
stage('Deploy') {
steps {
sh '''
echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
chmod 600 .vault_pass
ansible-playbook \
-i inventory/production \
--vault-password-file .vault_pass \
site.yml
'''
}
post {
always {
sh 'rm -f .vault_pass'
}
}
}
}
}
Best Practices
File Organization
ansible/
├── ansible.cfg
├── inventory/
│ ├── production
│ └── staging
├── group_vars/
│ ├── all/
│ │ ├── vars.yml # Plain variables
│ │ └── vault.yml # Encrypted (vault)
│ ├── webservers/
│ │ ├── vars.yml
│ │ └── vault.yml
│ └── databases/
│ ├── vars.yml
│ └── vault.yml
├── host_vars/
│ └── db1.example.com/
│ ├── vars.yml
│ └── vault.yml
└── playbooks/
└── site.yml
Naming Convention
# group_vars/all/vars.yml - references vault variables
db_connection:
host: "{{ db_host }}"
port: 5432
user: "{{ db_user }}"
password: "{{ vault_db_password }}" # Prefix with vault_
api_config:
endpoint: https://api.example.com
key: "{{ vault_api_key }}"
# group_vars/all/vault.yml - encrypted vault variables
vault_db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
vault_api_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
Don’t Encrypt Everything
Only encrypt actually sensitive values:
# BAD: Everything encrypted
# secrets.yml
$ANSIBLE_VAULT;1.1;AES256
...
# Contains: db_host, db_port, db_name, db_user, db_password
# GOOD: Only secrets encrypted
# vars.yml (plain)
db_host: db.example.com
db_port: 5432
db_name: production
db_user: app_user
# vault.yml (encrypted)
vault_db_password: ...
Encrypting Files (Certificates, Keys)
SSL Certificates
# roles/nginx/tasks/main.yml
- name: Copy SSL certificate
copy:
content: "{{ vault_ssl_certificate }}"
dest: /etc/nginx/ssl/server.crt
mode: '0644'
- name: Copy SSL private key
copy:
content: "{{ vault_ssl_private_key }}"
dest: /etc/nginx/ssl/server.key
mode: '0600'
# group_vars/webservers/vault.yml
vault_ssl_certificate: |
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAMH...
-----END CERTIFICATE-----
vault_ssl_private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
Binary Files
# Encrypt binary file
ansible-vault encrypt files/keystore.jks
# Use in playbook
- name: Copy keystore
copy:
src: files/keystore.jks # Ansible auto-decrypts
dest: /opt/app/keystore.jks
mode: '0600'
Vault with Lookups
Dynamic Secret Retrieval
# Use vault-encrypted file in lookup
- name: Get database password
set_fact:
db_password: "{{ lookup('file', 'secrets/db_password.txt') }}"
# secrets/db_password.txt is vault-encrypted
# Or include encrypted vars dynamically
- name: Include environment secrets
include_vars:
file: "secrets/{{ env }}-secrets.yml"
Debugging Vault Issues
# Verify file is encrypted
head -1 secrets.yml
# Should show: $ANSIBLE_VAULT;1.1;AES256
# Check which vault ID
ansible-vault view --vault-id prod@prompt secrets.yml
# Debug variable values (careful in logs!)
ansible-playbook playbook.yml -vvv --ask-vault-pass
Alternatives to Ansible Vault
When Ansible Vault isn’t enough:
| Tool | Use Case |
|---|---|
| HashiCorp Vault | Centralized secrets management |
| AWS Secrets Manager | AWS-native, rotation support |
| Azure Key Vault | Azure-native |
| SOPS | Git-friendly, multiple backends |
Using HashiCorp Vault
# Use hashi_vault lookup
- name: Get secret from HashiCorp Vault
set_fact:
db_password: "{{ lookup('hashi_vault', 'secret/data/database:password') }}"
Using AWS Secrets Manager
- name: Get secret from AWS
set_fact:
api_key: "{{ lookup('aws_secret', 'prod/api-key') }}"
When NOT to Use Ansible Vault
Ansible Vault isn’t ideal for:
- Secrets that rotate frequently — use a secrets manager instead
- Secrets shared across many systems — centralize with HashiCorp Vault
- Secrets that need audit logging — use a dedicated secrets platform
- Very large files — encryption/decryption adds overhead
Key Takeaways
- Encrypt only secrets, not entire config files
- Use vault IDs for multi-environment deployments
- Name vault variables with a
vault_prefix for clarity - Never commit vault passwords — use CI/CD secrets or external sources
- Rekey regularly — rotate vault passwords periodically
- Consider external secrets managers for complex environments
- Version control encrypted files — that’s the whole point
Ansible Vault strikes a balance between security and simplicity. For most teams, it’s the right choice for managing deployment secrets without adding infrastructure complexity.