Encrypt Django Secrets in Git With SOPS

Keep Django environment secrets encrypted in the repository with Mozilla SOPS and age: encrypt .env and YAML files, decrypt during deploys with Ansible or CI, use cloud KMS keys for teams, and keep keys out of Git.

Problem statement

Secrets for a Django deployment (SECRET_KEY, database password, API keys) often live in a password manager, someone's laptop, or a CI variable screen, and drift from what the code expects. Committing them in plain text is out of the question. SOPS encrypts the values in .env, YAML, or JSON files while leaving keys readable, so encrypted secret files can live in Git next to the code: changes are reviewed in pull requests, history is kept, and only holders of the decryption key (a developer's age key, a CI key, or a cloud KMS key) can read them.

Secrets in Git with SOPSEdit secretssops opens editorEncrypted filevalues only, in GitDeploy decryptsCI or Ansible keyServer env file600, outside repoSOPS keeps secrets reviewable in Git while only deploy keys can decrypt them.
  1. Install sops and age; generate an age key per person and one for CI (age-keygen -o key.txt).
  2. Add .sops.yaml with creation_rules listing recipients (age public keys or KMS ARNs) per path.
  3. Encrypt: sops --encrypt --in-place deploy/secrets/prod.env; edit later with sops deploy/secrets/prod.env.
  4. Decrypt at deploy time in CI or Ansible, writing a chmod 600 env file on the server (or a Kubernetes Secret).
  5. Keep private keys out of the repository; store the CI key as a CI secret or use cloud KMS with IAM.
  6. Rotate recipients with sops updatekeys when people leave.

Step-by-step solution

1. Keys and rules

age-keygen -o ~/.config/sops/age/keys.txt     # prints the public key: age1...
# .sops.yaml
creation_rules:
  - path_regex: deploy/secrets/prod\..*
    age: >-
      age1alice...,
      age1bob...,
      age1ciprod...
  - path_regex: deploy/secrets/staging\..*
    age: age1alice...,age1bob...,age1cistaging...

Each file is encrypted with a data key that is itself encrypted for every recipient, so any listed key can decrypt. For teams on AWS or GCP, add kms: or gcp_kms: entries so access is controlled with IAM instead of distributing age keys.

2. Encrypt the environment file

cat > deploy/secrets/prod.env <<'ENV'
SECRET_KEY=replace-with-generated-value
DATABASE_URL=postgres://app:password@db.internal:5432/app?sslmode=require
STRIPE_API_KEY=sk_live_...
ENV
sops --encrypt --in-place deploy/secrets/prod.env
git add deploy/secrets/prod.env .sops.yaml

The committed file looks like SECRET_KEY=ENC[AES256_GCM,data:...,type:str] plus a sops_ metadata block. Variable names stay visible, which makes diffs reviewable: a pull request shows which secret changed, not its value.

a committed, encrypted prod.envSECRET_KEY=ENC[AES256_GCM,data:9fK2...,type:str]DATABASE_URL=ENC[AES256_GCM,data:x7Qp...,type:str]STRIPE_API_KEY=ENC[AES256_GCM,data:Lm3a...,type:str]sops_age__list_0__map_recipient=age1alice...sops_mac=ENC[AES256_GCM,data:...]1231Names stay readable, so diffs show which secret changed.2Values are encrypted with a per-file data key.3A MAC detects tampering with any value.Reviewers see structure and changes; only key holders see valuesdjango-deployment.com
Commit the encrypted file and .sops.yaml; never commit private keys.

3. Decrypt during deploys

In GitHub Actions:

- name: Decrypt production secrets
  env:
    SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY_PROD }}
  run: sops --decrypt deploy/secrets/prod.env > prod.env
- name: Upload env file
  run: |
    scp prod.env deploy@server:/etc/myapp/env.new
    ssh deploy@server 'install -m 600 /etc/myapp/env.new /etc/myapp/env && rm /etc/myapp/env.new'

With Ansible, the community.sops collection decrypts files as variables or templates them onto hosts. With Kubernetes and GitOps, tools such as the Flux SOPS integration or helm-secrets decrypt in-cluster into Kubernetes Secrets.

4. Edit and review

sops deploy/secrets/prod.env decrypts into your editor and re-encrypts on save. In code review, look for unexpected secrets being added or removed; the values remain hidden.

5. Rotate access

When a developer leaves, remove their key from .sops.yaml and run sops updatekeys on each file, then rotate the actual secret values they could read. Removing a recipient does not change values they have already seen.

secrets lifecycle with SOPS1Define recipients in .sops.yamlpeople, CI, or KMS2sops edits the fileencrypted on save3Pull request reviewnames visible, values hidden4CI decrypts with its keywrites env file or Secret5Offboarding: updatekeys + rotatevalues they saw changeRemoving a key stops future access; rotating values removes past accessdjango-deployment.com
Use separate CI keys per environment so staging CI cannot read production.

Verification check: git grep -n "sk_live\|postgres://" -- deploy/secrets returns nothing readable; sops --decrypt deploy/secrets/prod.env works only with an authorised key; the server's /etc/myapp/env is mode 600 and the app passes check --deploy.

Explanation

SOPS uses envelope encryption: it generates a data key per file, encrypts each value with AES-GCM, and encrypts the data key separately for each recipient using age, PGP, or a cloud KMS. It also computes a MAC over all values to detect tampering. Because only values are encrypted, files stay structured and diff-friendly, which brings secrets into the same review and versioning workflow as code without exposing them. Access control moves to whoever holds a recipient key or KMS permission.

Edge cases / notes

  • Plaintext leaks. Decrypted files must never be committed; add patterns like *.decrypted and prod.env (outside deploy/secrets) to .gitignore.
  • Merge conflicts in encrypted files are hard to resolve by hand; decrypt both versions with sops and re-encrypt.
  • KMS outages block decryption; keep an age break-glass key stored securely offline.
  • Not a runtime store. SOPS protects secrets at rest in Git; the deployed env file still needs strict permissions.

Next steps

FAQ

Is it safe to commit SOPS-encrypted secrets?

Yes, if the private keys stay out of the repository and recipients are limited to people and systems that need access.

age or PGP for SOPS?

age is simpler and recommended for new setups. Cloud KMS is best for teams already managing IAM.

How does CI decrypt SOPS files?

Give CI its own age key as a CI secret, or grant its cloud identity KMS decrypt permission.

What happens when someone leaves the team?

Remove their key, run sops updatekeys, and rotate the secret values they had access to.

Keep going