Mozilla SOPS
Encrypt secrets in config files for version control
SOPS is an open-source editor of encrypted files that supports YAML, JSON, ENV, INI and binary formats. It encrypts secret values so configuration with credentials can be safely stored in git.
Key features
- Encrypts only the values in config files
- Works with age, GPG and KMS keys
- Editor integration for encrypted files
- Git-friendly secrets management
Pros & cons
Strengths
- Keeps secrets in version control safely
- No server to run
Trade-offs
- Not a vault with access control
Mozilla SOPS replaces
Last reviewed Aug 26, 2026 · 820 words
The commonest secrets leak in a homelab is not a hacked server. It is a docker-compose.yml with a database password in it, pushed to a repository that was private until it wasn't. SOPS fixes that specific problem with about 10 minutes of setup: it encrypts the values in a YAML, JSON, .env or INI file while leaving the keys readable, so the file diffs cleanly, reviews cleanly and commits safely. There is no server, no database and no daemon, which is why the 64 MB RAM figure really means "whatever a Go binary needs for one second". It started at Mozilla in 2015 and now lives under the CNCF as a project in its own right.
What an encrypted file actually looks like
Take a plain compose env file:
POSTGRES_PASSWORD=hunter2
SMTP_PASSWORD=app-specific-pw
After sops --encrypt, each value becomes an ENC[AES256_GCM,data:...,iv:...,tag:...,type:str] blob, and a sops metadata block is appended recording which keys can decrypt it, a timestamp and a MAC over the whole file. The left-hand side stays POSTGRES_PASSWORD=, so git log -p still tells you what changed, just not to what. The MAC means someone with write access to the repo cannot splice one encrypted value into another file without SOPS refusing to decrypt.
Use age keys, not GPG, unless you already run GPG
SOPS accepts GPG, age, AWS KMS, GCP KMS, Azure Key Vault and HashiCorp Vault as key backends. For a self-hoster the answer is age: a single-file keypair, no keyring, no expiry drama.
age-keygen -o ~/.config/sops/age/keys.txt
# Public key: age1...
A .sops.yaml at the repo root then tells SOPS which files get which recipients, so you never pass keys on the command line again:
creation_rules:
- path_regex: .*\.env$
age: age1yourlaptopkey
- path_regex: secrets/.*\.yaml$
age: age1yourlaptopkey,age1yourserverkey
Now sops --encrypt --in-place stack.env encrypts, sops stack.env opens a decrypted copy in $EDITOR and re-encrypts on save, and sops -d stack.env prints plaintext to stdout. Multiple recipients (laptop, server, a recovery key stored in the password manager) all decrypt the same file, and sops updatekeys re-wraps a file when a machine joins or leaves.
The Docker Compose pattern that keeps plaintext off disk
The trick is exec-env, which decrypts into the environment of a child process and never writes a cleartext file:
sops exec-env stack.env 'docker compose up -d'
Compose interpolates ${POSTGRES_PASSWORD} from the environment; the encrypted file is the only thing on disk or in git. On the server, SOPS_AGE_KEY_FILE=/root/.config/sops/age/keys.txt is the one path that must exist and must never be in the repo. Keep a copy of that private key in Vaultwarden as well, because losing it turns every encrypted file into noise. This slots into the Compose layout most homelabs converge on: one repo, one directory per stack, secrets encrypted alongside the compose file that uses them.
What SOPS is not
It is not a vault. There is no access control beyond who holds a key, no audit log of who decrypted what, no dynamic credentials, no scheduled rotation, no UI. If you need per-user policies, short-lived database passwords or a web console, that is OpenBao or Infisical territory, and both are heavier by an order of magnitude. Most single-operator homelabs need none of that; they need the compose password out of git, and SOPS is the smallest tool that does it well.
One consequence people miss: old commits stay decryptable by any key that was a recipient at the time. If a key leaks, the fix is to rotate every secret that key could read, not just to swap the key.
Two habits that prevent the classic mistakes
Never run sops -d file > file.dec inside the repo directory; the decrypted output ends up staged and pushed, which is the exact failure you installed SOPS to prevent. Use exec-env, exec-file or the editor mode instead. Second, add a pre-commit hook that checks every file matching your .sops.yaml rules contains a sops: metadata block and rejects the commit otherwise. A file that was never encrypted looks exactly like an ordinary file to git, and nothing else will catch it.
What I'd do
An age key on every machine that deploys, public keys listed in .sops.yaml, every .env and every Kubernetes secret manifest encrypted in place, and sops exec-env in the deploy script. Private keys backed up in the password manager. No vault server until there is more than one human deploying, and even then SOPS stays for the repo-side files. It has done this one job well for a decade, and it is the tool I would keep if I could keep only one from this category.
Compare Mozilla SOPS
5 head-to-head comparisons.
Similar password managers apps
Vaultwarden
Password ManagersLightweight Bitwarden-compatible password server
Replaces 1Password, LastPass
HashiCorp Vault
Password ManagersSecrets management and data protection for infrastructure
Replaces AWS Secrets Manager, Azure Key Vault
Infisical
Password ManagersOpen-source secrets management platform for developers
Replaces AWS Secrets Manager, Doppler
KeePassXC
Password ManagersCross-platform offline KeePass password manager
Replaces LastPass, 1Password
Bitwarden
Password ManagersOpen-source password manager you can self-host
Replaces 1Password, LastPass
KeeWeb
Password ManagersWeb-based client for KeePass databases
Replaces LastPass, 1Password