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:

  1. Sets up SSH authentication with the client-specific private key
  2. Configures Git identity with the appropriate name and email
  3. Automatically configures commit signing for all repositories in the directory tree
  4. 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 or age 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!