Overview
The error "no basic auth credentials" means the Docker client does not have usable credentials for the registry you’re pushing to. Common causes:
- You didn’t run docker login for that registry.
- The image is tagged for a different registry than you expect.
- The registry hostname/port in your tag doesn’t match what’s in ~/.docker/config.json.
- A credential helper or creds store is misconfigured or inaccessible (especially in CI).
This guide shows quick fixes for Docker Hub, GHCR, ECR, GCR/AR, ACR, and private registries, plus CI examples.
Quickstart (most cases)
- Identify the exact registry host you’re pushing to.
- Login to that host with docker login (use --password-stdin in CI).
- Ensure the image tag includes the same registry host.
- Push again.
Minimal working example
# Example: push busybox to Docker Hub under user/repo:1.0
export DOCKERHUB_USER="youruser"
export DOCKERHUB_TOKEN="your-token-or-password"
export IMAGE="${DOCKERHUB_USER}/demo:1.0"
# 1) Login (writes credentials to ~/.docker/config.json)
printf "%s" "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin docker.io
# 2) Tag with the exact registry host (docker.io implied, but explicit is safest)
docker pull busybox:latest
docker tag busybox:latest docker.io/$IMAGE
# 3) Push
docker push docker.io/$IMAGE
How Docker decides credentials
Docker reads credentials from ~/.docker/config.json or the path in $DOCKER_CONFIG. Entries can be:
- auths: base64-encoded credentials per registry.
- credHelpers: per-registry credential helper (e.g., ghcr.io -> "ghcr").
- credsStore: default OS-wide credential helper (osxkeychain, wincred, secretservice, pass).
Example config.json snippet:
{
"auths": {
"https://index.docker.io/v1/": {},
"ghcr.io": {}
},
"credHelpers": {
"ghcr.io": "ghcr"
},
"credsStore": "osxkeychain"
}
If the registry in your tag doesn’t match a key here, Docker can’t fetch credentials and you’ll see the error.
Step-by-step fix
Check your tag is fully qualified.
- If you omit a registry, Docker assumes Docker Hub (docker.io). Example: repo:tag => docker.io/library/repo:tag (or user/repo:tag when you own it).
- To push elsewhere, tag with the full host (e.g., ghcr.io/USER/repo:tag, 123456789012.dkr.ecr.us-east-1.amazonaws.com/repo:tag).
Login to the same host you’re tagging.
- docker login <registry>. Use --password-stdin for security.
Verify config path and contents.
- echo ${DOCKER_CONFIG:-$HOME/.docker}
- cat ${DOCKER_CONFIG:-$HOME/.docker}/config.json
- Ensure an auths/credHelpers entry exists for your registry host (exact hostname and port).
Re-tag and push.
- docker tag local:tag <registry>/<namespace>/<repo>:<tag>
- docker push <same-exact-reference>
If still failing, clear and re-authenticate.
- docker logout <registry>
- Remove stale entries from config.json and login again.
Registry-specific commands
- Docker Hub
printf "%s" "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin docker.io
- GitHub Container Registry (GHCR)
# PAT must have: read:packages, write:packages (and delete:packages if needed)
printf "%s" "$GHCR_PAT" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin
- Amazon ECR
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
- Google Artifact Registry / GCR
# Artifact Registry example (preferred over GCR)
gcloud auth print-access-token \
| docker login -u oauth2accesstoken --password-stdin \
us-central1-docker.pkg.dev
# GCR example
gcloud auth print-access-token \
| docker login -u oauth2accesstoken --password-stdin \
gcr.io
- Azure Container Registry (ACR)
# If admin user enabled (for quick tests)
printf "%s" "$ACR_PASSWORD" | docker login "$ACR_NAME".azurecr.io -u "$ACR_USERNAME" --password-stdin
# Or via Azure CLI (tokens/managed identities preferred)
az acr login --name "$ACR_NAME"
- Private/self-hosted registry
# Use the exact host:port you configured (default 5000)
printf "%s" "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin registry.example.com:5000
CI/CD examples
- GitHub Actions
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_PAT }}
- run: |
docker buildx build -t ghcr.io/${{ github.repository_owner }}/app:latest --push .
- GitLab CI
build:
image: docker:24
services: [docker:24-dind]
variables:
DOCKER_TLS_CERTDIR: ""
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:latest" .
- docker push "$CI_REGISTRY_IMAGE:latest"
- Generic shell runner
export DOCKER_CONFIG="$PWD/.docker"
mkdir -p "$DOCKER_CONFIG"
printf "%s" "$PASSWORD" | docker login -u "$USERNAME" --password-stdin registry.example.com
# Ensure tags match the registry
Common pitfalls
- Wrong registry in tag: pushing myorg/app:latest defaults to Docker Hub, not GHCR/ECR.
- Missing namespace: some registries require user/org prefix (e.g., ghcr.io/USER/app).
- Credential helper mismatch: credHelpers/credsStore set but helper binary not present in CI.
- Running docker with sudo uses root’s config at /root/.docker, not your user’s. Either avoid sudo or set DOCKER_CONFIG.
- PAT scope issues (GHCR needs write:packages; GCP uses oauth2accesstoken; ECR uses AWS-generated token).
- Multiple accounts: credentials for the host exist but belong to a different user/org without push rights.
- Stale or corrupted config.json: remove offending entries and re-login.
Performance notes
- Use BuildKit/buildx with --push to stream layers as they are built, reducing round-trips:
docker buildx build --push -t ghcr.io/USER/app:latest .
- Minimize layer churn to avoid re-pushing large layers; keep Dockerfile layers stable.
- Use .dockerignore to shrink context size; smaller contexts mean fewer/lighter layers to upload.
- Enable cache export/import to reuse layers across CI runs:
docker buildx build \
--cache-to=type=registry,ref=ghcr.io/USER/app:cache,mode=max \
--cache-from=type=registry,ref=ghcr.io/USER/app:cache \
--push -t ghcr.io/USER/app:latest .
- Push only necessary tags (avoid pushing both latest and a large set of unique tags unless needed).
Troubleshooting checklist
- docker info | grep Username shows your Docker Hub login when applicable.
- docker logout <registry> then login again with the correct host.
- Confirm the image reference you push matches the login host byte-for-byte.
- Inspect ~/.docker/config.json for matching auths/credHelpers entries.
- In CI, print echo "$DOCKER_CONFIG" and ls "$DOCKER_CONFIG" to ensure the right path is used.
FAQ
Why does it work locally but not in CI? Your local machine uses an OS credential helper. In CI, that helper may not exist. Use docker login with --password-stdin and DOCKER_CONFIG set to a writable path.
Do I need to base64-encode credentials manually? No. docker login writes the proper format or stores credentials via helpers. Don’t edit auth strings by hand.
Can I avoid storing plaintext passwords? Yes. Use tokens/PATs (GHCR, ACR), aws ecr get-login-password (ECR), or gcloud access tokens (GCR/AR). Prefer short-lived tokens in CI.
Why do I still get auth errors after login? Usually the tag registry doesn’t match the login host, or your account lacks push permission to the repo/namespace.