1 SOPS and Secrets
Matt Cupp edited this page 2026-05-29 17:35:45 -04:00

SOPS and Secrets

How secrets (database passwords, API tokens, etc.) are kept out of git in plaintext while still being recoverable if Nexus needs to be rebuilt.

Overview

Plaintext secrets can't go into git. The solution here uses two layers:

  • Runtime .env files — live only on Nexus at /home/matt/repos/homelab-docker/<service>/.env, are gitignored via **/.env, and are read directly by docker compose up -d. During normal operation, SOPS is not involved at all.
  • .env.enc files — SOPS-encrypted versions committed to git. These are disaster-recovery backups only. If Nexus needs to be rebuilt from scratch, the plaintext .env files would be gone; .env.enc files make them recoverable.

Komodo deploys without touching SOPS. The encrypted files exist purely so secrets can be reconstructed from git if the host is lost.

Tools

Tool Purpose
SOPS Encrypts/decrypts files (only values, not keys — diffs stay readable)
age Encryption backend; provides the public/private keypair

Key Holders

Either machine can encrypt or decrypt.

Machine Private key location
Nexus ~/.config/sops/age/keys.txt
Atlas %APPDATA%\sops\age\keys.txt

Public keys are declared in .sops.yaml at the root of the homelab-docker repo:

creation_rules:
  - path_regex: \.env(\.enc)?$
    age: >-
      age18p7sc5xrer90x4l5jcn7deu8lputkvatjd223n4ldn2ujr2zhaqq4u78l5,
      age1uf500t6wxfxzf5sd723lmynajxva4qy3wt9zvx7rqzk3azmuas7q0m00cp
    # First key: Nexus (~/.config/sops/age/keys.txt)
    # Second key: Atlas (%APPDATA%\sops\age\keys.txt)

The path_regex covers both .env and .env.enc files automatically. Files are encrypted for both public keys simultaneously — either private key can decrypt.

Current Backup Status

Service .env.enc in git?
bookstack Yes
tandoor Yes
frigate Yes
wyze-bridge Yes
dashy No (no secrets)
homeassistant No
komodo No
pinchflat No
mosquitto No
freshrss No
dispatcharr No

Services without .env.enc must be restored from PBS backups or recreated manually if Nexus is rebuilt.

Common Operations

Decrypt a backup to restore a .env

cd /home/matt/repos/homelab-docker
sops -d --input-type dotenv --output-type dotenv bookstack/.env.enc > bookstack/.env

Edit an encrypted file in-place

SOPS decrypts the file, opens it in $EDITOR, and re-encrypts on save.

sops bookstack/.env.enc

Encrypt a plaintext .env to create or update a backup

cd /home/matt/repos/homelab-docker
sops --encrypt bookstack/.env > bookstack/.env.enc

Then commit the .env.enc file. Never commit the plaintext .env.

Adding a New Service with Secrets

  1. Create the plaintext .env in the service directory on Nexus: /home/matt/repos/homelab-docker/<service>/.env It is gitignored automatically by **/.env — no extra config needed.

  2. Add env_file: - ./.env to the service's docker-compose.yml.

  3. Encrypt a backup:

    cd /home/matt/repos/homelab-docker
    sops --encrypt <service>/.env > <service>/.env.enc
    
  4. Commit the .env.enc file:

    git add <service>/.env.enc
    git commit -m "feat(<service>): add SOPS-encrypted env backup"
    

Installing SOPS and age

Nexus (Ubuntu Server)

# Install age
sudo apt update
sudo apt install age

# Install sops (check https://github.com/getsops/sops/releases for latest)
SOPS_VERSION="3.9.1"
wget https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64 \
  -O /tmp/sops
sudo mv /tmp/sops /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops

# Point SOPS at the key file automatically
echo 'export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt' >> ~/.bashrc
source ~/.bashrc

# Verify
sops --version
age --version

Atlas (Windows 11)

Install via winget or download binaries from the GitHub releases pages:

Add both executables to your PATH. The key file lives at %APPDATA%\sops\age\keys.txt.

Generating a new key pair (bootstrap only)

Run once on each machine. Store the output (keys.txt) in Bitwarden — if the file is lost, all encrypted backups become unrecoverable.

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# Output: Public key: age1...  ← add this to .sops.yaml

After generating, add the new public key to .sops.yaml and re-encrypt all existing .env.enc files so the new key can decrypt them.

Troubleshooting

"no age keys found" / SOPS can't find a key

Ensure the key file exists and the env var points to it:

ls -la ~/.config/sops/age/keys.txt
echo $SOPS_AGE_KEY_FILE

If the env var is unset, either source .bashrc or pass the path explicitly:

SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt sops -d bookstack/.env.enc

Decrypt fails with "no private key found for recipient"

You are on a machine that is not a key holder. Only Nexus and Atlas hold private keys. Transfer the file to one of those machines to decrypt it.

Verify the public key in .sops.yaml matches the private key on this machine

age-keygen -y ~/.config/sops/age/keys.txt
# Output should match one of the public keys in .sops.yaml

Never commit a plaintext .env

The .gitignore in homelab-docker blocks **/.env and komodo/compose.env. Run git status before committing and verify no plaintext env files appear as staged.