Secrets Management for Homelabs: Redaction, Scanning, and Rehydration
The moment you push a config file to a public Git repo, every password, API key, and token in it is compromised. Even if you delete the commit, it lives in Git history forever. My homelab backup repo is public — and it's safe — because every secret passes through a three-stage lifecycle: redact, scan, rehydrate.
The Secrets Inventory
My homelab manages secrets across 9 services. Here's what gets redacted:
| Service | Secrets | Location |
|---|---|---|
| Plex | OnlineToken, Username, Mail | Preferences.xml |
| Pi-hole | Admin session token | pihole.toml |
| Tautulli | API key, admin password | config.ini |
| Homepage | Widget API keys (Portainer, Tautulli, Traefik, Glances) | services.yaml |
| Traefik | Wildcard TLS cert + private key | tls.yaml, certs/ |
| Arr Stack | Prowlarr/Radarr/Sonarr API keys | config.xml |
| Gluetun | WireGuard private key | docker-compose.yml |
| Immich | PostgreSQL password | .env (not tracked) |
| Home Assistant | Long-lived access tokens | Runtime only |
Stage 1: Automated Redaction
The redact-secrets.sh script uses sed to replace sensitive values with REDACTED. It targets two categories:
Generic Patterns
Case-insensitive matching for common secret field names:
password, passwd, api_key, api-key, token, secret, client_secret, passkey
Service-Specific Patterns
Targeted replacements for known config formats:
PlexOnlineToken="..."→PlexOnlineToken="REDACTED"PlexOnlineUsername="..."→PlexOnlineUsername="REDACTED"- Portainer tokens (
ptr_*) - Tailscale keys (
tskey-api-*) WIREGUARD_PRIVATE_KEY: ...→WIREGUARD_PRIVATE_KEY: REDACTED
Stage 2: Leak Scanning Gate
Even with automated redaction, new services or config format changes can introduce secrets the patterns don't catch. The scan-secrets.sh script is the safety net:
grep -RInE '(password|api_?key|token|secret)[[:space:]]*[:=][[:space:]]*[^#[:space:]]+' configs/
If any match is found, the script exits with code 1 and the backup pipeline stops — no commit, no push. This has caught secrets from newly added services more than once.
Stage 3: Rehydration on Restore
When restoring from backup, every REDACTED placeholder must be replaced with real values. The workflow:
- Create
~/.homelab-secrets.envwith all actual values (chmod 600, never in Git) - Source the file:
source ~/.homelab-secrets.env - Run
sedcommands to inject each secret into its config file - Verify:
grep -r "REDACTED" configs/— must return nothing
The SECRETS.md catalog documents each secret with:
- Exact file location and config key
- Format validation (regex patterns, length requirements)
- How to source or regenerate the value
- Rotation/renewal procedures
- Masked examples
Rotation Practices
- API keys: Rotate quarterly or after suspected compromise
- Passwords: Rotate annually
- TLS certificates: Regenerate before expiry (check with
openssl x509 -enddate) - VPN keys: Rotate immediately if exposed, then on regular cadence
Security Rules I Follow
- Never commit unredacted secrets to Git — always run
scan-secrets.shbefore pushing manual changes - Store
.env.secretslocally only — file permissions600, added to.gitignore - Test service connectivity after rehydration — check logs for auth errors
- Validate secrets after injection —
grep -r "REDACTED" configs/must return empty
What I Learned
- Public repos force good habits. If my repo were private, I'd probably get lazy about redaction. Making it public means the automation must work.
- Document every secret. When it's 2 AM and you're restoring from backup, you don't want to figure out where each API key comes from.
- Two layers always. Redaction handles the expected cases; scanning catches the edge cases.
- Treat secrets as cattle, not pets. If a key is compromised, rotate it. Don't try to figure out if it was "really" leaked.
Back to the series: Start Here · Backup Automation · Docker Compose Stack · Disaster Recovery