๐ Mozilla SOPS: age, KMS, and GitOps-Friendly Secret Workflows
Intro: SOPS is one of the cleanest ways to keep encrypted configuration in Git without normalizing long-lived plaintext secrets in the repository. The important design question is not โcan we encrypt a YAML file?โ but who decrypts, where, with what root of trust, and for how long.
What this page includes
- where SOPS fits relative to Vault, CI variables, and Kubernetes-native secret patterns;
- installation, bootstrap, and day-one usage with
age;- examples for
.sops.yaml, file encryption, key rotation, and CI/GitOps usage;- sample outputs and review notes for teams adopting SOPS.
What SOPS is good at
SOPS is strongest when a team wants to:
- keep encrypted YAML/JSON/ENV/INI files in Git;
- let operators edit secrets without inventing a custom crypto workflow;
- use age or cloud KMS as the root of trust;
- preserve structure while encrypting leaf values rather than storing one giant opaque blob;
- support GitOps or config-in-repo flows where plaintext should never be committed.
What SOPS is not
SOPS is not a full secret lifecycle platform. It does not replace:
- dynamic short-lived secrets from Vault or cloud secret managers;
- rotation orchestration for database credentials or cloud identities;
- access governance, approvals, or audit flows by itself;
- workload-identity design.
A practical mental model is:
- use SOPS when encrypted config must live in Git;
- use Vault / cloud secret managers when a runtime needs dynamic secret issuance, rotation, revocation, or brokered access.
Preferred keying choice
When possible, prefer age for simple modern file encryption. Use cloud KMS or Vault transit where the organization already has strong control ownership and audit around those services.
Installation patterns
Linux amd64 example
export SOPS_VERSION=v3.11.0
curl -LO "https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64"
sudo mv "sops-${SOPS_VERSION}.linux.amd64" /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
sops --version
macOS with Homebrew
brew install sops age
sops --version
age --version
Windows notes
Use the official release artifact or package manager the team standardizes on, then verify sops --version from PowerShell or a terminal. Keep the private age key in the standard user config path or a specifically controlled override path.
Day-one bootstrap with age
1. Generate an age key pair
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
Print the recipient (public key):
grep '^# public key:' ~/.config/sops/age/keys.txt
2. Create a .sops.yaml policy
creation_rules:
- path_regex: secrets/.*\.ya?ml$
age:
- age1examplepublickeyreplacewithrealrecipient
3. Encrypt a file in place
sops encrypt -i secrets/app.enc.yaml
4. View or edit it safely
sops decrypt secrets/app.enc.yaml
sops edit secrets/app.enc.yaml
Example encrypted file shape
SOPS preserves document structure and adds metadata. A typical encrypted YAML looks like this:
apiVersion: v1
kind: Secret
metadata:
name: app-config
stringData:
DB_PASSWORD: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
API_TOKEN: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
sops:
age:
- recipient: age1examplepublickeyreplacewithrealrecipient
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-29T12:00:00Z"
mac: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
version: 3.11.0
Useful commands
Encrypt to stdout
sops encrypt --age age1examplepublickeyreplacewithrealrecipient secrets/app.yaml > secrets/app.enc.yaml
Decrypt to stdout
sops decrypt secrets/app.enc.yaml
Decrypt from stdin with explicit type
cat secrets/app.enc.yaml | sops decrypt --input-type yaml --output-type yaml
Extract one value
sops decrypt --extract '["stringData"]["DB_PASSWORD"]' secrets/app.enc.yaml
Rotate data key metadata after changing recipients or policy
sops updatekeys -y secrets/app.enc.yaml
In-place edit using the configured editor
EDITOR=vim sops edit secrets/app.enc.yaml
Example .sops.yaml patterns
Simple age-based repo policy
creation_rules:
- path_regex: secrets/.*\.ya?ml$
age:
- age1teamrecipientaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Different rules by path
creation_rules:
- path_regex: env/dev/.*\.ya?ml$
age:
- age1devrecipientaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- path_regex: env/prod/.*\.ya?ml$
age:
- age1prodrecipientbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
KMS-backed example
creation_rules:
- path_regex: prod/.*\.ya?ml$
kms:
- arn:aws:kms:us-east-1:111122223333:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Where the age private key lives
By default, SOPS looks for keys.txt in the SOPS config directory for the user profile. Keep that file out of Git, restrict file permissions, and prefer workstation storage or secret injection patterns that match the teamโs trust model.
Useful overrides:
export SOPS_AGE_KEY_FILE=/secure/location/keys.txt
export SOPS_AGE_RECIPIENTS=age1examplepublickeyreplacewithrealrecipient
CI and GitOps usage notes
Safer CI pattern
- CI receives decryption authority from a workload identity or tightly-scoped cloud principal.
- The pipeline decrypts only in the stage that truly needs plaintext.
- Plaintext is written to ephemeral workspace only.
- Logs, artifacts, and debug output never include decrypted secret values.
- The job destroys workspace and short-lived credentials after use.
What not to do
- do not store the private age key as a long-lived plaintext blob in the repository or base image;
- do not decrypt in a wide early stage if only one later deploy step needs plaintext;
- do not assume โencrypted in Gitโ means runtime exposure is solved;
- do not give every developer production recipients if the team has real environment separation.
Sample output you should expect
Version check
$ sops --version
sops 3.11.0
Extracting a field
$ sops decrypt --extract '["stringData"]["DB_PASSWORD"]' secrets/app.enc.yaml
super-secret-password-value
Missing key example
$ sops decrypt secrets/app.enc.yaml
Failed to get the data key required to decrypt the SOPS file.
Review checklist
.sops.yamlexists and matches repo layout intentionally- recipients differ by environment where that distinction matters
- private keys or KMS auth are not baked into the repo or base image
- CI decrypts only at the stage that needs plaintext
- decrypted values do not land in artifacts, logs, shell history, or cache layers
- operational ownership exists for recipient changes and
updatekeys
Use this page with
- Secret Management on HashiCorp Vault
- Workload Federation and Non-Human Identities
- GitHub, GitLab, and Cloud Trust Patterns
- snippets/secrets/.sops.yaml.example
- snippets/secrets/sops-age-bootstrap.sh
- snippets/secrets/sops-output-sample.txt
Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.