MO

Mozilla SOPS

Encrypt secrets in config files for version control

Password Managers ★ 23.2k stars Medium setup MPL-2.0

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