What this error means
You tried to pull or push an image and the registry’s token service refused your request. The token you presented is valid, but it lacks the required scope (permissions) for the repository or action (pull, push).
Typical message:
- insufficient_scope: authorization failed
Root causes:
- Not logged in to the correct registry host
- Token lacks repository scope (e.g., pull or push)
- Wrong repository path/namespace
- Repository doesn’t exist or is private and you lack access
- Cloud registry login flow not used (ECR, GHCR, ACR, GCR)
Quickstart: the fast fixes
- Ensure you’re logging into the exact registry host:
# Docker Hub
docker login docker.io
# GitHub Container Registry
docker login ghcr.io
# AWS ECR (example region us-east-1)
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
# Azure Container Registry
az acr login --name acmeacr
- Use a token with the right scopes:
- Docker Hub: Personal Access Token for CLI, repository access granted
- GHCR: PAT with read:packages (pull) and write:packages (push), SSO enabled if org requires it
- ECR: IAM permissions ecr:GetAuthorizationToken, ecr:BatchGetImage, ecr:PutImage, etc.
- ACR: Role assignment (AcrPull/AcrPush)
- Reference the correct repository name and tag:
docker pull ghcr.io/acmeinc/web:1.0.0
If the above still fails, follow the steps below.
Minimal working example (Docker Hub private repo)
Scenario: Push a private image to docker.io/acmeinc/private-app:1.0.0
# 1) Authenticate with a Docker Hub PAT (not your account password)
docker login docker.io -u acmeci # will prompt for PAT
# 2) Build locally
docker build -t docker.io/acmeinc/private-app:1.0.0 .
# 3) Push
docker push docker.io/acmeinc/private-app:1.0.0
# 4) Pull (from another machine or after logout)
docker logout docker.io
docker login docker.io -u acmeci # PAT again
docker pull docker.io/acmeinc/private-app:1.0.0
If you see insufficient_scope at step 3 or 4, your token lacks push or pull scope for acmeinc/private-app, or you’re not hitting the right registry host.
Step-by-step troubleshooting
- Confirm the image reference
- Use the fully qualified name: registry/namespace/name:tag
- Examples:
- docker.io/acmeinc/private-app:1.0.0
- ghcr.io/acmeinc/web:1.0.0
- 123456789012.dkr.ecr.us-east-1.amazonaws.com/api:latest
- Ensure repository exists and you have access
- For new repos, push once to create them (some registries auto-create on push; others require creating beforehand).
- Re-login to the correct host
docker logout ghcr.io || true
docker login ghcr.io
- Inspect the auth challenge (optional but precise)
# Ask for a manifest; registry responds 401 with WWW-Authenticate
curl -sI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
https://registry-1.docker.io/v2/acmeinc/private-app/manifests/1.0.0
Look for WWW-Authenticate: Bearer realm=..., service=..., scope=repository:acmeinc/private-app:pull. If your token lacks that scope (pull) or push when pushing, you’ll get insufficient_scope.
- Use the registry-native login flow
- Docker Hub: PAT only for CLI
- GHCR: docker login ghcr.io with PAT that has read:packages/write:packages and is SSO-enabled for orgs
- ECR: use aws ecr get-login-password; ensure region matches the repo
- ACR: az acr login or docker login with admin user or service principal
- Verify Docker credential store
cat ~/.docker/config.json
- Check auths and credHelpers entries for your registry host. Misconfigured credential helpers can cause stale or missing tokens.
- Retry with a fresh token
- Rotate PATs or re-run cloud CLI login to refresh short-lived tokens (ECR tokens expire after 12 hours).
- Test a pull-only operation
docker pull ghcr.io/acmeinc/web:1.0.0
If pull works but push fails, you’re missing push scope/role.
Registry-specific quick reference
| Registry | Login command | Required permissions for pull/push |
|---|---|---|
| Docker Hub | docker login docker.io | Repo access; use a Personal Access Token for CLI |
| GHCR | docker login ghcr.io | read:packages (pull), write:packages (push), org SSO enabled |
| AWS ECR | aws ecr get-login-password | IAM: ecr:GetAuthorizationToken, ecr:BatchGetImage, ecr:PutImage, ecr:InitiateLayerUpload |
| Azure ACR | az acr login --name NAME | ACR roles: AcrPull, AcrPush |
| GCR/Artifact Registry | gcloud auth configure-docker | IAM roles: Artifact Registry Reader/Writer (or GCR equivalents) |
CI examples
GitHub Actions pulling from GHCR:
name: ci
on: [push]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
run: |
docker build -t ghcr.io/acmeinc/web:${{ github.sha }} .
docker push ghcr.io/acmeinc/web:${{ github.sha }}
ECR in a CI runner (region us-east-1):
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
docker pull 123456789012.dkr.ecr.us-east-1.amazonaws.com/api:latest
Common pitfalls
- Using account password instead of a PAT where required
- Logging into the wrong host (docker.io vs ghcr.io vs ECR hostname)
- Registry SSO not authorized for your token (GHCR orgs)
- ECR region mismatch or token expired
- Wrong repository path (missing org/namespace)
- Assuming anonymous pull for a private repo
- Stale credentials in credential helper; fix by re-login or clearing entries
Performance and reliability notes
- Avoid logging in for every build step. Log in once per job and reuse Docker’s credential store.
- Use registry-native credential helpers (gcloud, ECR, ACR) to obtain short-lived tokens automatically.
- Prefer fully qualified image names to prevent accidental pulls from the wrong registry.
- Minimize permission scope: pull-only for deployment nodes; push only in build jobs.
- Cache base images in your registry to reduce cross-registry pulls and auth round-trips.
Tiny FAQ
What causes insufficient_scope?
- Your token is valid but lacks the repository scope (pull or push) requested by the registry.
How do I see what scope is required?
- Send a manifest request with curl and inspect the WWW-Authenticate header. It contains the scope and service the registry expects.
Why does pull work but push fails?
- Your token has pull only. Ensure write:packages (GHCR), AcrPush (ACR), or ecr:PutImage (ECR) permissions.
Do I need to log in for private base images in Dockerfiles?
- Yes. Ensure the builder has credentials before the FROM step (BuildKit build secrets/registry auth).
How do I reset bad credentials?
- Run docker logout <registry> or remove the entry from ~/.docker/config.json, then log in again.