Photo by Bernd ๐ท Dittrich on Unsplash
So I was setting up a CI/CD pipeline on a fresh Ubuntu server at some ungodly hour, ran docker pull to grab an image from a private registry, and got slapped with this:
Error saving credentials: error storing credentials - err: exec: "docker-credential-desktop": executable file not found in $PATH, out: ``
Or sometimes it shows up like this, depending on your setup:
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": unauthorized: incorrect username or password.
error storing credentials: error storing credentials - err: exec: "docker-credential-osxkeychain": executable file not found in $PATH
Either way, Docker's basically telling you it can't find the credential helper it was told to use. And honestly, this one trips people up constantly because the error message sounds more complicated than the actual fix.
Why This Happens
Docker stores login credentials using what it calls a "credential helper" — a small binary that handles the secure storage of your registry usernames and passwords. The config file at ~/.docker/config.json tells Docker which helper to use. The problem? That config file is pointing to a binary that either doesn't exist on your current machine, isn't in your PATH, or was set up for a completely different OS (super common when you copy configs between systems or Docker Desktop sets something and then gets uninstalled).
Open up your config file and you'll probably see something like this:
{
"credsStore": "desktop",
"auths": {}
}
That "credsStore": "desktop" line is the culprit. It's telling Docker to use docker-credential-desktop, which is a Docker Desktop thing — and if you're on a headless Linux server, that binary flat-out doesn't exist. Same deal if you see osxkeychain on a Linux box, or wincred anywhere that isn't Windows.
Fix 1: Remove or Change the credsStore Entry
This is the quickest fix and works in the majority of cases. Just edit your ~/.docker/config.json and either remove the credsStore line entirely or point it to a helper that actually exists on your system.
The nuclear option — just wipe the whole config and start fresh:
rm ~/.docker/config.json
Then log in again:
docker login
Docker will recreate the config file. On a server without a credential helper installed, it'll store credentials in base64 directly in the config (not ideal for production security, but fine for getting unblocked). If you're in a CI environment, honestly this is usually the fastest path forward.
If you don't want to nuke the whole file (maybe you have other stuff in there), just manually edit it:
nano ~/.docker/config.json
And remove the "credsStore" line, or change its value to an empty string:
{
"auths": {}
}
Fix 2: Install the Actual Credential Helper
If you want a proper credential store (and in most cases, you should — storing credentials as base64 in a plaintext file isn't great), install the right helper for your platform.
On Linux, docker-credential-pass is the go-to. Here's how to get it:
# Download the latest release (check GitHub for current version)
wget https://github.com/docker/docker-credential-helpers/releases/download/v0.8.0/docker-credential-pass-v0.8.0.linux-amd64
# Rename and move it
mv docker-credential-pass-v0.8.0.linux-amd64 docker-credential-pass
chmod +x docker-credential-pass
sudo mv docker-credential-pass /usr/local/bin/
Then set up pass (the password manager it relies on) and initialize it:
sudo apt install pass gpg -y
gpg --gen-key
pass init your-gpg-email@example.com
And update your Docker config to use it:
{
"credsStore": "pass"
}
Fair warning — the GPG key setup can be a whole thing on headless servers with no entropy. I've spent more time than I'd like to admit fighting gpg --gen-key hanging forever on a VM. If that happens, install haveged to speed up entropy generation: sudo apt install haveged -y. Annoying, but it works.
Fix 3: Use a Per-Registry Auth Token Instead
This one's underrated, especially for CI pipelines. Instead of relying on a credential helper at all, you can pass credentials directly using environment variables or configure Docker to use a registry token.
For something like GitHub Container Registry or Docker Hub in a pipeline, just do:
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
This pipes your credentials in without needing any credential store. The --password-stdin flag keeps your password out of your shell history too, which is a nice bonus. Set those environment variables in your CI secrets and you're good to go — no credential helper needed, no config file drama.
In my experience, this is the cleanest approach for automated environments. No helper binaries to install, no GPG setup, no PATH weirdness.
Fallback: Check What's Actually in Your PATH
If you've done all of the above and it's still complaining, do a quick sanity check. Whatever your credsStore value is, Docker is looking for a binary called docker-credential-[value]. Check if it exists:
which docker-credential-pass
# or
ls /usr/local/bin/ | grep docker-credential
If nothing comes back, the binary isn't there or it's not in your PATH. You can also run the helper directly to see what happens:
echo "https://index.docker.io/v1/" | docker-credential-pass get
That'll surface any underlying errors — maybe pass isn't initialized, maybe GPG is unhappy. The direct output is way more useful than what Docker shows you.
One more thing — if you're using Docker in a rootless setup or with sudo, remember that the config file location changes. Running sudo docker login reads from /root/.docker/config.json, not your user's home directory. I've seen this burn people who set everything up correctly under their user and then wondered why sudo docker pull still failed.
Hope this saves you the 45 minutes of Googling I spent the first time this bit me.
Related: Docker Error Getting Credentials — Here's What's Actually Breaking It
๋๊ธ
๋๊ธ ์ฐ๊ธฐ