We’ve all been there. You’re moving fast, the deadline is tomorrow, and you just need this thing to work. So you do the thing. The unspeakable thing.
db_password: "mySuperSecretPassword123"
And then you commit it. And push it. To a public repo.
Congratulations — your database credentials are now indexed by GitHub search and every bot on the internet is attempting to connect to your server as we speak.
Let’s fix that. For good.
The Problem With “I’ll Handle It Later”
Secrets management is one of those things everyone knows they should do properly, but nobody does until something goes wrong. And when something goes wrong with secrets, it’s not a “oops, fix it and move on” situation. It’s a “rotate every credential, audit every access log, notify your users” situation.
The good news? Doing it right is actually not that complicated. The approach just depends on your situation.
Scenario 1: You’re Flying Solo
You’re a solo dev. No team. Just you, your laptop, and an unhealthy amount of coffee. You don’t need an enterprise-grade secrets vault — but you do need something.
The Ansible Vault Approach
If you’re using Ansible (and you should be — more on that another time), Ansible Vault lets you encrypt individual values right inside your YAML files.
ansible-vault encrypt_string 'mySuperSecretPassword123' --name 'db_password'
This spits out something like:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653337396131333566316364373666653437396162393731343237
3436643062343566626635663739653934663930386566310a316330623337313930626134326334
...
That blob is safe to commit. It’s AES-256 encrypted. Nobody’s reading that without your vault password.
When you run your playbook, Ansible asks for the vault password (or you point it to a password file):
ansible-playbook playbook.yml --ask-vault-pass
# or
ansible-playbook playbook.yml --vault-password-file ~/.vault_pass
Where Do You Store the Vault Password?
Not in the repo. Never in the repo.
For solo work, your options are:
- A file at
~/.vault_passon your machine (never committed) - Your system keychain (macOS Keychain, GNOME Keyring)
- A password manager like 1Password or Bitwarden
Add the password file to .gitignore:
.vault_pass
ansible/host_vars/
SSH Keys: Keep Them On Your Machine
Your personal SSH key (~/.ssh/id_rsa or ~/.ssh/id_ed25519) never leaves your machine. Full stop. You don’t commit it. You don’t send it over Slack. You don’t put it in a .env file.
Think of it like your house key. You wouldn’t photocopy it and tape it to the front door.
Scenario 2: You’re on a Small Team (2–10 People)
Now things get interesting. You’ve got a team. People are onboarding. Someone’s going on vacation. Another person just left. Suddenly “just keep it on your machine” doesn’t scale anymore.
Separate Concerns: Three Types of Keys
Before we go further, let’s get clear on the three types of SSH keys you’re dealing with:
| Key Type | Who Has It | Where It Lives |
|---|---|---|
| Personal key | Individual developer | Their laptop only |
| Deploy key | The server | Server’s ~/.ssh/, public key on GitHub |
| CI/CD key | GitHub Actions / your CI | Stored as a secret in the CI platform |
These are three completely different things. Don’t conflate them.
Deploy Keys: One Key Per Repo
A deploy key is an SSH keypair where:
- The private key lives on your server
- The public key is registered on your GitHub repo as a “deploy key”
Generate one on your server:
ssh-keygen -t ed25519 -f ~/.ssh/my_project_deploy -N "" -C "deploy@my-project"
Add the public key to GitHub at Settings → Deploy keys. Give it read-only access. Done.
Now your server can pull from GitHub without your personal credentials being involved at all. If the server gets compromised, you revoke that one deploy key. Your personal access is untouched.
# ~/.ssh/config on the server
Host github-myproject
HostName github.com
User git
IdentityFile ~/.ssh/my_project_deploy
Sharing the Vault Password With Your Team
For a small team, you have a few options:
Option A: Shared password manager Use 1Password Teams, Bitwarden Organizations, or similar. Create a shared vault. Put the Ansible vault password there. Everyone on the team gets access. When someone leaves, you change the password, re-encrypt, and update the shared vault.
Option B: ansible-vault with key per environment Use different vault passwords for staging vs production. Staging password is more widely shared. Production password is restricted to whoever actually deploys to prod.
ansible-vault encrypt_string 'stagingPassword' --name 'db_password' --vault-id staging@~/.vault_pass_staging
ansible-vault encrypt_string 'prodPassword' --name 'db_password' --vault-id prod@~/.vault_pass_prod
Option C: A dedicated secrets manager If you’re already on AWS, Azure, or GCP, use their native secrets managers (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager). More setup, but much better audit trails and access control.
The “Someone Just Left” Protocol
When a team member leaves:
- Rotate the vault password — re-encrypt all secrets with a new password
- Revoke their personal SSH key from any servers they had access to
- Rotate any shared credentials they had access to (DB passwords, API keys)
- Remove them from the shared password manager
Step 3 is the one people skip. Don’t skip step 3.
Scenario 3: Big Organization
You’ve got multiple teams, multiple environments, compliance requirements, and an audit coming up next quarter. Welcome to enterprise secrets management. It’s exactly as fun as it sounds.
Dedicated Secrets Managers Are Non-Negotiable
At this scale, you need a proper secrets manager:
- HashiCorp Vault — the gold standard for on-premise/multi-cloud
- AWS Secrets Manager — if you’re AWS-native, it’s excellent
- Azure Key Vault — same deal for Azure shops
- Doppler or Infisical — modern SaaS options with great DX
These give you:
- Centralized access control (RBAC)
- Secret rotation (automatic or scheduled)
- Full audit logs (who accessed what, when)
- Short-lived credentials (tokens that expire)
Ansible Vault at Scale
Ansible Vault still works at scale, but you wire it to your secrets manager instead of a local file:
# Use a script that fetches from AWS Secrets Manager as the vault password source
ansible-playbook playbook.yml --vault-password-file scripts/get-vault-password.sh
Where get-vault-password.sh does something like:
#!/bin/bash
aws secretsmanager get-secret-value \
--secret-id "ansible/vault-password" \
--query SecretString \
--output text
Now the vault password itself is protected by IAM. Nobody can decrypt your Ansible secrets without the right AWS permissions. Beautiful.
CI/CD at Scale: GitHub Actions + Secrets
For GitHub Actions, secrets are stored at the repo or environment level:
# .github/workflows/deploy.yml
- name: Deploy to staging
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: /home/forge/deploy.sh
The STAGING_SSH_KEY here is the private key of a dedicated CI/CD keypair. Its public key is in ~/.ssh/authorized_keys on the server. When an engineer leaves, you rotate this key pair — not your personal keys.
For production deployments, use GitHub Environments with required reviewers:
deploy-prod:
environment: production # Requires approval from designated reviewers
needs: deploy-staging
This means nobody — not even someone with repo write access — can deploy to production without a human approving it. That’s the kind of thing that saves jobs.
The Golden Rules
Let’s wrap this up with the rules that apply regardless of your team size:
| Rule | Why |
|---|---|
| Never commit secrets to git | Rotation after exposure is a nightmare |
| One key per purpose | If it’s compromised, blast radius is minimal |
| Personal keys stay on your machine | Always |
| Rotate credentials when someone leaves | Don’t be the company that doesn’t do this |
| Use short-lived tokens where possible | Expiry is free rotation |
| Audit who has access to what | You can’t protect what you can’t see |
| Store the vault password somewhere secure, not in the repo | Obvious in hindsight, disaster in practice |
Quick Reference
Solo developer:
# Encrypt a secret
ansible-vault encrypt_string 'my-secret' --name 'var_name'
# Run playbook with vault
ansible-playbook playbook.yml --ask-vault-pass
# Generate a deploy key
ssh-keygen -t ed25519 -f ~/.ssh/project_deploy -N "" -C "deploy@project"
Small team checklist:
- Vault password in shared password manager
- One deploy key per server/repo combination
- CI secrets stored in GitHub Secrets, not in code
- Offboarding protocol documented
-
.gitignoreincludes all key files and password files
Big org checklist:
- HashiCorp Vault / AWS Secrets Manager / equivalent set up
- Vault password fetched from secrets manager at runtime
- Production environment has required reviewers
- Audit logging enabled
- Automatic secret rotation configured for critical credentials
Security doesn’t have to be complicated. It just has to be intentional.
Now go rotate that password you’ve had since 2021. You know the one.