Managing Multiple Git Accounts with direnv
2025-05-24
Working for multiple clients often requires maintaining separate GitHub accounts for each organisation. This means managing different SSH keys, commit signing configurations, and project-specific environment variables. Whilst you could manually switch configurations or maintain complex shell scripts, I believe there is a more elegant solution: direnv
.
If you are not familiar with direnv
then you are in for a treat! direnv
is a powerful shell extension that automatically loads and unloads environment variables based on your current directory. This makes it perfect for managing client-specific Git configurations whilst keeping everything organised and automated.
You will want direnv
installed to follow along, most package managers will get you there, on macos brew install direnv
does it. There are specific installation instructions here for other OSes.
⚠️ caveat emptor: I am assuming you are using git via a terminal. If you are not using git via a terminal your milage may vary depending on the tooling you are using.
The Problem
When working with multiple clients, you typically need:
- Different SSH keys for each GitHub account
- Separate Git user configurations (name and email)
- Commit signing setup for each account
- Project-specific environment variables
Managing these manually is error-prone and tedious. You might forget to switch configurations, accidentally commit with the wrong identity, or struggle to keep environment variables organised.
The Solution: Hierarchical Configuration with direnv
The key insight I want to share is to how to use direnv’s hierarchical configuration capabilities. By structuring your projects directory and using .envrc
files at different levels, you can create a system that automatically handles client-specific configurations whilst allowing for project-specific overrides.
Setting Up the Directory Structure
Start by organising your projects with a clear hierarchy:
~/projects/
├── acme/ # Client-specific directory
│ ├── .envrc # Client configuration
│ └── cyote/ # Project directory
│ └── .envrc # Project-specific variables
├── cyberdyne/ # Another client
│ ├── .envrc
│ └── skynet/
└── personal/ # Personal projects
└── .envrc
Creating the Client Configuration
For each client, create a .envrc
file in their directory. Here’s an example for our imaginary client “Acme”:
~/projects/acme/.envrc
export GIT_SSH_PRIVATE_KEY="~/.ssh/acmedev_ed25519"
export GIT_SSH_COMMAND="ssh -i ${GIT_SSH_PRIVATE_KEY} -F /dev/null"
export GIT_AUTHOR_NAME="Hector B. Sector"
export GIT_AUTHOR_EMAIL="[email protected]"
export GIT_COMMITTER_NAME="Hector B. Sector"
export GIT_COMMITTER_EMAIL="[email protected]"
# Find all git repositories up to 2 folders deep and configure SSH-based GPG signing
# (NOTE: this is zsh syntax, not bash!)
repos=()
while read -r gitdir; do
repos+=("$gitdir")
# Get the repository directory by removing the .git suffix
repo_dir=$(dirname "$gitdir")
# Navigate to the repository directory and configure in a subshell
(
cd "$repo_dir" &&
git config commit.gpgsign true &&
git config gpg.format ssh &&
git config user.signingkey "$GIT_SSH_PRIVATE_KEY"
)
done < <(find . -maxdepth 3 -name .git -type d)
echo "Configured git signing for ${#repos[@]} repos"
This configuration does several important things:
- Sets up SSH authentication with the client-specific private key
- Configures Git identity with the appropriate name and email
- Automatically configures commit signing for all repositories in the directory tree
- Uses SSH-based GPG signing which is simpler than traditional GPG keys
Activating the Configuration
When you first cd
into the client directory, direnv will warn you:
direnv: error /Users/hector/projects/acme/.envrc is blocked. Run direnv allow to approve its content
Run direnv allow
to approve and activate the configuration:
$ direnv allow
direnv: loading ~/projects/acme/.envrc
Configured git signing for 1 repos
direnv: export +GIT_AUTHOR_EMAIL +GIT_AUTHOR_NAME +GIT_COMMITTER_EMAIL +GIT_COMMITTER_NAME +GIT_SSH_COMMAND +GIT_SSH_PRIVATE_KEY
Now you can clone repositories and they’ll automatically use the correct SSH key and Git configuration:
$ git clone [email protected]:acme/cyote
Adding Project-Specific Variables
Often you’ll need project-specific environment variables for local development. Create a .envrc
file in the project directory:
~/projects/acme/cyote/.envrc
export ACME_API_TOKEN=123
After running direnv allow
, you might notice something concerning:
direnv: loading ~/projects/acme/cyote/.envrc
direnv: export +ACME_API_TOKEN
The Git-related exports are gone! This happens because direnv only loads the most specific .envrc
file by default.
The Magic of source_up
This is where direnv’s source_up
directive saves the day. Edit your project’s .envrc
:
~/projects/acme/cyote/.envrc
source_up
export ACME_API_TOKEN=123
Now direnv will walk up the directory tree, load the parent .envrc
first, then apply the project-specific variables:
direnv: loading ~/projects/acme/cyote/.envrc
direnv: loading ~/projects/acme/.envrc
Configured git signing for 1 repos
direnv: export +ACME_API_TOKEN +GIT_AUTHOR_EMAIL +GIT_AUTHOR_NAME +GIT_COMMITTER_EMAIL +GIT_COMMITTER_NAME +GIT_SSH_COMMAND +GIT_SSH_PRIVATE_KEY
Perfect! You now have both the client-specific Git configuration and project-specific environment variables.
Benefits of This Approach
This hierarchical configuration system provides several advantages:
- Automatic Context Switching: No more manually switching Git configurations or remembering to set environment variables.
- Reduced Errors: Impossible to accidentally commit with the wrong identity when the configuration is automatic.
- Clean Organisation: Each client’s configuration is isolated and easy to manage.
- Flexible Layering: Project-specific variables inherit from client configurations without duplication.
- Version Control Friendly:
.envrc
files can be committed to share configurations with your team but I would be very careful with this ⚠️ (see below).
Security Considerations
While this approach is convenient, keep security in mind:
- Never commit sensitive values like API tokens or private keys to version control
- Consider using tools like
sops
orage
for encrypting sensitive.envrc
files - Use
.envrc.local
files for truly sensitive values that should never be shared - Regularly rotate SSH keys and API tokens
Conclusion
Managing multiple Git accounts doesn’t have to be a hassle. With direnv’s hierarchical configuration system, you can create a seamless workflow that automatically handles client-specific Git settings while maintaining flexibility for project-specific needs.
The source_up
directive is the key ingredient that makes this system truly powerful, allowing you to layer configurations without repetition or manual intervention. Once set up, you can focus on coding instead of configuration management.
Give this approach a try for your multi-client workflow – your future self will thank you for the time saved and errors avoided!