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:
- Give every account its own config directory.
- Let the shell flip
DOCKER_CONFIGbetween 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
# one-time per account
docker-use add appwrite -u my-appwrite-username
docker-use add personal -u chiragagg5k
# day-to-day
docker-use appwrite
docker-use personal
docker push chiragagg5k/job-sidecar:temp-appwrite-pr-4f081ed-20260521-r2
docker-use whoami # which one am I on right now?
docker-use list # all accounts
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:
eval "$(docker-use init zsh)"
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
brew tap chiragagg5k/tools
brew install docker-use
Then drop this into your ~/.zshrc (or ~/.bashrc, or ~/.config/fish/config.fish):
eval "$(docker-use init zsh)"
Binaries for other platforms are on the releases page.
If you end up using it, I'd love to hear what works and what doesn't. Open an issue or reach out directly.




















