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:

ToolUse Case
HashiCorp VaultCentralized secrets management
AWS Secrets ManagerAWS-native, rotation support
Azure Key VaultAzure-native
SOPSGit-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

  1. Encrypt only secrets, not entire config files
  2. Use vault IDs for multi-environment deployments
  3. Name vault variables with a vault_ prefix for clarity
  4. Never commit vault passwords — use CI/CD secrets or external sources
  5. Rekey regularly — rotate vault passwords periodically
  6. Consider external secrets managers for complex environments
  7. 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.