Ansible Vault Secrets Management: Encrypting Playbook Variables, Password File Configuration, and CI/CD Integration

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Inherited an Ansible repository from a team that had been running it for two years. The first thing the git history audit surfaced was an AWS access key that had been committed in a group_vars file 14 months earlier, then removed in a subsequent commit when someone noticed it was plaintext. The key had been active for 14 months. The repository had 23 contributors with git clone access. The key had IAM permissions to describe all EC2 instances and read from several S3 buckets in the production account. There was no way to know if the key had been used by anyone other than the intended automation.
The entire production AWS account's security posture had to be treated as potentially compromised based on that one historical commit. Every external connection to production systems was audited for the period the key was active. Four weeks of security review work traced to one commit that could have been prevented by encrypting the key with ansible-vault encrypt_string before it ever touched the filesystem in plaintext form.
Encryption workflow: integrating Vault into the Ansible development process
Ansible Vault adoption fails when the encryption workflow is too cumbersome for daily development use — engineers work around it by using plaintext variables in files that are excluded from version control, which creates the same unencrypted secret problem with the additional issue that the secrets are also untracked. The encrypt_string workflow that allows mixed encrypted and plaintext variables in the same file is significantly more developer-friendly than requiring entire variable files to be vault-encrypted, and the pre-commit hook that detects high-entropy strings before commit prevents accidental plaintext commits without requiring perfect developer discipline.
Install detect-secrets pre-commit hook to catch plaintext secrets before they reach git history
Install the detect-secrets pre-commit hook as the first line of defense against plaintext secrets in the Ansible repository, catching credentials before the git commit completes. Install detect-secrets: pip install detect-secrets. Generate the baseline file that marks existing known-false-positives: detect-secrets scan > .secrets.baseline. Create a .pre-commit-config.yaml file in the repository root with the detect-secrets hook: repos with id: detect-secrets, args: ['--baseline', '.secrets.baseline']. Install the hooks: pre-commit install. When a developer attempts to commit a file containing a high-entropy string or known credential pattern (AWS access key format, private key header, JWT token format), detect-secrets blocks the commit and shows which file and line triggered the detection. The developer must either encrypt the value with ansible-vault encrypt_string and replace the plaintext with the ciphertext, or add the detected pattern to the .secrets.baseline file with an explicit annotation marking it as a false positive (acceptable for vault-encrypted strings that detect-secrets cannot distinguish from plaintext high-entropy strings).
Establish a vault password management policy that separates vault passwords from the playbook repository
Establish a policy that vault passwords are never stored in the Ansible repository, never transmitted via email or Slack, and are available to authorized personnel exclusively through the organization's secrets manager or password vault. Store vault passwords in the organization's HashiCorp Vault, AWS Secrets Manager, or 1Password Teams with access restricted to the team or individuals who need to run Ansible playbooks. Document the vault password retrieval process in the team's runbook: engineers retrieve the vault password from the secrets manager into a local file at ~/.vault_pass, configure ansible.cfg to reference that file path, and delete the local file when finished. CI/CD pipelines receive the vault password through the pipeline's secret injection mechanism, writing it to a temporary file at the start of the run and deleting it at the end. Rotate vault passwords on a schedule coordinated with the secrets manager rotation policy — most organizations target annual rotation with immediate rotation after team member offboarding. The vault password should never appear in ansible-playbook command lines, shell history, log files, or CI/CD output.
Advanced patterns: vault IDs and external secret backend integration
The basic Ansible Vault single-password model works for small teams and single-environment deployments but creates access control challenges when the same repository is used to manage multiple environments with different security requirements. Vault IDs and external secret backend integration address these challenges at different layers — vault IDs provide cryptographic separation of secrets by environment, while external backends eliminate the static secret problem entirely for organizations with dynamic secret infrastructure.
Use vault IDs to implement environment-segregated secret access where production secrets require a separate approval process
Implement vault ID separation so that the production vault password requires a separate approval step — retrieving it from a secrets manager account with more restrictive access controls than the development vault password. The development vault ID password is accessible to all engineers in the team's password vault. The production vault ID password is accessible only to senior engineers and site reliability engineers via a separate secrets manager path with required approval workflow. Engineers working on development playbooks retrieve only the dev vault password and can run playbooks against development environments using only --vault-id dev@~/.vault_pass_dev. Deploying to production requires retrieving the production vault password through the approval process: --vault-id prod@~/.vault_pass_prod. The vault ID labels in the encrypted ciphertext headers make it visible in code review which environment a secret belongs to: $ANSIBLE_VAULT;1.2;AES256;dev vs $ANSIBLE_VAULT;1.2;AES256;prod in the commit diff shows clearly which environment's secrets are being changed.
Migrate frequently-rotated secrets from Ansible Vault files to HashiCorp Vault or AWS Secrets Manager lookups
Migrate secrets that require frequent rotation (database passwords, API keys, TLS certificates) from Ansible Vault encrypted files to dynamic lookup from HashiCorp Vault or AWS Secrets Manager, eliminating the operational overhead of re-encrypting and re-committing secrets each time they rotate. Static secrets in Ansible Vault files require three steps on each rotation: decrypt the vault file locally, update the plaintext value, and re-encrypt with ansible-vault encrypt_string — a process that must be coordinated across all team members who have the vault password and must produce a new git commit. Dynamic secrets from HashiCorp Vault's database secrets engine are generated fresh for each playbook run, rotate automatically after a configured TTL, and require no git commits or vault re-encryption when the underlying credentials change. For AWS resources, the community.aws.aws_secret lookup plugin retrieves values from AWS Secrets Manager: db_password: "{{ lookup('amazon.aws.aws_secret', 'production/db/password', region='us-east-1') }}" retrieves the current secret value at playbook runtime without any Ansible Vault configuration required for that specific value.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
The bottom line
Ansible Vault is the minimum viable secrets protection for Ansible playbooks stored in version control, preventing the plaintext credential commits that require full credential rotation when discovered in git history. Encrypt sensitive variable files with ansible-vault encrypt or encrypt individual values inline with ansible-vault encrypt_string to allow mixed plaintext and encrypted content in variable files. Configure vault password file access via ansible.cfg vault_password_file or ANSIBLE_VAULT_PASSWORD_FILE environment variable for CI/CD pipelines, storing the vault password itself in the organization's secrets manager rather than in the repository. Install detect-secrets pre-commit hooks as the backstop against accidental plaintext commits. Use vault IDs to separate production and development secrets with independent vault passwords and access controls. Migrate frequently-rotated secrets to HashiCorp Vault or AWS Secrets Manager dynamic lookups to eliminate re-encryption overhead on secret rotation.
Frequently asked questions
How do I encrypt a variable file with Ansible Vault?
Encrypt a variable file with Ansible Vault using ansible-vault encrypt which converts an existing plaintext YAML variable file to an AES256-encrypted format that Ansible automatically decrypts at playbook runtime. Create a plaintext variable file first (group_vars/production/secrets.yml) containing the sensitive variables: db_password: mysecretpassword and api_key: sk-abc123. Run ansible-vault encrypt group_vars/production/secrets.yml which prompts for a new vault password twice, then overwrites the plaintext file with the encrypted version beginning with $ANSIBLE_VAULT;1.1;AES256. Commit the encrypted file to version control — it is safe to commit because the ciphertext is useless without the vault password. When running playbooks, Ansible automatically detects and decrypts vault-encrypted files: ansible-playbook site.yml --ask-vault-pass prompts for the vault password interactively, or ansible-playbook site.yml --vault-password-file ~/.vault_pass reads the password from a file. Verify the encryption worked by running ansible-vault view group_vars/production/secrets.yml which decrypts and displays the file contents without permanently decrypting it, then running grep db_password group_vars/production/secrets.yml to confirm the plaintext value is no longer visible in the file.
How do I use ansible-vault encrypt_string for encrypting individual variable values?
Use ansible-vault encrypt_string to encrypt individual variable values that should appear in-line in a partially-encrypted variable file, leaving non-sensitive values in plaintext for readability while protecting sensitive values. Run ansible-vault encrypt_string --vault-id default@~/.vault_pass 'mysecretpassword' --name 'db_password' which outputs a YAML-formatted encrypted string block: db_password: !vault | $ANSIBLE_VAULT;1.1;AES256 followed by the ciphertext. Copy this output directly into your variable file, allowing the file to contain a mix of plaintext and encrypted values: db_host: prod-db.internal is plaintext while db_password: !vault | $ANSIBLE_VAULT... is encrypted. The !vault tag tells Ansible to decrypt this specific value using the vault password when the variable is accessed during playbook execution. The encrypt_string approach is useful for files like group_vars/all/vars.yml that contain many variables where only a few are sensitive — encrypting the entire file with ansible-vault encrypt forces the entire file to be ciphertext, making diff reviews of non-sensitive changes impossible. With encrypt_string, git diff shows plaintext changes to non-sensitive variables and shows only ciphertext changes for encrypted values.
How do I configure Ansible Vault for CI/CD pipeline execution without interactive password prompts?
Configure Ansible Vault for CI/CD by storing the vault password as a CI/CD pipeline secret, writing it to a temporary file at the start of the pipeline run, and configuring Ansible to read the vault password from that file using either the ANSIBLE_VAULT_PASSWORD_FILE environment variable or the vault_password_file ansible.cfg setting. In GitHub Actions: create a repository secret named ANSIBLE_VAULT_PASSWORD containing the vault password. In the workflow, write the vault password to a temporary file at the start of the Ansible job: echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass && chmod 600 /tmp/vault_pass. Set the environment variable for the subsequent Ansible step: env: { ANSIBLE_VAULT_PASSWORD_FILE: /tmp/vault_pass }. Add a cleanup step that always runs to remove the temporary password file: if: always() → rm -f /tmp/vault_pass. This approach avoids passing the vault password on the command line (which appears in process listings and CI/CD logs) and avoids hardcoding it in ansible.cfg (which would commit the password to version control). Verify the CI/CD pipeline decryption works by adding a simple task that accesses a vault-encrypted variable and prints its length (not its value) to confirm decryption succeeded without exposing the secret in logs.
How do I use multiple Ansible Vault IDs to separate secrets by environment?
Use Ansible Vault IDs to encrypt secrets with different passwords for different environments, so that the production vault password is separate from the development vault password and an engineer who knows only the development password cannot decrypt production secrets. Create environment-specific vault password files: ~/.vault_pass_dev and ~/.vault_pass_prod containing different passwords. Encrypt development secrets with the dev vault ID: ansible-vault encrypt_string --vault-id dev@~/.vault_pass_dev 'dev-db-password' --name 'db_password' which produces ciphertext labeled with the dev vault ID in the $ANSIBLE_VAULT;1.2;AES256;dev header. Encrypt production secrets with the prod vault ID: ansible-vault encrypt_string --vault-id prod@~/.vault_pass_prod 'prod-db-password' --name 'db_password'. Run playbooks providing both vault ID passwords: ansible-playbook site.yml --vault-id dev@~/.vault_pass_dev --vault-id prod@~/.vault_pass_prod. Ansible automatically matches each encrypted value's vault ID label to the corresponding password, decrypting dev secrets with the dev password and prod secrets with the prod password. In CI/CD, provide only the production vault ID password secret to the production deployment pipeline — the production pipeline cannot decrypt development secrets and development engineers cannot decrypt production secrets even if they have access to the repository.
How do I rotate Ansible Vault passwords without re-encrypting every file manually?
Rotate Ansible Vault passwords using the ansible-vault rekey command which re-encrypts vault-encrypted files with a new password without requiring manual decryption and re-encryption of each file individually. Run ansible-vault rekey group_vars/production/secrets.yml which prompts for the current vault password (to decrypt) and the new vault password (to re-encrypt), then overwrites the file with content encrypted under the new password. For rekeying multiple files in one operation: ansible-vault rekey $(find . -name '*.yml' -exec grep -l 'ANSIBLE_VAULT' {} +) which finds all vault-encrypted files in the repository and rekeys them all in sequence with the same current and new passwords. For repositories using encrypt_string in mixed files (where only specific variables are vault-encrypted), rekey does not work on individual strings within a file — those variables must be individually decrypted with ansible-vault decrypt_string (using the old password) and re-encrypted with ansible-vault encrypt_string (using the new password). Schedule vault password rotation annually or after any personnel change that had access to the vault password, and coordinate rotation across all systems that run Ansible playbooks to update the vault password file on each system simultaneously.
How do I integrate Ansible Vault with HashiCorp Vault for dynamic secret retrieval?
Integrate Ansible with HashiCorp Vault using the community.hashi_vault.hashi_vault lookup plugin to retrieve secrets dynamically at playbook runtime rather than storing encrypted secrets in Ansible Vault files. Install the collection: ansible-galaxy collection install community.hashi_vault. In playbook tasks or variable files, use the lookup plugin: db_password: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/data/production/db:password auth_method=token token=YOUR_TOKEN url=https://vault.example.com') }}". Replace the static token with an AppRole authentication method for CI/CD: set the VAULT_ADDR and VAULT_TOKEN environment variables in the CI/CD pipeline from pipeline secrets, and the lookup plugin automatically uses environment variables for authentication when no explicit auth parameters are provided. The HashiCorp Vault integration approach provides advantages over Ansible Vault file encryption: secrets are never stored in Git even in encrypted form, HashiCorp Vault provides dynamic secrets with automatic rotation for database credentials (db secret engine generates a new username and password per playbook run rather than using a static password), and secret access is audited in HashiCorp Vault's audit log with the AppRole identity. Use Ansible Vault file encryption for environments where HashiCorp Vault is not available and HashiCorp Vault lookups for environments with HashiCorp Vault infrastructure.
How do I audit an existing Ansible repository for plaintext secrets in git history?
Audit an Ansible repository for plaintext secrets in git history using truffleHog or git-secrets to scan every commit for credentials that may have been committed before Ansible Vault was adopted. Install truffleHog: pip install trufflehog3. Run trufflehog3 filesystem /path/to/ansible-repo --include-paths '*.yml,*.yaml,*.cfg' which scans all YAML and configuration files in the repository's filesystem state. For full git history scanning: trufflehog3 git file:///path/to/ansible-repo --since-commit HEAD~1000 to scan the last 1000 commits for secrets. Each finding shows the commit hash, file, line number, and the detected secret pattern (high entropy string, AWS access key format, private key header). For each finding, treat the exposed credential as compromised regardless of how old the commit is — any consumer of the repository who cloned it before the secret was removed has a copy in their local git history. Rotate all discovered credentials immediately, document the rotation in the incident log, and add pre-commit hooks using git-secrets or detect-secrets to prevent future plaintext secret commits: pip install detect-secrets && detect-secrets scan > .secrets.baseline && pre-commit install with a detect-secrets hook in .pre-commit-config.yaml.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
