# Moving team secrets out of Ansible Vault

> Why teams move secrets out of Ansible Vault, how to export them with ansible-vault, bring them into per-environment storage, and keep playbooks working.

URL: https://crunu.com/blog/migrate-from-ansible-vault
Language: en
Updated: 2026-10-30

To move secrets out of Ansible Vault, decrypt each vault file with `ansible-vault view` or `ansible-vault decrypt`, bring the values into a store that gives each person and machine its own access, and change how hosts receive them: either the playbook reads them from the environment at run time, or the application on each host fetches them itself. Do it one inventory group or environment at a time, and reissue the values that were shared under a password everyone knew. Crunu can't import arbitrary Ansible Vault files, so the move goes through decrypted files you drag in.

This post explains the usual reasons for leaving, a step-by-step move, and how to keep a playbook working during and after the switch. It doesn't argue that Ansible Vault is wrong for everyone; for a small team that already runs everything through Ansible, it is often enough.

## Why teams move off it

Ansible Vault does what it says: it "encrypts variables and files so you can protect sensitive content such as passwords or keys rather than leaving it visible as plaintext in playbooks or roles" ([Ansible Vault](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault.html)). The friction appears as a team grows.

**Access is a password, not a person.** Anyone who has the vault password can decrypt everything encrypted with it. The Ansible documentation suggests a single password for "a small team or few sensitive values" and multiple passwords for larger teams, for example "different passwords for different users or different levels of access", managed with vault IDs ([Ansible: managing vault passwords](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_managing_passwords.html)). That works, but every password is still shared by everyone at its level, and nothing records who decrypted what.

**Leaving is expensive.** When someone with the production vault password leaves, every file under that password needs a rekey, every inline encrypted variable has to be re-created because "you cannot rekey encrypted variables" ([Ansible: encrypting content](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_encrypting_content.html)), and the old password still opens every earlier commit. The post on [removing someone from git-crypt, SOPS or Ansible Vault](https://crunu.com/blog/remove-someone-from-git-crypt-sops-ansible-vault) goes through that in detail.

**Reviews and merges are awkward.** With file-level encryption, the documentation notes, "the contents of files are no longer easy to access and read." To Git, an encrypted file is an opaque block: a diff shows changed ciphertext, and two branches that touch the same vault file conflict in text nobody can resolve by reading it. One workaround is a diff driver that runs `ansible-vault view`, with vault files marked `merge=binary` so Git doesn't try to merge ciphertext ([Ansible: human readable git diff for vault files](https://selivan.github.io/2016/11/29/ansible-human-readable-git-diff-for-vault.html)). It helps, but each developer has to set it up and hold the password.

**It only covers data at rest.** The documentation warns: "Encryption with Ansible Vault ONLY protects 'data at rest'. Once the content is decrypted ('data in use'), play and plugin authors are responsible for avoiding any secret disclosure." Values decrypted on the control node and templated onto hosts end up in files on those hosts.

*Figure: Diagram of who can decrypt Ansible Vault values, split into your side and the provider side. On your side, anyone with the file key can decrypt. The Git host and backups keep only encrypted files.*

## Before you start: an inventory

List what you have before decrypting anything.

- **Vault files.** Usually `group_vars/<group>/vault.yml` and `host_vars/<host>/vault.yml`, plus any encrypted task or template files.
- **Inline encrypted variables.** Search for `!vault |` in your YAML.
- **Vault IDs and password sources.** Which label belongs to which environment, and where each password lives: a file, a script, a secret manager.
- **Consumers.** Which roles and templates use which variables, and where the values land on the hosts: a systemd environment file, an application config, a `.env`.

The last list matters most. It tells you how values should reach each host after the move.

## Step by step

1. Pick one environment to move first, typically staging. Create a matching environment in the new store, such as `payments-api / staging`.
2. Print each vault file to the terminal with `ansible-vault view --vault-id staging@prompt group_vars/staging/vault.yml`, or write a temporary decrypted copy with `ansible-vault decrypt --output` to a path outside the repository.
3. Turn the values into the files your applications actually read: a `.env` for a service, an INI file such as `~/.aws/credentials`, or a flat YAML file. Drag them onto the environment and check the variable names and paths it shows.
4. Delete the temporary decrypted copies.
5. Change how the hosts get the values, using one of the two patterns below, and deploy to staging.
6. When staging runs on the new path, remove the old vault file from the playbook and repeat for the next environment.
7. After production moves, reissue the values that were protected only by a shared password, starting with the most sensitive.

Step 7 is the easiest one to skip. Everyone who ever had the production vault password could read every production value. Moving them to a new store doesn't change that; new values at the providers do.

## Keeping playbooks working: two patterns

**Pattern 1: the playbook reads values from its environment.** Run `ansible-playbook` under `crunu run` on the control node, and replace vault variables with the env lookup, which "allows you to query the environment variables available on the controller when you invoked Ansible" ([Ansible: env lookup](https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/env_lookup.html)):

```yaml
# group_vars/staging/main.yml
db_password: "{{ lookup('ansible.builtin.env', 'DB_PASSWORD') }}"
stripe_secret_key: "{{ lookup('ansible.builtin.env', 'STRIPE_SECRET_KEY') }}"
```

```sh
crunu run --env payments-api/staging -- ansible-playbook -i inventory/staging site.yml
```

This is the smallest change: templates and roles stay as they are, and no vault password is needed. During the switch, the lookup's `default` parameter, added in ansible-core 2.13, lets the old vault variable stand in when the environment variable is missing, for example `lookup('ansible.builtin.env', 'DB_PASSWORD', default=vault_db_password)`. Remove the fallback and the vault file once every run goes through `crunu run`.

If playbooks run from CI rather than a laptop, the same command works with a service token in `CRUNU_TOKEN`. Give that token only the environment the pipeline deploys and bind it to the repository and branch with OIDC, so a copied token is useless outside the pipeline. Values still pass through the control node and still land in whatever files the templates write on the hosts.

**Pattern 2: hosts fetch their own values.** Enroll each host as a machine with the Crunu agent, and have Ansible deploy only the service unit that starts the application through `crunu run`. The playbook never handles a secret.

```ini
# templates/payments-api.service.j2
[Service]
User=payments
ExecStart=/usr/local/bin/crunu run --env payments-api/{{ env_name }} --restart on-change -- /opt/payments/bin/server
```

The `payments` user must belong to the `crunu` group to reach the agent's socket. If an application insists on an environment file, `crunu pull --env-out` writes one for systemd's `EnvironmentFile=` with permissions 0600. The [systemd integration](https://crunu.com/integrations/systemd) has the full setup. This pattern takes longer to adopt, but it removes secrets from the control node and the playbook repository entirely.

## What changes for people

*Figure: Priya Natarajan dragged the .aws folder from her computer onto the payments-api production environment. Crunu recognized two files and proposes where each one goes: \~/.aws/config, marked as able to run commands, and \~/.aws/credentials. Only field names such as aws_access_key_id are listed; values are not shown. The cursor rests on Add (2).*

In Crunu, each environment has its own key, sealed to each person and machine with a role on it. A developer with Editor on staging doesn't hold the production key at all. When someone leaves, removing them rotates the keys of the environments they had and shows which versions they received, instead of a rekey of every file under a shared password. Each saved change is a new version with its author, and **History** lists what changed by variable name.

## Going back the other way

Some playbooks or teammates will still expect Ansible Vault for a while. Crunu can export an environment to a `keys.vault.yml` file in the Ansible Vault 1.1 format with AES256, which `ansible-vault view` opens with a separate password generated in the **Export…** dialog. Inside is a YAML document that lists each file by its path with its contents, so a playbook reads it as data rather than as ready-made variables. The export is a snapshot; a new version needs a new export. The [Ansible Vault integration](https://crunu.com/integrations/ansible-vault) describes it.

## Honest limits

- **No importer for Ansible Vault files.** Crunu reads only the `keys.vault.yml` files made by its own earlier versions. Everything else comes in as decrypted files.
- **Nested YAML stays text.** Flat YAML files show as fields; nested structures are stored and edited as the whole file.
- **Not an Ansible plugin.** There is no lookup plugin or inventory source; the integration is the process environment and files on the host.

If you're still weighing options rather than moving, see the [Crunu vs Ansible Vault](https://crunu.com/alternatives/ansible-vault) page and the [SOPS vs Ansible Vault](https://crunu.com/compare/sops-vs-ansible-vault) comparison. The wider offboarding picture is in the guide on [what to rotate when a developer leaves](https://crunu.com/guides/developer-offboarding-secret-rotation).

## Sources

- [Ansible Vault, Ansible documentation](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault.html), checked October 30, 2026.
- [Managing vault passwords, Ansible documentation](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_managing_passwords.html), checked October 30, 2026.
- [Encrypting content with Ansible Vault, Ansible documentation](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_encrypting_content.html), checked October 30, 2026.
- [ansible.builtin.env lookup, Ansible documentation](https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/env_lookup.html), checked October 30, 2026.
- [Ansible: human readable git diff for vault encrypted files, selivan.github.io](https://selivan.github.io/2016/11/29/ansible-human-readable-git-diff-for-vault.html), checked October 30, 2026.
