Last updated: March 20, 2026

git-crypt transparently encrypts files in a Git repository – they are encrypted at rest and in the remote, but automatically decrypted when you check them out on an unlocked machine. age handles the actual file encryption for workflows where git-crypt is not suitable: secret shares, offline archives, or encrypting entire directories before pushing. Use git-crypt for secrets that live alongside code (API keys, certs, .env files). Use age directly when you need to encrypt artifacts, share secrets outside the repo, or avoid GPG entirely.

Prerequisites

Before you begin, make sure you have the following ready:

Step 1 - Install git-crypt

macOS
brew install git-crypt

Ubuntu/Debian
sudo apt install git-crypt

Verify
git-crypt --version

Step 2 - Initialize git-crypt in a Repository

cd /path/to/your-repo

Initialize -- creates .git-crypt/ directory
git-crypt init

Export the symmetric key (back this up -- losing it means losing access)
git-crypt export-key ~/backup/git-crypt-key.bin
chmod 600 ~/backup/git-crypt-key.bin

Step 3 - Configure Which Files Get Encrypted

Edit or create .gitattributes in the repo root:

.gitattributes

Encrypt all .env files anywhere in the repo
/.env filter=git-crypt diff=git-crypt

Encrypt specific secret files
secrets/api-keys.json filter=git-crypt diff=git-crypt
config/production.yml filter=git-crypt diff=git-crypt

Encrypt entire directory
secrets/ filter=git-crypt diff=git-crypt

Encrypt .pem and .key files everywhere
*.pem filter=git-crypt diff=git-crypt
*.key filter=git-crypt diff=git-crypt

After adding the attributes, stage and commit the files:

git add .gitattributes secrets/api-keys.json
git commit -m "Add git-crypt encryption config"
git push

Verify files are encrypted in the remote
git show HEAD:secrets/api-keys.json  # Shows encrypted binary
git-crypt status                      # Shows which files are encrypted

Step 4 - Add Team Members via GPG Keys

git-crypt multi-user model uses GPG: each team member GPG public key can unlock the repo.

Each team member - generate or export their GPG public key
gpg --gen-key
gpg --export --armor alice@example.com > alice.pub

Repo admin - import the key and add them
gpg --import alice.pub
git-crypt add-gpg-user alice@example.com

Commit the key blob
git add .git-crypt/
git commit -m "Add alice as git-crypt collaborator"
git push

Alice then unlocks on her machine:

git clone git@github.com:org/repo.git
cd repo
git-crypt unlock  # Uses her GPG private key automatically
cat secrets/api-keys.json  # Now readable

Step 5 - GPG-Free Workflow: Use a Shared Key File

For teams that want to avoid GPG complexity, share the symmetric key via a secrets manager:

Repo admin - export key and store in 1Password / Vault / AWS Secrets Manager
git-crypt export-key - | base64 > /tmp/git-crypt-key.b64
Store this base64 string in your secrets manager

Team member - retrieve and unlock
your-secrets-tool get git-crypt-key | base64 -d > /tmp/git-crypt-key.bin
git-crypt unlock /tmp/git-crypt-key.bin
shred -u /tmp/git-crypt-key.bin

Step 6 - Configure CI/CD Integration

Store the git-crypt key as a base64 CI secret and unlock during the pipeline:

GitHub Actions
name: Deploy
on: [push]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install git-crypt
        run: sudo apt-get install -y git-crypt

      - name: Unlock secrets
        env:
          GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }}
        run: |
          echo "$GIT_CRYPT_KEY" | base64 -d > /tmp/git-crypt-key.bin
          git-crypt unlock /tmp/git-crypt-key.bin
          shred -u /tmp/git-crypt-key.bin

      - name: Deploy
        run: ./deploy.sh

For GitLab CI:

unlock_and_deploy:
  script:
    - apt-get install -y git-crypt
    - echo "$GIT_CRYPT_KEY" | base64 -d > /tmp/gckey.bin
    - git-crypt unlock /tmp/gckey.bin
    - shred -u /tmp/gckey.bin
    - ./deploy.sh

Step 7 - Encrypt Individual Files with age

For files you want to keep out of the repo entirely, encrypt them with age before committing:

Install age
brew install age  # macOS
or: go install filippo.io/age/cmd/age@latest

Generate a key pair
age-keygen -o ~/.age/key.txt
Public key printed to stdout - age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

Encrypt a secrets file
age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \
    -o secrets/prod-env.age \
    secrets/prod-env.txt

Add plaintext to .gitignore, commit only the encrypted version
echo "secrets/prod-env.txt" >> .gitignore
git add secrets/prod-env.age .gitignore
git commit -m "Add encrypted production secrets"

Decrypt
age -d -i ~/.age/key.txt -o secrets/prod-env.txt secrets/prod-env.age

Step 8 - Multi-Recipient age Encryption for Teams

Encrypt to multiple recipients – anyone with their private key can decrypt:

Collect team members public keys
alice: age1abc123...
bob:   age1def456...
ci:    age1ghi789...

age -r age1abc123... \
    -r age1def456... \
    -r age1ghi789... \
    -o secrets/prod-env.age \
    secrets/prod-env.txt

Decrypt (any recipient's key works)
age -d -i ~/.age/key.txt secrets/prod-env.age

Store all team public keys in a file for repeatability:

recipients.txt
age1abc123...  alice
age1def456...  bob
age1ghi789...  ci-runner

Encrypt to all recipients at once
age $(grep -v '^#' recipients.txt | sed 's/^/-r /') \
    -o secrets/prod-env.age secrets/prod-env.txt

Step 9 - Audit and Rotate Keys

List all git-crypt encrypted files
git-crypt status -e | head -20

For age - stop including the departing user's key in future encryptions
Re-encrypt existing files with new recipient list
age -r age1alice... -r age1bob... \
    -o secrets/prod-env.age \
    <(age -d -i ~/.age/key.txt secrets/prod-env.age.bak)

Rotate git-crypt key - remove user's key blob and reinitialize
rm -rf .git-crypt/keys/default/0/<removed-user-key-id>/
git-crypt init
All remaining users must re-add themselves

Step 10 - Preventing Accidental Plaintext Commits

The biggest failure mode for git-crypt is committing a secret file before adding the filter attribute to .gitattributes. Once the plaintext is in history, you need a history rewrite. expensive and disruptive. Add a pre-commit hook to catch this before it happens:

#!/usr/bin/env bash
.git/hooks/pre-commit. block commits of known secret files if git-crypt is not unlocked

Check if git-crypt is initialized
if [ ! -d ".git-crypt" ]; then
  exit 0  # No git-crypt, nothing to check
fi

Verify that encrypted files are listed in .gitattributes
STAGED=$(git diff --cached --name-only)

for file in $STAGED; do
  # Check if the file matches a git-crypt pattern
  if git check-attr filter "$file" | grep -q "git-crypt"; then
    # Verify the file is actually being stored encrypted
    if git-crypt status "$file" 2>/dev/null | grep -q "not encrypted"; then
      echo "ERROR: $file is staged but not encrypted by git-crypt."
      echo "Run: git-crypt unlock"
      exit 1
    fi
  fi
done

Install the hook in every clone with a setup script:

#!/usr/bin/env bash
scripts/setup-hooks.sh
cp scripts/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
echo "Git hooks installed."

Add ./scripts/setup-hooks.sh to your onboarding README so new team members run it on first clone.

Migrating Existing Secrets into git-crypt

If secrets are already in your repository history in plaintext, you need to purge them before adding encryption. The safest approach:

Step 1 - Rotate the actual secrets first. assume they are compromised
(change API keys, rotate certs, generate new passwords)

Step 2 - Remove the file from history using git-filter-repo
pip install git-filter-repo
git filter-repo --path secrets/api-keys.json --invert-paths

Step 3 - Add git-crypt attributes
echo 'secrets/api-keys.json filter=git-crypt diff=git-crypt' >> .gitattributes
git-crypt init
git-crypt export-key ~/backup/git-crypt-key.bin

Step 4 - Add the new (rotated) secret file and commit
git add .gitattributes secrets/api-keys.json
git commit -m "Encrypt api-keys.json with git-crypt"

Step 5 - Force push the rewritten history
git push --force-with-lease origin main

Alert all collaborators to re-clone after a history rewrite. Any stale clones can re-introduce the purged files if pushed again.

Step 11 - Use age-plugin-yubikey for Hardware Key Protection

For higher-assurance environments, the age-plugin-yubikey plugin lets you store age private keys on a YubiKey rather than on disk:

Install the plugin
cargo install age-plugin-yubikey

Initialize a new age identity on the YubiKey
age-plugin-yubikey --generate --slot 1
Outputs recipient - age1yubikey1q...

Encrypt to the YubiKey recipient
age -r age1yubikey1q... -o secrets/prod.age secrets/prod.txt

Decrypt. requires physical YubiKey touch
age -d -i age-plugin-yubikey: secrets/prod.age

YubiKey-backed age identities are particularly useful for repository administrators who hold the master encryption key. Day-to-day CI pipelines use the shared symmetric git-crypt key, while the YubiKey-secured age identity protects the key export itself.

Step 12 - Team Offboarding: Revoking Access Without Rekeying

When a team member leaves, git-crypt has no native revocation. you cannot simply remove their GPG key and have them locked out of future commits, because they already have a copy of the decrypted history. The correct procedure:

  1. Remove the departing user’s key blob from the repository:
Find their GPG key fingerprint
gpg --list-keys departing@example.com
FINGERPRINT - ABCD1234...

Remove their key blob
rm .git-crypt/keys/default/0/ABCD1234.gpg
git add -A
git commit -m "Remove departing user from git-crypt access"
  1. Rotate the symmetric key and all secrets it protects:
Export current encrypted files as plaintext
git-crypt unlock
cp secrets/api-keys.json /tmp/api-keys-backup.json

Re-initialize git-crypt with a new key
git-crypt init
git-crypt export-key ~/backup/git-crypt-key-v2.bin

Re-add remaining team members
git-crypt add-gpg-user alice@example.com
git-crypt add-gpg-user bob@example.com

Re-encrypt the files (they are already tracked by .gitattributes)
git add secrets/
git commit -m "Rekey git-crypt after team member departure"
  1. Distribute the new key to CI/CD systems and remaining team members.
  2. Rotate the actual secret values (API keys, passwords) as an independent step. assume the departing person has copies of the plaintext values regardless of repository access.

The combination of key rotation and secret rotation is the only complete offboarding. Repository access removal alone is insufficient.

Troubleshooting

Configuration changes not taking effect

Restart the relevant service or application after making changes. Some settings require a full system reboot. Verify the configuration file path is correct and the syntax is valid.

Permission denied errors

Run the command with sudo for system-level operations, or check that your user account has the necessary permissions. On macOS, you may need to grant terminal access in System Settings > Privacy & Security.

Connection or network-related failures

Check your internet connection and firewall settings. If using a VPN, try disconnecting temporarily to isolate the issue. Verify that the target server or service is accessible from your network.

Frequently Asked Questions

How long does it take to encrypt git repos with git-crypt and age?

For a straightforward setup, expect 30 minutes to 2 hours depending on your familiarity with the tools involved. Complex configurations with custom requirements may take longer. Having your credentials and environment ready before starting saves significant time.

What are the most common mistakes to avoid?

The most frequent issues are skipping prerequisite steps, using outdated package versions, and not reading error messages carefully. Follow the steps in order, verify each one works before moving on, and check the official documentation if something behaves unexpectedly.

Do I need prior experience to follow this guide?

Basic familiarity with the relevant tools and command line is helpful but not strictly required. Each step is explained with context. If you get stuck, the official documentation for each tool covers fundamentals that may fill in knowledge gaps.

Is this approach secure enough for production?

The patterns shown here follow standard practices, but production deployments need additional hardening. Add rate limiting, input validation, proper secret management, and monitoring before going live. Consider a security review if your application handles sensitive user data.

Where can I get help if I run into issues?

Start with the official documentation for each tool mentioned. Stack Overflow and GitHub Issues are good next steps for specific error messages. Community forums and Discord servers for the relevant tools often have active members who can help with setup problems.

Related Articles

Built by theluckystrike. More at zovo.one