Home Lessons LearnedDitching Cloud Secret Managers: Age Encryption in a Git Repo

Ditching Cloud Secret Managers: Age Encryption in a Git Repo

by Marc

Google Secret Manager costs money. Not much per secret, but when you run five side projects across four servers, and each has its own set of API keys, database passwords, and service tokens, the bill adds up — and so does the cognitive overhead of managing IAM permissions, service accounts, and secret versions across multiple GCP projects. So I replaced it with a private GitHub repo and age encryption.

The Problem

I had secrets scattered across three systems: Google Secret Manager for work-related projects, environment variables hardcoded in server dotfiles, and a few passwords in plain text files Id rather not think about. When a server died or I set up a new device, recreating the environment meant hunting through GCP consoles, SSH-ing into other machines to copy .env files, and hoping I hadnt forgotten anything.

The Solution: age + Git

age is a simple, modern encryption tool. One command to encrypt, one to decrypt. No config files, no key servers, no expired certificates. A single private key file is all you need. The idea is dead simple:

  1. Store all secrets in a single JSON file
  2. Encrypt it with age using your public key
  3. Commit the encrypted file to a private GitHub repo
  4. Decrypt on any device that has the private key

The encrypted secrets.age file is safe to store anywhere — GitHub, Google Drive, a USB stick. Without the private key, it is meaningless bytes.

The Setup

First, generate an age key pair (one time, on any device):

age-keygen -o ~/.config/age/key.txt
chmod 600 ~/.config/age/key.txt

The file contains both the public key (safe to share) and the private key (never leaves the device). Copy this file to every machine that needs to decrypt secrets — Mac, Raspberry Pi, cloud server, phone.

Create your secrets file:

# Create secrets JSON
cat > /tmp/secrets.json << EOF
{
  "my-api-key": "sk-abc123...",
  "db-password": "supersecret",
  "smtp-pass": "xxxx yyyy zzzz wwww"
}
EOF

# Encrypt with your public key
PUB=$(grep "public key" ~/.config/age/key.txt | awk {print })
age -r "$PUB" -o ~/secrets-repo/secrets.age /tmp/secrets.json
rm /tmp/secrets.json

Push to a private repo:

cd ~/secrets-repo
git add secrets.age
git commit -m "update secrets"
git push

The Helper Script

A tiny shell script makes retrieval seamless:

#!/bin/bash
# get-secret.sh — decrypt and extract a secret by name
SECRETS=$(age -d -i ~/.config/age/key.txt ~/secrets-repo/secrets.age)
if [ -z "$1" ]; then
    echo "$SECRETS" | python3 -c "import sys,json; [print(k) for k in sorted(json.load(sys.stdin))]"
else
    echo "$SECRETS" | python3 -c "import sys,json; print(json.load(sys.stdin).get(,))"
fi

Now any script on any server can pull a secret:

# List all secrets
~/secrets-repo/get-secret.sh

# Get a specific one
TOKEN=$(~/secrets-repo/get-secret.sh my-api-key)

# Use in a deploy script
export DB_PASSWORD=$(~/secrets-repo/get-secret.sh db-password)

Binary Files Too

Age encrypts any file, not just JSON. I use it for Android signing keystores, SSH keys, and anything else that needs to live across machines but should not be in plain text:

# Encrypt a keystore
age -r "$PUB" -o ~/secrets-repo/app.keystore.age ~/app.keystore

# Decrypt when needed
age -d -i ~/.config/age/key.txt ~/secrets-repo/app.keystore.age > /tmp/app.keystore

The Key Backup Problem

The obvious risk: if you lose the private key, you lose everything. My key lives on four devices (Mac, two Raspberry Pis, a cloud server) and in Google Passwords as a last resort. The key is three lines of text — it fits on a sticky note in a drawer if you want a truly offline backup.

57 Secrets, Zero Cloud Bills

I currently store 56 secrets across all my side projects: API keys, database passwords, SMTP credentials, signing keystores, OAuth tokens, Cloudflare tokens, Telegram bot tokens, and more. Adding a new secret takes 30 seconds. Syncing to a new server is one git clone and one scp of the key file.

The total cost: $0/month. The GitHub repo is private (free tier). age is open source. The only thing I pay for is the peace of mind that all my secrets are in one place, encrypted, versioned, and backed up.

You may also like

Leave a Comment