April 8, 2019

Using Github with Multiple Accounts

At my current project, I need to use a different Github account from my personal one. To improve my workflow, I had to find a way to easily switch between both accounts. I explored a few options until settling with, in my opinion, the most convenient solution.

The base for every solution I found was how to select between two different SSH keys associated with the different accounts.

Let’s pretend I’m working for client Foo. Right now, on my work laptop, I have two SSH keys:

  • ~/.ssh/id_rsa: my own private key
  • ~/.ssh/id_rsa_foo: the private key I generated for Foo’s repos in their enterprise Github

The problem is, given a cloned repo I’m working on, how do I make sure I’m using the correct key whether it is a personal repo or a work repo.

For any solution to work well, you might need to run

ssh-add  ~/.ssh/id_rsa

and

ssh-add ~/.ssh/id_rsa_foo

to have both keys in your SSH agent.

First Solution: Custom Host Name in SSH Config

One of the first solutions I came across a while ago, was adding a block in my ~/.ssh/config with a host alias for Github.com associating a different identity file with it. For example, adding

Host foo
    HostName github.com
    User kaoatfoo
    IdentityFile ~/.ssh/id_rsa_foo

to my SSH config, will make it so that I can replace git@github.com:foo-corp/some-foo-repo.git with git@foo:foo-corp/some-foo-repo.git to select the correct identity when working at a Foo repository. This solution is mostly fine and you could go it the other way around and create an alias for your personal repos instead, if it’s more convenient for you.

In my case, I want to alias my work repos because I want my personal Github experience to be as friction-less as possible. However, I faced an issue.

In my project, we work with Terraform and use modules stored in private repos in the Foo enterprise account. If I want to apply some of this code locally, Terraform cannot access the modules because the host name in the Terraform code is not aliased:

module "bar_service" {
    source = "git@github.com:foo-corp/service.git"
    
    ...
}

Of course using a host name alias in the code would require every team member to share the exact same SSH config, which adds extra friction for every team member and I definitely don’t want that just to cater for my own personal setup.

That kept me looking for a better solution.

Second Solution: The GIT_SSH_COMMAND Environment Variable

After some googling around, I came across the GIT_SSH_COMMAND environment variable. This allows you to customize the command Git uses for executing SSH and it is particularly handy because you can use

GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_foo'

to tell Git to use your work identity in a given shell. The first way I used this, was creating a fgit (as in Foo Git) alias in my shell

alias fgit="GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_foo'"

so I could run fgit commit, fgit push, etc when working on a Foo repo. This was reasonable, but still had the limitation with Terraform modules, since Terraform is not aware of my fgit alias. For that reason, I ended up creating toggle aliases

alias fgit-on="export GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_foo'"
alias fgit-off="unset GIT_SSH_COMMAND"

I also added a check for the environment variable in my shell prompt that would print something like [foo-git-on] in red so I would have a visual reminder that I had this on or off.

This has proven to be quite OK. I would forget to toggle it on occasionally, but the access denied error in git was a reliable reminder.

I still longed for a more convenient setup, though, and ended up stumbling upon a tool that helped me solve this.

Current Solution: Environment Variables and direnv

I stumbled upon direnv at some point and it became apparent on how it could help me with my env var setup for different Git identities.

How direnv works

Once you hook it up into your shell, direnv will look for a .envrc file at the current and parent directories of any directory you visit in your shell.

When it first encounters a new .direnv file, it won’t execute it before you direnv allow it. This is to make sure you check the file contents beforehand, since .envrc files can execute arbitrary shell code, not just exporting environment variables. It keeps track of the hash of the file too, so If the content changes you need to allow it again.

After being allowed, direnv will execute a .envrc present in the current directory hierarchy, keeping track of environment changes done along the way, and reverting them back when you navigate away from the hierarchy where your .envrc is present.

My setup

All my Foo repos like in a folder like ~/Work/Foo/ in my work laptop. I created a .envrc file at this folder with the environment variable I was using on my aliases.

export GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_foo'

At some point I also realized I overlooked another aspect of my setup which was the author email in my commits. Github associates a user account with a commit based on the primary email of that account matching the author email on the commit.

Since I had my personal email on my global gitconfig, that meant some commits I made at Foo showed up associated with my personal Github account on the history in Github, instead of my work account. Oops…

The solution for that was adding the GIT_AUTHOR_EMAIL and GIT_COMMITER_EMAIL environment variable to my .envrc, making it look like

export GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_foo'
export GIT_AUTHOR_EMAIL='workusername@workdomain.com'
export GIT_COMMITTER_EMAIL='workusername@workdomain.com'

I also use a direnv package for Emacs, which enables me to run Git and Terraform and also means that Magit works seamlessly for both work and personal repos.

So far I’m quite happy with my setup. The only tool that I rely on and that is not well integrated with this setup is IntelliJ. I couldn’t find a direnv plugin for IntelliJ, but I did come across a possible solution using a different plugin. I haven’t tested it yet, though, since I don’t use IntelliJ to run Git commands anyway and I have been using Emacs for editing Terraform as well.