So I was setting up a fresh CI/CD pipeline on a new Ubuntu server at like 11pm, everything's going smoothly, and then Docker just... refuses to pull an image. No real explanation, just this cryptic little error staring back at me:
Error saving credentials: error storing credentials - err: exit status 1,
out: `error getting credentials - err: exit status 1,
out: \`pass not initialized\``
Or sometimes it shows up like this when you try to docker login:
error getting credentials - err: exit status 127, out: ``
I've seen this a hundred times across different machines — bare metal servers, fresh VMs, GitHub Actions runners, you name it. The root cause almost always traces back to Docker's ~/.docker/config.json file pointing to a credential store that either doesn't exist, isn't installed, or just quietly broke at some point.
Why this actually happens
Docker tries to be helpful by offloading credential storage to your OS's native secret manager — things like docker-credential-desktop on Mac (tied to Keychain), docker-credential-wincred on Windows, or docker-credential-pass on Linux. When you install Docker Desktop, it writes a credsStore entry into your config.json automatically.
The problem? When you later move that config to a different machine, run Docker in a container or CI environment, uninstall Docker Desktop, or install Docker Engine standalone on a server — that credential helper is suddenly gone. But the config file still has the reference. Docker looks for the binary, can't find it, and throws a fit.
Here's what a broken config typically looks like:
cat ~/.docker/config.json
{
"credsStore": "desktop",
"auths": {}
}
That "credsStore": "desktop" is the culprit. It's expecting docker-credential-desktop to be on your PATH, and it's not.
Fix #1 — Just remove the credsStore entry (fastest fix)
Honestly, if you're on a headless Linux server and you don't need fancy keychain integration, just pull that entry out. Docker will fall back to storing credentials as base64 in the config file itself — not the most secure thing in the world, but totally fine for most dev/staging environments.
nano ~/.docker/config.json
Edit it to look like this:
{
"auths": {}
}
Save it, then run docker login again. Nine times out of ten, this just works. If you want to do it without opening an editor:
cat ~/.docker/config.json | python3 -c "import sys,json; d=json.load(sys.stdin); d.pop('credsStore',None); d.pop('credHelpers',None); print(json.dumps(d,indent=2))" > /tmp/docker_config_tmp.json && mv /tmp/docker_config_tmp.json ~/.docker/config.json
A bit verbose but it gets the job done without you accidentally breaking the JSON manually.
Fix #2 — Install the missing credential helper
If your config references pass specifically, you might actually want to keep using it — especially for a shared server where multiple people are pushing/pulling from private registries. In that case, install the helper instead of removing the reference.
For docker-credential-pass on Linux:
sudo apt-get install pass gpg -y
# Download the credential helper
VERSION=0.8.1
curl -fsSL "https://github.com/docker/docker-credential-helpers/releases/download/v${VERSION}/docker-credential-pass-v${VERSION}.linux-amd64" -o /usr/local/bin/docker-credential-pass
chmod +x /usr/local/bin/docker-credential-pass
Then initialize pass with a GPG key — this is where people usually get stuck. You need an actual GPG key set up first:
gpg --gen-key
gpg --list-keys # grab the key ID from here
pass init YOUR_GPG_KEY_ID
Now your config.json should have "credsStore": "pass" and it'll actually work. This approach is more work upfront, but it means your credentials aren't just sitting as base64 in a text file.
Fix #3 — Use a credHelpers override for specific registries
Here's the thing though — sometimes you don't want a global credential store change. Maybe you're logging into multiple registries (Docker Hub, ECR, GCR) and only one is causing issues. You can use credHelpers to set per-registry helpers instead of a global credsStore.
{
"auths": {},
"credHelpers": {
"public.ecr.aws": "ecr-login",
"gcr.io": "gcloud",
"index.docker.io": ""
}
}
Setting a registry's helper to an empty string "" tells Docker to use plain auth for that one. This is super useful in CI environments where you're mixing cloud registries with Docker Hub and don't want the global config to apply everywhere.
(Side note — if you're on AWS and using ECR, install amazon-ecr-credential-helper and set it up properly rather than fighting with tokens expiring. Future you will thank you.)
Fallback tip if nothing works
If you've tried all of this and Docker is still complaining, just nuke the config and start fresh:
mv ~/.docker/config.json ~/.docker/config.json.bak
mkdir -p ~/.docker
echo '{}' > ~/.docker/config.json
docker login
I know it feels drastic, but sometimes the config file just gets into a weird state — especially if you've had multiple versions of Docker installed on the same machine over the years. Starting clean takes 30 seconds and usually sorts it out immediately.
In my experience, the vast majority of docker config.json credential store errors on Linux servers come from someone copying a config file over from their Mac where Docker Desktop wrote that credsStore: desktop entry. Just something to keep in mind if you're onboarding a new team member or spinning up from an image snapshot.
Hope this saves you the 45 minutes of Googling I've definitely never wasted on this exact problem.
Related: Docker Error Getting Credentials — Here's What's Actually Breaking It
๋๊ธ
๋๊ธ ์ฐ๊ธฐ