Ansible Vault #

Every serious Ansible project stores sensitive data: database passwords, API keys, SSL private keys, authentication tokens, and third-party service credentials. Storing these values in plaintext in a Git repository is an unacceptable security risk — one careless commit, one clone to a lost laptop, or one careless contributor, and the entire infrastructure could be exposed to the public. Ansible Vault is Ansible’s built-in solution to this problem: it encrypts individual files or strings using AES-256, so sensitive data can be stored safely in Git without the risk of leaking to unauthorized people. This article discusses how Vault works behind the scenes, the two encryption modes available, strategies for managing vault passwords, integration with roles and CI/CD, and the patterns you must avoid so your Vault implementation truly provides the expected protection.

Why Vault Matters #

Before discussing syntax and commands, it’s important to understand what threats Vault actually mitigates. There are three common scenarios that cause secrets to leak from Ansible repositories, and each scenario requires different protection.

Scenario one: clone to an unauthorized developer. Ansible repositories are cloned by many people — not just engineers handling production deployments, but also data analysts who need log access, project managers who want to read documentation, or new contributors onboarding. Without encryption, everyone with read access to the repository sees all the secrets.

Scenario two: leaking automated backups. Many teams back up Git repositories to cloud services like GitHub, GitLab, Bitbucket, or internal mirrors. These backups often have looser access controls than the main repository — anyone with access to the GitHub organization can see all branches, including branches containing unrotated secrets.

Scenario three: history that can’t be deleted. Git stores the entire change history. If a secrets.yml file was ever committed in plaintext, deleting it in the next commit doesn’t remove the record from history. Anyone with repository access before the fix commit can recover that file with git log -p.

Vault answers all three scenarios with one approach: sensitive files or strings never exist in plaintext in Git. Files encrypted by Vault only contain ciphertext that can’t be read without the vault password. Anyone cloning the repository without the vault password sees useless ciphertext.

Vault isn’t a replacement for a secret manager — it’s a complement. Vault encrypts data at rest in the repository, but it doesn’t prevent the vault password from leaking, doesn’t record detailed audit trails, and doesn’t support automatic rotation. For very sensitive secrets like database root passwords or production private keys, consider using Vault for local caching and an external secret manager (HashiCorp Vault, AWS Secrets Manager) as the primary source. The encryption article discusses this integration pattern in depth.

Architecture: How Vault Works Behind the Scenes #

Understanding Vault’s workflow helps you make the right decisions when troubleshooting problems or designing new workflows. The following diagram shows the complete flow from plaintext to ciphertext and back.

flowchart TD
    A["Plaintext file<br/>secrets.yml"] --> B{"ansible-vault<br/>encrypt"}
    B --> C["Vault password<br/>from prompt / file / env"]
    C --> D["Key derivation<br/>PBKDF2 + salt"]
    D --> E["AES-256-CTR<br/>encryption"]
    E --> F["Ciphertext file<br/>$ANSIBLE_VAULT;1.1;AES256"]
    F --> G["Commit to Git<br/>safe because it's ciphertext"]

    H["Playbook run"] --> I["Ansible reads the vault file"]
    I --> J["Vault password<br/>supplied to ansible-playbook"]
    J --> K["Decrypt on-the-fly<br/>in memory"]
    K --> L["Variables available<br/>at runtime"]
    L --> M["Tasks run<br/>with the real secret"]

    G -. commit .-> H

    style B stroke:#ff9800,stroke-width:2px
    style E stroke:#ff9800,stroke-width:2px
    style F stroke:#2196f3,stroke-width:2px
    style K stroke:#2196f3,stroke-width:2px

Three important things from this diagram:

1. Salt and key derivation. Ansible Vault doesn’t use the vault password directly as the AES key. It passes the vault password through a PBKDF2-based key derivation function with a random salt. This means two files encrypted with the same password produce different ciphertext, and rainbow table attacks become ineffective.

2. The $ANSIBLE_VAULT;1.1;AES256 header. Every Vault-encrypted file starts with this header. The header tells Ansible which Vault format version and algorithm were used. Currently Ansible supports format 1.1 (standard) and 1.2 (a new format with additional features). Version 1.1 is sufficient for most cases.

3. Decrypt on-the-fly. Vault files are never decrypted to disk while a playbook runs. Ansible decrypts in memory, uses the plaintext values only for the currently running task, then releases them when the playbook finishes. This minimizes the window where secrets exist in an extractable form.

Vault Format 1.2 (introduced in Ansible 2.12) brings several improvements: better key wrapping support, a more compact binary format, and the ability to encrypt with multiple vault IDs in one file. For new projects, use format 1.2 unless you need compatibility with older Ansible versions. Set the default format with ANSIBLE_VAULT_FORMAT=1.2 in the environment.

Two Modes: File Encryption vs String Encryption #

Vault supports two different encryption granularities. Each has its right place in an Ansible security strategy.

Mode 1: Full file encryption #

The entire file contents are encrypted into ciphertext. This mode suits files whose contents are entirely sensitive — for example a vault.yml containing all production secrets.

# Encrypt an existing file (the plaintext file is overwritten with ciphertext)
ansible-vault encrypt group_vars/production/vault.yml

# Create a new encrypted file directly from the editor
ansible-vault create group_vars/production/vault.yml

# Edit an encrypted file — Ansible decrypts, opens the editor, encrypts again on save
ansible-vault edit group_vars/production/vault.yml

# View the contents without opening the editor
ansible-vault view group_vars/production/vault.yml

# Permanently decrypt to disk (be careful — this writes plaintext!)
ansible-vault decrypt group_vars/production/vault.yml

When to use this mode: when a file only contains sensitive data and there are no regular values that need reviewing in Git. The vault.yml file in group_vars/production/ is a classic example — its contents are 100% passwords and tokens.

Mode 2: Individual string encryption #

Only specific values are encrypted, while the YAML file structure stays in plaintext. This mode suits files that are mostly regular content but have one or two sensitive values.

# Encrypt a string and display it on stdout
ansible-vault encrypt_string 'SuperSecret123' --name 'db_password'

The resulting output:

db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653236336462626566653339316533393634313636353535623135623
  3365663636343365623062323438353563373832663430640a633138623430383
  ...

This encrypted string can be pasted directly into any YAML file, mixed with regular plaintext variables. When Ansible runs the playbook, it recognizes the !vault tag and decrypts that value automatically.

When to use this mode: when a file contains a mix of regular variables (that need reviewing in Git, like hostnames, ports, environment names) and sensitive values (that shouldn’t be visible). Example: the group_vars/all/vars.yml file containing common configuration for all servers.

Comparison Table of the Two Modes #

AspectFull File EncryptionIndividual String Encryption
GranularityEntire file is ciphertextOnly specific values are ciphertext
File reviewable in GitNo (all ciphertext)Yes (plaintext structure, only sensitive values hidden)
Main commandansible-vault encryptansible-vault encrypt_string
Use caseDedicated vault.ymlMixed vars files
Edit overheadMust decrypt → edit → encrypt each timeNormal editing, only !vault values are decoded
Good for large teamsLess (hard code review)More (structure stays visible)
Runtime performanceSameSame

Decision Tree for Choosing the Mode #

flowchart TD
    A["Does the file contain sensitive data?"] -- "No" --> B["No Vault needed"]
    A -- "Yes" --> C{"Is the file 100%<br/>sensitive data?"}
    C -- "Yes" --> D["Mode: Full File Encryption<br/>ansible-vault encrypt"]
    C -- "No" --> E{"Are there regular values<br/>that need reviewing?"}
    E -- "Yes" --> F["Mode: String Encryption<br/>ansible-vault encrypt_string"]
    E -- "No" --> G["Split into 2 files:<br/>vars.yml + vault.yml"]
    G --> D

    style D stroke:#2196f3,stroke-width:2px
    style F stroke:#2196f3,stroke-width:2px
    style G stroke:#2196f3,stroke-width:2px

Complete Vault Operations Table #

Every ansible-vault subcommand has a specific purpose. This table summarizes them all with usage examples and important notes.

CommandPurposeExampleImportant Note
encryptEncrypt an existing fileansible-vault encrypt vars.ymlThe original file is overwritten with ciphertext
createCreate a new file and encrypt it directlyansible-vault create vault.ymlOpens the editor, then encrypts on save
decryptPermanently decrypt to diskansible-vault decrypt vault.ymlBe careful — the file becomes plaintext
viewView contents without changing the fileansible-vault view vault.ymlRead-only, exits without saving
editDecrypt → open editor → encrypt againansible-vault edit vault.ymlEditor from the EDITOR env var
rekeyChange the vault password without decryptingansible-vault rekey vault.ymlNeeds the old and new passwords
encrypt_stringEncrypt a single stringansible-vault encrypt_string 'secret'Output to stdout, manual copy-paste
encrypt_string --stdin-nameEncrypt a string from stdinecho "secret" | ansible-vault encrypt_string --stdin-nameUseful for scripts

Important points from this table:

  • decrypt is rarely used in normal workflows — it removes the protection. If you need to “see” a file’s contents, use view. If you need to “edit”, use edit (the file gets encrypted again on save).
  • rekey is the official way to change the vault password without the risk of plaintext being exposed on disk. The rekey operation only accepts ciphertext input/output; there’s no intermediate stage where the file exists in plaintext.
  • encrypt_string doesn’t write files — you must copy the output and paste it manually into the target file. This is a security feature (no plaintext written to disk), but also a common source of errors (forgetting to copy the output).

Vault Password Management: Sources and Strategies #

The vault password is the most critical encryption key — anyone who has this password can decrypt every file encrypted with it. There are several ways to provide it to Ansible, and the right choice depends on the execution context: run manually by an engineer, run by CI/CD, or run by an orchestrator like AWX.

Vault Password Sources #

# Method 1: Interactive prompt — Ansible asks when the playbook starts
ansible-playbook site.yml --ask-vault-pass

# Method 2: Password file — Ansible reads from a file
ansible-playbook site.yml --vault-password-file ~/.vault_pass

# Method 3: Password generator script — stdout output becomes the password
ansible-playbook site.yml --vault-password-file ~/bin/get-vault-pass.sh

# Method 4: Environment variable
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
ansible-playbook site.yml
# ansible.cfg — set the default so you don't need the flag every time
[defaults]
vault_password_file = ~/.vault_pass

The vault password file must not be committed to Git. Add it to .gitignore immediately after creating it. This file contains the encryption key for all secrets — leaking one file means leaking every vault using that password.

# .gitignore
.vault_pass
*.vault_pass
vault_pass.txt
ansible_vault_password

ANTI-PATTERN: vault password in the repository #

# ANTI-PATTERN: password inside a playbook or vars file
# File: group_vars/all/vault.yml
vault_db_password: "MyVaultPassword123!"   # ← DON'T! This isn't how Vault works
# CORRECT: password in a separate file, outside the repository
# File: ~/.vault_pass (chmod 600)
MyVaultPassword123!

# ansible.cfg references this file
[defaults]
vault_password_file = ~/.vault_pass
# CORRECT (alternative): password from a secret manager at runtime
# File: ~/bin/get-vault-pass.sh
#!/bin/bash
aws secretsmanager get-secret-value \
    --secret-id ansible/vault-password \
    --query SecretString --output text

Vault Password Resolution Sequence Diagram #

sequenceDiagram
    participant Dev as "Developer / CI"
    participant ANS as "ansible-playbook"
    participant CFG as "ansible.cfg"
    participant SRC as "Password Source"
    participant VLT as "Vault File"
    participant TSK as "Task"

    Dev->>ANS: "ansible-playbook site.yml"
    ANS->>CFG: "read default config"
    CFG-->>ANS: "vault_password_file = ~/bin/get-vault-pass.sh"

    alt "--ask-vault-pass passed"
        ANS->>Dev: "prompt Vault password:"
        Dev-->>ANS: "type password (hidden)"
    else "--vault-password-file passed"
        ANS->>SRC: "read file / run script"
        SRC-->>ANS: "password from file / script stdout"
    else "ANSIBLE_VAULT_PASSWORD_FILE env"
        ANS->>SRC: "read path from env var"
        SRC-->>ANS: "password"
    end

    ANS->>VLT: "decrypt group_vars/production/vault.yml"
    VLT-->>ANS: "plaintext in memory"
    ANS->>TSK: "run task with the secret"
    TSK-->>ANS: "execution result"
    ANS-->>Dev: "playbook output (with no_log where appropriate)"

Important from the sequence diagram: the password only exists in memory while the playbook runs, and Ansible doesn’t write it to logs. But if a task echoes the password without no_log: true, the password can appear in output — always combine Vault with no_log on sensitive tasks.


Vault IDs: Per-Environment Isolation #

For projects with several environments (development, staging, production), using the same vault password for all environments is an unnecessary risk. If the staging vault password leaks, an attacker can decrypt the production vault files. Vault IDs solve this problem.

A Vault ID is a label identifying a specific vault. Files encrypted with a specific vault ID can only be decrypted with the vault password matching that ID.

# Encrypt a string with the 'production' vault ID
ansible-vault encrypt_string 'ProdSecret' \
    --name 'db_password' \
    --vault-id production@~/.vault_pass_prod

# Encrypt a string with the 'staging' vault ID
ansible-vault encrypt_string 'StagingSecret' \
    --name 'db_password' \
    --vault-id staging@~/.vault_pass_staging

Output for production:

db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  production    # ← the vault ID is visible in the header
  66386439653236336462626566653339...

When running a playbook with multiple vault IDs:

ansible-playbook site.yml \
    --vault-id production@~/.vault_pass_prod \
    --vault-id staging@~/.vault_pass_staging

Vault ID Usage Patterns Table #

PatternSuitable forAdvantagesDisadvantages
Single global vault IDSmall project, one environmentSimple, one passwordLeak = all secrets leak
Per environment (prod, staging, dev)Multi-environment projectPer-environment isolationNeed to manage several passwords
Per team (backend, frontend, data)Large organizations with dedicated teamsPer-team isolationCan explode into many passwords
Per data type (db, api, ssl)High compliance requirementsGranular, independent rotationNeeds a good vault catalog
Hybrid: per environment + per typeEnterpriseMaximum isolationHigh complexity

Recommendation: start with per environment (prod/staging/dev). Add granularity only if there’s a clear compliance or security need.

Don’t use the ‘default’ vault ID. The default vault ID is an alias for an unlabeled vault. Using default makes it easy to accidentally decrypt files with the wrong password. Always use explicit labels like production@path or staging@path.

The best pattern for managing Vault is separating regular variables from sensitive variables into different files. This pattern makes code review easier (reviewers can read regular variables in Git without needing the vault password) and reduces the risk of accidentally committing secrets.

Directory Structure #

inventory/
  production/
    hosts.ini
    group_vars/
      all/
        vars.yml          # Regular variables — plaintext, safe in Git
        vault.yml         # Sensitive variables — ENCRYPTED
      dbservers/
        vars.yml          # DB-specific configuration — plaintext
        vault.yml         # DB passwords — ENCRYPTED
      webservers/
        vars.yml
        vault.yml
  staging/
    hosts.ini
    group_vars/
      all/
        vars.yml
        vault.yml
      ...

Example Contents #

# inventory/production/group_vars/dbservers/vars.yml — plaintext
db_host: prod-db.internal
db_port: 5432
db_name: app_production
db_user: app_user
db_pool_size: 20
db_password: "{{ vault_db_password }}"   # Reference the variable from the vault
# inventory/production/group_vars/dbservers/vault.yml — ENCRYPTED
# ansible-vault encrypt_string 'SuperSecret123' --name 'vault_db_password'
vault_db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653236336462626566653339...

vault_db_admin_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653236336462626566653340...

With this pattern:

  • vars.yml can be reviewed in pull requests without the vault password — reviewers can read the configuration and judge whether it’s appropriate.
  • vault.yml only contains secrets and is entirely encrypted.
  • Playbooks use db_password (from vars.yml) whose value is resolved from vault_db_password (from vault.yml).
  • When deploying to a different environment (staging), just swap the entire group_vars/ directory — the reference variables stay the same, only the values differ.

ANTI-PATTERN: one vault for all environments #

# ANTI-PATTERN: one vault.yml for all environments
inventory/
  group_vars/
    all/
      vault.yml    # ← contains BOTH production and staging secrets
# Contents of vault.yml — all secrets in one file
vault_prod_db_password: SuperSecret123
vault_staging_db_password: LessSecret456
# Problem: leaking one file = leaking ALL environments
# Problem: rotating the production password requires editing the same file as staging
# CORRECT: vault per environment
inventory/
  production/
    group_vars/all/vault.yml    # only production secrets
  staging/
    group_vars/all/vault.yml    # only staging secrets
  development/
    group_vars/all/vault.yml    # only development secrets
# CORRECT (more granular): vault per environment per group
inventory/
  production/
    group_vars/
      dbservers/vault.yml       # production DB-specific secrets
      webservers/vault.yml      # production web-specific secrets

Vault in CI/CD Pipelines #

Using Vault in CI/CD requires special attention because CI runners aren’t engineers who can type a password at a prompt. There are several patterns for providing the vault password in pipelines.

Pattern 1: Secret on the CI platform #

# .github/workflows/deploy.yml
name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production    # Requires manual approval
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Ansible
        run: pip install ansible

      - name: Create the vault password file
        run: |
          echo "${{ secrets.VAULT_PASSWORD }}" > /tmp/.vault_pass
          chmod 600 /tmp/.vault_pass          

      - name: Run the playbook
        run: |
          ansible-playbook -i inventory/production/ site.yml \
            --vault-password-file /tmp/.vault_pass          

      - name: Clean up the vault password
        if: always()
        run: rm -f /tmp/.vault_pass

Critical points:

  • chmod 600 — the password file must only be readable by the owner.
  • if: always() — cleanup must run even if the playbook fails, so the password doesn’t linger on the runner.
  • environment: production — GitHub Actions provides an approval gate before a job runs against a protected environment.

Pattern 2: Vault password from a cloud secret manager #

# .github/workflows/deploy.yml
- name: Fetch the vault password from AWS Secrets Manager
  run: |
    aws secretsmanager get-secret-value \
      --secret-id ansible/vault-password \
      --query SecretString \
      --output text > /tmp/.vault_pass
    chmod 600 /tmp/.vault_pass    
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

This pattern is safer because the vault password itself is rotated by AWS, and CI doesn’t need to know the plaintext value — only access to Secrets Manager.

Pattern 3: AWX / Ansible Tower as the secret store #

For teams already using AWX or Ansible Tower, the Vault Credential in AWX is the best approach. AWX stores vault passwords in its internal database encrypted with Fernet, and these credentials can only be accessed by Job Templates with the right permissions.

# ansible.cfg on the CI runner — no vault_password_file needed
[defaults]
# AWX handles the vault password, not the runner

The CI runner triggers an AWX Job Template via API or webhook; AWX handles the vault decryption using securely stored credentials.

AWX Vault Credential vs Vault Password File — AWX Vault Credential is the safer approach for organizations. The vault password is stored encrypted in the AWX database, audit logs record who used the credential, and rotation only needs to happen once in AWX. CI runners never see the vault password. For small teams not yet using AWX, secrets on the CI platform are sufficient.

ANTI-PATTERN: vault password in pipeline logs #

# ANTI-PATTERN: echoing the password in a step that fails during debugging
- name: Debug vault issue
  run: |
    cat /tmp/.vault_pass    # ← DON'T! The password will appear in GitHub Actions logs    
# CORRECT: use masking and delete the file before debugging
- name: Debug vault issue
  run: |
    if [ ! -s /tmp/.vault_pass ]; then
      echo "Vault file is empty or missing"
    fi
    # DON'T cat /tmp/.vault_pass in logs
    # Use 'echo "::add-mask::$(cat /tmp/.vault_pass)"' to mask it in logs    

GitHub Actions automatically detects strings that look like secrets and masks them in logs. But don’t rely on auto-masking — always avoid echoing secret values in commands visible in logs.


Rekey: Vault Password Rotation #

Vault passwords should be rotated periodically — just like any other account password. If there’s any chance a password leaked (lost laptop, contributor leaving the team, exposed repository mirror), change the password immediately. ansible-vault rekey enables rotation without manually decrypting and re-encrypting files.

# Rekey a single file
ansible-vault rekey group_vars/production/vault.yml
# Prompt: Vault password (old)
# Prompt: New Vault password (new)
# Prompt: Confirm New Vault password

# Rekey all vault files in a directory
find group_vars/ -name 'vault.yml' -exec ansible-vault rekey {} \;

# Rekey with a specific vault ID
ansible-vault rekey group_vars/production/vault.yml --vault-id production@prompt

Rekey accepts the old and new passwords in one operation, and only produces new ciphertext — there’s never a stage where the plaintext file is written to disk. After rekeying, commit the rekeyed files to Git.

When to Rekey #

SituationActionPriority
A developer who knew the vault password leaves the teamRekey all vaults they knewUrgent
A laptop/device containing the vault password is lostRekey all vaults on that deviceUrgent
A security audit finds the password in logs/backupsRekey the affected vaultsUrgent
Periodic rotation (e.g. every 90 days)Rekey all vaultsRoutine
No incident, just best practiceNo rekey needed
Rekey only changes the password, not the ciphertext salt. The salt is generated once per file and stays the same after rekeying. For maximum security, decrypt all files, remove them from Git history, then re-encrypt with a new password. But for most cases, rekey is sufficient.

Vault Integration with Roles and Playbooks #

Vault doesn’t stand alone — it works with roles and playbooks to provide secrets to the tasks that need them. A good understanding of this integration prevents common mistakes like unresolved variables or secrets leaking into logs.

Pattern 1: Vault Variables Referenced in vars.yml #

# roles/webserver/defaults/main.yml
db_host: localhost
db_port: 5432

# roles/webserver/vars/main.yml
# (vars/main.yml has higher priority than defaults)

# inventory/production/group_vars/webservers/vars.yml
db_host: prod-db.internal
db_password: "{{ vault_db_password }}"   # Reference the variable from vault.yml

# inventory/production/group_vars/webservers/vault.yml (ENCRYPTED)
vault_db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  ...
# playbook site.yml
- hosts: webservers
  roles:
    - role: webserver

When the playbook runs, Ansible loads variables in order: defaultsinventory varsplay varsrole varshost_varsgroup_vars. The vault_db_password variable from vault.yml is resolved first, then db_password in vars.yml takes its value. The webserver role receives the resolved db_password when its tasks run.

The role article discusses variable precedence in more detail. For context on how roles are composed into larger playbooks, see automation.

Pattern 2: no_log on Sensitive Tasks #

Variables resolved from the vault can appear in Ansible output if the task using those variables is logged. Add no_log: true to prevent this:

# CORRECT: hide the output of tasks using secrets
- name: Set the database password for the application user
  postgresql_user:
    name: app_user
    password: "{{ db_password }}"
    state: present
  no_log: true    # Ansible won't display this task's command or output

no_log: true hides the entire task output — including error messages. This is a trade-off between security and debugging ease. For development, you can disable no_log; for production, always enable it.

When NOT to use no_log: when you need to debug a failed task and that task’s output isn’t sensitive. But remember, if a task uses variables from the vault, its output is most likely sensitive. The safe default: no_log: true on every task using vault variables.

Pattern 3: Tasks That Write Secrets to Files on Managed Nodes #

Sometimes secrets need to be written to files on the destination server (for example database configuration used by applications). This pattern needs special attention:

- name: Deploy the database configuration
  template:
    src: db_config.j2
    dest: /etc/myapp/db.conf
    owner: myapp
    group: myapp
    mode: '0600'    # Only the owner can read
  no_log: true

# db_config.j2
host={{ db_host }}
port={{ db_port }}
user={{ db_user }}
password={{ db_password }}

Note the mode: '0600' — configuration files containing passwords must only be readable by the user running the application. 0600 means only the owner has read and write access.


When Not to Use Vault / Alternatives #

Vault is an excellent solution for most cases, but it isn’t the only one. There are situations where other approaches are more appropriate.

Keep Using Vault #

STILL use Ansible Vault if:
  ✓ Secrets are stored in the Git repository (config files, inventory, role defaults)
  ✓ Small-to-medium teams without a dedicated secret manager
  ✓ Open-source projects or public repositories (Vault enables sharing
    encrypted secrets without exposing values)
  ✓ Compliance doesn't require granular per-secret-access audit trails
  ✓ Secret rotation is done manually per release (no real-time rotation needed)

Consider an External Secret Manager #

CONSIDER HashiCorp Vault / AWS Secrets Manager if:
  ✗ Secrets change in real time (automatic rotation every hour/minute)
  ✗ Compliance requires detailed per-access audit trails
  ✗ Large teams with many applications needing different secrets
  ✗ Secrets are shared between many systems (Ansible + Kubernetes + applications)
  ✗ There's a need for dynamic secrets (e.g. database credentials changing per connection)

Comparison Table: Vault vs Alternatives #

AspectAnsible VaultHashiCorp VaultAWS Secrets Manager
Setup complexityLow (built-in)High (needs a server)Medium (needs an AWS account)
Audit trailGit historyDetailed per-access logsCloudTrail + access logs
Automatic rotationNo (manual rekey)Yes (dynamic secrets)Yes (Lambda rotation)
CostFree (part of Ansible)Free (open source) + infraPer secret per month
Suitable forStatic secrets in reposDynamic, high-security secretsProjects in the AWS ecosystem
Offline supportYes (local encrypted files)No (needs a running server)No (needs an AWS connection)

You can also combine Vault with a secret manager: use Vault for configuration files stored in the repo, and the secret manager for very sensitive secrets (like database root passwords). Ansible pulls secrets from both sources at runtime, and the secret manager becomes the authoritative source for the most critical secrets.

The encryption article discusses encryption layers and external secret manager integration in more detail. For workflow and approval gate patterns combining Vault with access control, see workflow.


Summary #

  • Ansible Vault encrypts sensitive data using AES-256 with PBKDF2 key derivation — encrypted files are safe to store in Git without the risk of leaking to unauthorized people.
  • Two modes are available: full file encryption (ansible-vault encrypt) for entirely sensitive files, and string encryption (ansible-vault encrypt_string) for individual values in YAML files that are mostly regular content.
  • Separate regular variables from the vaultvars.yml (plaintext, safe for review) references vault.yml (encrypted) — this pattern makes code review easier and reduces the risk of accidental commits.
  • The vault password must never be committed to Git — store it in a local file with chmod 600 in .gitignore, or as a secret on the CI/CD platform, or in an AWX Vault Credential for larger organizations.
  • Use different vault IDs per environment (production@path, staging@path) — a leaked staging vault password doesn’t compromise the production vault, and per-environment rotation can be done independently.
  • Rotate passwords with ansible-vault rekey — there’s never a stage where the plaintext file is written to disk, so rekeying is safe to do routinely without risk.
  • Integrate with no_log: true on tasks using vault variables — without no_log, secrets can appear in Ansible output when tasks fail and run in verbose mode.
  • Vault isn’t the only solution — for very sensitive secrets or real-time rotation needs, consider an external secret manager (HashiCorp Vault, AWS Secrets Manager) and fetch secrets at runtime using lookup plugins.

← Previous: Drift Handling Next: Encryption →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact