Blog
Next

Store Secrets Safely in Git with SOPS and age

A small, practical setup for keeping encrypted project secrets in Git without committing plaintext credentials.

I recently came across a secrets.yaml file in a public repository. It contains API tokens and passwords, yet it is safe to publish because the values are encrypted with SOPS and age.

There is one important condition: only the encrypted file and public keys belong in Git. The private age key must never be committed.

How it works

SOPS encrypts each value while leaving the YAML keys readable. This makes diffs useful without revealing the data:

database_password: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]

The sops section at the bottom stores encryption metadata and age recipients. An age recipient beginning with age1 is a public key, so it is fine to share. The matching private identity, beginning with AGE-SECRET-KEY-, is what unlocks the file.

Set it up

Install the two tools on macOS:

brew install sops age

On Linux, install sops and age with your package manager or from their official releases.

Generate an age identity:

mkdir -p "$HOME/Library/Application Support/sops/age"
age-keygen -o "$HOME/Library/Application Support/sops/age/keys.txt"
chmod 600 "$HOME/Library/Application Support/sops/age/keys.txt"

On Linux, use ~/.config/sops/age/keys.txt instead.

The command prints a public recipient. You can also retrieve it later:

age-keygen -y "$HOME/Library/Application Support/sops/age/keys.txt"

Keep keys.txt outside the project and back it up somewhere secure, such as a password manager. Losing it means losing access to the secrets.

Create .sops.yaml in the project root and replace the example recipient with yours:

creation_rules:
  - path_regex: secrets/.*\.ya?ml$
    age: age1your_public_recipient_here

Now create the encrypted file:

mkdir -p secrets
sops secrets/secrets.yaml

Your editor opens a temporary decrypted view. Add the values, save, and close it:

DATABASE_URL: postgres://user:password@localhost/app
API_TOKEN: replace-me

The file on disk will contain encrypted values. Check it before committing:

git diff -- secrets/secrets.yaml
sops decrypt secrets/secrets.yaml

The second command is only a local check. Avoid redirecting its output to a file, since that would create a plaintext copy.

Use the secrets in an application

If the application reads environment variables, SOPS can decrypt them only for the child process:

sops exec-env secrets/secrets.yaml 'pnpm dev'

For production, CI should receive its own age private key through the platform's secret store. Do not paste the private key into the repository, Docker image, build logs, or a committed .env file.

When a teammate joins, add their public recipient to .sops.yaml, then update the encrypted file:

sops updatekeys secrets/secrets.yaml

They can decrypt it with their own private key. Nobody needs to share a private key over chat.

A short safety checklist

  • Commit .sops.yaml and the encrypted secrets file.
  • Never commit an age private key or decrypted output.
  • Use separate recipients for people and CI systems.
  • Keep a secure backup of the private key.
  • Rotate a credential immediately if it was ever committed in plaintext. Encrypting it later does not remove it from Git history.

This setup works well for small projects and configuration repositories. For larger teams, a cloud KMS or dedicated secret manager can make access control, revocation, and auditing easier. SOPS supports those too, so the encrypted file format can stay while the key management grows with the project.

References