
SOPS for Ansible
October 24, 2026
I’ve used Ansible for more than 10 years, and one problem I’ve seen everyone deal with is handling secrets in Ansible. Many people put them in the code, but since Ansible code often ends up in Git, that means adding secrets to Git repositories, which you should avoid at all costs.
Ansible Vault is a good solution, since it lets you encrypt files. I’ve used Ansible Vault for many years, but I think it has 2 major limitations.
The first limitation of Ansible Vault is that it uses AES, a symmetric encryption algorithm. Symmetric encryption uses a single key to encrypt and decrypt content. This means you need to provide the key at runtime, so Ansible Vault reduces the secrets issue but doesn’t solve it, because you still have to decide how to handle the master key securely.
The second limitation with Ansible Vault is that it encrypts at the file level, so if you open the file, you will just see random characters, This means that you will often end up having 2 files for variables, one for non-secret ones and the other for secret ones. The double file is not the real problem, though. The real problem is that the secret file contains both the encrypted value and the keys, so without the master key, you can’t read the keys. This means you need to document the key names elsewhere so you can modify the code without opening the Vault file.
Lately, I’ve started using CNCF’s SOPS project instead of Ansible Vault, since it solves both issues. In fact, SOPS encrypts at the YAML-file level, so it scrambles only the values while the keys remain clear. Also, it supports asymmetric encryption with a big number of providers.
Since I’ve used GPG keys for more than a decade, I have many workflows that leverage them, so it was an easy decision to use them for SOPS as well. You can also start with Age if you are not bought into the GPG ecosystem.
SOPS is managed with a .sops.yaml file in the root of the repository.
Mine look like this:
creation_rules:
- path_regex: .*\.sops\.yaml$
encrypted_regex: '^(.*_password|.*_secret|.*_key|.*_token)$'
pgp: D880521E8D13FB5D0BCFC65817DBA3103879E9D9
This means that every file in the project that ends in .sops.yaml will have any value whose key ends in _password, _secret, _key, or _token encrypted with my GPG key.
It is possible to encrypt an unencrypted file with sops --encrypt group_vars/all/secrets.yaml > group_vars/all/secrets.sops.yaml.
SOPS will create the new file with the encrypted keys.
It is important to know that SOPS adds a structure called sops to the file where it saves its own configurations, and part of this configuration is a hash of the file, so any further modification to the file has to be done using the sops edit command; otherwise it will complain about data integrity being broken.
Ansible, by itself, can’t leverage SOPS secrets.
You’ll therefore need to tweak your ansible.cfg file to have:
[defaults]
vars_plugins_enabled = host_group_vars,community.sops.sops
If you already have a [default] group, you should simply add to it.
Further, you should add the community.sops collection to your requirements.yaml, so that it looks like:
collections:
- name: community.sops
It is now possible to get the needed collections with:
ansible-galaxy collection install -r requirements.yaml
From this point forward, SOPS variables will be usable exactly like any other variable.
It is important to remember that SOPS handles the security of the variables at rest, but Ansible will handle them at runtime, so it is important to keep all the Ansible good practices on secret management in mind, such as using no_log: true where there is a risk of variables ending up in logs, avoiding the use of ansible.builtin.debug or other modules that will leak the content of variables, etc.
I think SOPS is a very good secret store for Ansible. Although it has capabilities that would let it scale to big teams, I think different designs would be better for those scenarios. Personally, I use it for single-user projects, so I’m not hitting any of those limitations, and I can clearly see that a small team wouldn’t either.