Chirag's Blog

Managing multiple Docker Hub accounts using docker-use

May 25, 2026

docker-use banner

Most of the time I'm signed into my work Docker Hub account, and that's fine. Almost everything I build ends up there. Then one weekend I was playing around with openruntimes/orchestrator, an orchestration service for running containerized workloads with callbacks. I had a PR open with some small tweaks, and I wanted to publish release-candidate images so I could pull them down and actually test end-to-end.

I didn't want those RC images sitting in my work namespace. So for the first time, I actually needed to switch Docker Hub accounts on my machine, and there isn't a good way to do it. The official answer is docker logout, then docker login again. Docker does respect a DOCKER_CONFIG environment variable that points at a config directory, but you still have to set it yourself every time.

A few years ago I'd have spent the afternoon hunting for someone else's tool. In the age of AI, you can just build the one you wished existed. So I did.

The result is docker-use.

The idea

The fix is small. Docker already respects the DOCKER_CONFIG environment variable, and whatever directory it points at is treated as the active config. So the whole mechanism is:

  1. Give every account its own config directory.
  2. Let the shell flip DOCKER_CONFIG between them by name.

No patches to Docker, no daemon involvement, no new credential store. A directory per account, plus a shell function to switch between them.

What it looks like

add shells out to a normal docker login, so 2FA and access tokens behave the way you already expect.

How it works

There are three pieces.

Each account lives in its own config directory, at ~/.docker-accounts/<name>/config.json. Names are validated against ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$ so they can't escape the directory or surprise the shell.

The trickier piece is a shell wrapper that mutates the parent shell. A child process can't change its parent's environment variables, so the binary on its own can't make DOCKER_CONFIG stick in your shell. The workaround is a shell function, generated by docker-use init zsh, that you source in your rc file:

The wrapper calls the binary, reads the printed config path, and assigns it to DOCKER_CONFIG in the current shell. It's a plain assignment, not an eval on the path itself, so a weird config name can't smuggle anything into your shell. zsh, bash, and fish all work.

The last piece is keeping Docker's credential helpers in place. When docker login finishes, the resulting config.json often contains "credsStore": "osxkeychain" (or secretservice on Linux). If you wipe that field, Docker Desktop's keychain integration breaks and the next push prompts you for a password. So during add, docker-use reads the config Docker just wrote and preserves the credential helper settings.

Things I'd flag if you read the source

A few decisions that weren't obvious going in.

The shell wrapper assigns the config path directly instead of eval-ing whatever the binary prints. Eval-ing user-controllable output tends to age badly.

Account names get validated before any filesystem operation runs. docker-use add ../etc -u … is not a debugging session I want to have.

remove asks for confirmation before deleting. It's a destructive action on credentials, so the extra keystroke is worth it.

Install

Then drop this into your ~/.zshrc (or ~/.bashrc, or ~/.config/fish/config.fish):

Binaries for other platforms are on the releases page.

It's a small tool for a small problem, but the kind of friction you live with for years before noticing you don't have to.

If you end up using it, I'd love to hear what works and what doesn't. Open an issue or reach out directly.