Ansible for the Homelab — Part 4: Secrets Management with Ansible Vault

Ansible for the Homelab — Part 4: Secrets Management with Ansible Vault
Ansible for the Homelab — The Daily Stomp

By now your playbooks are doing real work - bootstrapping machines, installing Docker, managing roles. At some point you're going to want to commit all of this to git. And that's when you notice the problem: your inventory and group vars contain API keys, tokens, and passwords in plain text.

Ansible Vault solves this. It encrypts sensitive values so you can safely commit everything to a repository without exposing secrets.

The Problem with Plaintext Secrets

Consider a Cloudflare role that needs an API token. The naive approach puts it directly in a vars file:

# roles/cloudflare-dns/vars/main.yml
cloudflare_api_token: "your-token-here"   # ← anyone with repo access can read this

Commit that and your token is in git history forever. Even if you delete it later, it's already there.

The Split Vars Pattern

The clean solution is to keep two vars files side by side: one for non-sensitive values (committed in plain text) and one for secrets (committed encrypted).

roles/cloudflare-dns/vars/
├── main.yml     ← plain text, safe to commit
└── vault.yml    ← encrypted, safe to commit

In main.yml, reference the vault variable by name using a vault_ prefix convention:

# roles/cloudflare-dns/vars/main.yml
cloudflare_api_token: "{{ vault_cloudflare_api_token }}"

In vault.yml, store the actual secret:

# roles/cloudflare-dns/vars/vault.yml  (before encrypting)
vault_cloudflare_api_token: "your-actual-token-here"

Ansible loads both files automatically and resolves the reference at runtime. Your real token never appears outside the encrypted file.

Encrypting the Vault File

Once you've written vault.yml with the real values, encrypt it:

ansible-vault encrypt roles/cloudflare-dns/vars/vault.yml

You'll be prompted to create a vault password. After encryption, the file becomes unreadable ciphertext - but it's still a valid YAML file as far as git is concerned. Commit it freely.

To view or edit an encrypted file later:

ansible-vault view roles/cloudflare-dns/vars/vault.yml
ansible-vault edit roles/cloudflare-dns/vars/vault.yml

Running Playbooks with Vault

When you run a playbook that uses encrypted files, Ansible needs the vault password. The simplest way:

ansible-playbook playbooks/setup-cloudflare-dns.yml --ask-vault-pass

For automated runs or to avoid typing the password every time, store it in a file and point ansible.cfg at it:

echo "your-vault-password" > ~/.ansible_vault_pass
chmod 600 ~/.ansible_vault_pass
# ansible.cfg
[defaults]
vault_password_file = ~/.ansible_vault_pass

Add ~/.ansible_vault_pass to your .gitignore. It stays on your machine; the encrypted vault files travel with your repo.

Encrypting Individual Strings

Sometimes you don't want a whole separate file - you just need to encrypt a single value inline. encrypt_string handles this:

ansible-vault encrypt_string 'your-token-here' --name 'cloudflare_api_token'

Output:

cloudflare_api_token: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  62336138366562613266643065306630...

Paste that directly into any vars file. The !vault tag tells Ansible to decrypt it at runtime.

What You Have Now

group_vars/
├── all.yml        ← non-sensitive defaults, committed in plain text
└── vault.yml      ← encrypted, committed safely

roles/
├── cloudflare-dns/vars/
│   ├── main.yml   ← references {{ vault_cloudflare_api_token }}
│   └── vault.yml  ← encrypted API token
└── cloudflare-tunnel/vars/
    ├── main.yml
    └── vault.yml  ← encrypted tunnel credentials

Your repo is now safe to push. Secrets are encrypted at rest, decrypted at runtime, and never exposed in plain text.

What's Next

Part 5 puts all of this together with real services - deploying Docker containers using Ansible, managing compose files, and getting applications running across the homelab.