Skip to content

SOPS Backend Guide

envdrift supports two encryption backends: dotenvx (default) and SOPS. This guide is the end-to-end reference for the SOPS path — how it differs from dotenvx, why SOPS users don't use envdrift's Vault Sync, and exactly how to set it up against a cloud KMS (with a full Azure Key Vault walkthrough).

If you're choosing between backends first, read Encryption Backends. If you want the shared encrypt/decrypt mechanics, see Encryption.

The core difference: who distributes the decrypt key

This is the single most important thing to understand, because it changes which commands you use.

dotenvx SOPS
Decrypt key lives in a portable .env.keys file (DOTENV_PRIVATE_KEY_<ENV>) your KMS / age / PGP — there is no .env.keys
How teammates get decrypt access envdrift Vault Sync pushes the key to a cloud vault and pulls it back you grant access in KMS/age/PGP itself (e.g. an Azure RBAC role on the key)
Team commands sync, pull, vault-push, vault-pull, decrypt --verify-vault not needed — use encrypt / decrypt

dotenvx has one secret artifact (.env.keys) that must reach every teammate, so envdrift gives you Vault Sync to distribute it. SOPS has no such portable secret: it wraps each file's data key with your KMS/age/PGP key, and that system already decides who can decrypt. So with SOPS you never push a key anywhere — you add a teammate to the KMS key (or hand them an age key) and they can decrypt immediately.

SOPS does not use Vault Sync — and doesn't need to

The entire Vault Sync family (vault-push, vault-pull, sync, and decrypt --verify-vault) is built around the dotenvx .env.keys artifact and is dotenvx-only. decrypt --verify-vault explicitly rejects non-dotenvx backends. SOPS access control lives in your KMS/age/PGP, not in an envdrift vault mapping.

Two ways to use vault clouds — don't confuse them

The word "vault" means two different things here:

  1. envdrift Vault Sync — pushes/pulls the dotenvx .env.keys private key using your cloud vault's secrets API (e.g. Azure Key Vault secrets). dotenvx only.
  2. A cloud KMS as the SOPS key — SOPS wraps the file's data key with a cloud key (e.g. Azure Key Vault keys, AWS KMS, GCP KMS). This is SOPS-native.

So SOPS absolutely uses cloud vaults — just through the keys API via SOPS's own config, not through envdrift's key-pushing commands. The permission sets are different: dotenvx Vault Sync needs Secrets Get/List; SOPS + Azure Key Vault needs Keys crypto permissions (see below).

Install

pip install envdrift          # SOPS support needs no extra envdrift extras

You also need the sops binary on PATH. Either install it yourself (brew install sops, or see the SOPS releases) or let envdrift fetch it by setting auto_install = true (below).

Configuration

Set SOPS as the backend in envdrift.toml (or [tool.envdrift] in pyproject.toml). All SOPS options live under the [encryption.sops] subsection:

[encryption]
backend = "sops"

[encryption.sops]
auto_install = false              # set true to let envdrift download the sops binary
config_file = ".sops.yaml"        # optional: path to a SOPS policy file
age_recipients = "age1example..." # age public key(s) for encryption
age_key_file = "keys.txt"         # age private key for local decryption (sets SOPS_AGE_KEY_FILE)
# kms_arn  = "arn:aws:kms:us-east-1:123456789:key/abc-123"
# gcp_kms  = "projects/p/locations/global/keyRings/r/cryptoKeys/k"
# azure_kv = "https://my-vault.vault.azure.net/keys/my-key/<version>"

Use the [encryption.sops] subsection — not flat keys

Options are read from the [encryption.sops] table (config_file, age_recipients, azure_kv, …). Flat keys like sops_config_file under [encryption] are ignored by the parser.

The simple path: encrypt / decrypt

For SOPS this is all you need — no vault mappings, no extra config sections.

# Encrypt in place (backend + key resolved from envdrift.toml)
envdrift encrypt .env.production

# Or pick the backend/key explicitly, ignoring config
envdrift encrypt .env.production --backend sops --age age1example...

# Decrypt in place (backend auto-detected from the file's SOPS metadata)
envdrift decrypt .env.production

SOPS encrypts values while keeping keys readable and appends its own metadata:

DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
API_KEY=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
sops_version=3.13.1

Flags differ by direction (both override config):

  • encrypt--backend, --sops-config, --age, --age-key-file, --kms, --gcp-kms, --azure-kv. The key-recipient flags (--age/--kms/--gcp-kms/ --azure-kv) choose who can decrypt and are encryption-only.
  • decrypt--backend, --sops-config, --age-key-file. Decryption needs no key-recipient flags: SOPS reads the recipients from the file's metadata and unwraps the data key using your credentials (az login, KMS access, or the age key).

See encrypt and decrypt.

Azure Key Vault walkthrough (verified end-to-end)

This sets up SOPS to wrap your data keys with an Azure Key Vault key. envdrift authenticates to Azure via SOPS, which uses the standard Azure credential chain (az login, env vars, or managed identity).

1. Permissions — Key Vault keys, not secrets

SOPS uses the vault's keys API. This is a different RBAC role than dotenvx Vault Sync (which uses secrets). On the vault, grant:

Task Built-in role
Create the key (one-time) Key Vault Crypto Officer
Encrypt / decrypt files (everyone) Key Vault Crypto User
# Replace <user-or-principal> and the scope with your vault's resource ID
SCOPE=$(az keyvault show --name my-vault --query id -o tsv)

az role assignment create --assignee "<user-or-principal>" \
  --role "Key Vault Crypto Officer" --scope "$SCOPE"   # to create keys
az role assignment create --assignee "<user-or-principal>" \
  --role "Key Vault Crypto User"   --scope "$SCOPE"   # to encrypt/decrypt

RBAC changes take ~30–60s to propagate. If you only hold Key Vault Secrets Officer (the dotenvx Vault Sync role), az keyvault key create will fail with Forbidden (ForbiddenByRbac) — that's the secrets-vs-keys distinction in action.

2. Create the key

az keyvault key create --vault-name my-vault --name sops-key \
  --kty RSA --size 2048 --query "key.kid" -o tsv
# -> https://my-vault.vault.azure.net/keys/sops-key/<version>

3. Point envdrift at it

[encryption]
backend = "sops"

[encryption.sops]
azure_kv = "https://my-vault.vault.azure.net/keys/sops-key/<version>"

4. Encrypt and decrypt

envdrift encrypt .env.production       # wraps the data key with your Azure key
envdrift decrypt .env.production       # calls Azure to unwrap, restores plaintext

The encrypted file records which key was used, so decryption needs no extra config — only az login (or any Azure credential with Key Vault Crypto User on the key):

sops_azure_kv__list_0__map_vault_url=https://my-vault.vault.azure.net
sops_azure_kv__list_0__map_name=sops-key
sops_azure_kv__list_0__map_version=<version>

Granting a teammate decrypt access

No key push. Give them Key Vault Crypto User on the key (or vault) and they can envdrift decrypt immediately:

az role assignment create --assignee teammate@example.com \
  --role "Key Vault Crypto User" --scope "$SCOPE"

Using pull and lock with SOPS

Partly — and this trips people up, so here is the precise behavior.

pull and lock are part of the Vault Sync family. They do two things:

  1. A key-sync step (fetch/verify the dotenvx .env.keys private key from a vault) — this is dotenvx-only. pull runs it by default; lock runs it only with --verify-vault or --sync-keys.
  2. An encrypt/decrypt step that uses whichever backend your config resolves to — this does support SOPS.

Consequences for SOPS:

  • lock and pull refuse to start without a [vault.sync] section (and a [vault] provider block) — they derive their file work-list and build a vault client up front, regardless of backend. The provider block needs that provider's own required field: vault_url for Azure/HashiCorp, region for AWS (defaulted), project_id for GCP. A pure-SOPS project has none of this.
  • lock does not hit Vault Sync by default (it only encrypts), so lock --force works for SOPS once the config above exists. Plain envdrift pull, however, runs the key-sync step, looks for a dotenvx secret, and fails for SOPS — use envdrift pull --skip-sync to go straight to decryption.

Recommendation: for SOPS, use envdrift encrypt / envdrift decrypt. They need no vault scaffolding and are the intended SOPS workflow. Only reach for lock/pull if you specifically want their batch file-discovery, in which case add a minimal [vault.sync]/[vault] block and run pull --skip-sync.

# Minimal scaffolding ONLY if you want lock/pull to drive SOPS encrypt/decrypt.
# No key is ever pushed/pulled here — access is your KMS/age/PGP.
[vault]
provider = "azure"

[vault.azure]
vault_url = "https://my-vault.vault.azure.net/"

[vault.sync]
default_vault_name = "my-vault"

[[vault.sync.mappings]]
secret_name = "unused-for-sops"   # never read for SOPS; only gives a work-list
folder_path = "."
environment = "production"
envdrift lock --force          # encrypts mapped files with SOPS
envdrift pull --skip-sync      # decrypts mapped files with SOPS (no secrets call)

What SOPS does not support

  • envdrift Vault Sync (vault-push, vault-pull, sync, decrypt --verify-vault) — dotenvx-only by design.
  • Partial encryption (push / pull-partial) — dotenvx-only.

See also