Fix Docker ECR 'no basic auth credentials' Error After AWS CLI v2 Migration
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke:
~/.docker/config.jsonhas a staleauthsblock (or nocredHelpersentry) for your ECR registry, so Docker never invokesdocker-credential-ecr-loginand falls back to nonexistent static credentials. - How to fix it: Remove the conflicting
authsentry, installamazon-ecr-credential-helper, and add a correctly scopedcredHelperskey pointing toecr-login. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your
~/.docker/config.jsonand get a corrected config without leaking your account ID or registry URL to any third-party server.
The Incident (What Does the Error Mean?)
You run:
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-service:latest
And receive:
Using default tag: latest
The push refers to repository [123456789012.dkr.ecr.us-east-1.amazonaws.com/my-service]
erro: denied: Your authorization token has expired. Reauthenticate and try again.
-- OR --
error: no basic auth credentials
Immediate consequence: Every docker push and docker pull to ECR is dead. CI/CD pipelines fail at the image publish step. Deployments that depend on freshly built images cannot proceed. In a GitOps flow, this silently blocks rollouts without a clear alert unless your pipeline surfaces Docker exit codes.
The root cause is almost always one of three things:
amazon-ecr-credential-helper(docker-credential-ecr-login) is not installed or not on$PATH.- A legacy
authsblock in~/.docker/config.jsontakes precedence over thecredHelpersentry, causing Docker to attempt static Basic Auth with empty or expired tokens. - AWS CLI v2 was installed but the old v1
aws ecr get-loginworkflow left a hardcoded token inauthsthat has since expired (ECR tokens expire after 12 hours).
The Attack Vector / Blast Radius
This is not just a broken build — it is a latent credential hygiene failure with real blast radius:
Stale token exposure: The expired Base64-encoded token sitting in auths is still a credential artifact. Any process that reads ~/.docker/config.json — including misconfigured log shippers, container introspection tools, or a compromised CI runner — can extract it. Even expired, the token reveals your AWS Account ID and the ECR region.
Credential helper bypass: If credHelpers is absent and auths is present, Docker will never call docker-credential-ecr-login. This means IAM role-based ephemeral credentials are completely bypassed. You are now dependent on whatever static token was last written — which is exactly the credential model AWS ECR was designed to eliminate.
CI/CD runner scope: On a shared CI runner (GitHub Actions, GitLab Runner, Jenkins agent), a poisoned ~/.docker/config.json in the runner's home directory affects every job on that runner, not just yours. A single misconfigured node can silently break pushes across multiple teams and services.
Privilege escalation vector: If an attacker compromises a runner and finds a valid (non-expired) ECR token in auths, they can pull private images, inspect layers for embedded secrets, and push malicious tags — all without touching IAM.
How to Fix It
Step 0 — Verify the Binary Exists
which docker-credential-ecr-login
# Must return a path, e.g. /usr/local/bin/docker-credential-ecr-login
docker-credential-ecr-login version
# Must not return 'command not found'
If missing, install it:
# Linux (x86_64)
VERSION=0.8.0
curl -Lo /usr/local/bin/docker-credential-ecr-login \
https://amazon-ecr-credential-helper-releases.s3.amazonaws.com/${VERSION}/linux-amd64/docker-credential-ecr-login
chmod +x /usr/local/bin/docker-credential-ecr-login
Basic Fix — Repair ~/.docker/config.json
The most common broken state after an AWS CLI v1 → v2 migration:
{
- "auths": {
- "123456789012.dkr.ecr.us-east-1.amazonaws.com": {
- "auth": "QVdTOmV5SndZWGxzYjJG...(expired base64 token)"
- }
- },
- "credsStore": "desktop"
+ "credHelpers": {
+ "123456789012.dkr.ecr.us-east-1.amazonaws.com": "ecr-login",
+ "public.ecr.aws": "ecr-login"
+ }
}
Critical: The auths block for the ECR domain must be removed entirely. Docker evaluates auths before credHelpers for matching registries. A present-but-empty auths entry will still block the helper.
After editing, verify immediately:
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
# Expected: Login Succeeded
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-service:latest
Enterprise Best Practice — Scoped credHelpers + IAM Role, No Long-Lived Tokens
Do not use aws ecr get-login-password in CI at all if you can avoid it. Use instance roles, IRSA (EKS), or OIDC federation so docker-credential-ecr-login resolves credentials automatically from the environment.
# ~/.docker/config.json on the CI runner / EKS node
{
- "auths": {},
- "credsStore": "ecr-login"
+ "credHelpers": {
+ "123456789012.dkr.ecr.us-east-1.amazonaws.com": "ecr-login",
+ "987654321098.dkr.ecr.eu-west-1.amazonaws.com": "ecr-login",
+ "public.ecr.aws": "ecr-login"
+ }
}
Why credHelpers (scoped) over credsStore (global):
credsStoreapplies to ALL registries. Ifdocker-credential-ecr-loginis called for Docker Hub, it will fail and break non-ECR pulls.credHelpersis registry-domain-scoped. Surgical, predictable, auditable.
IAM policy minimum required on the runner role:
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
}
Plus repository-scoped actions (ecr:BatchCheckLayerAvailability, ecr:PutImage, etc.) locked to the specific repository ARN — not *.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Checkov — Scan for Hardcoded ECR Auth Tokens in Config Files
Add to your pre-commit or pipeline:
checkov -d . --check CKV_DOCKER_2
# Flags Dockerfiles and config artifacts with embedded credentials
2. OPA/Conftest Policy — Reject auths Blocks in docker config
# policy/docker_config.rego
package docker.config
deny[msg] {
input.auths[registry]
input.auths[registry].auth != ""
msg := sprintf("Hardcoded auth token found for registry '%v'. Use credHelpers instead.", [registry])
}
conftest test ~/.docker/config.json --policy policy/
3. GitHub Actions — Canonical ECR Login Pattern (No Manual Token Handling)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr-push
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
# This action handles token refresh and credHelpers injection automatically.
# Never use 'aws ecr get-login-password | docker login' in Actions — token is
# written to runner disk and persists across jobs.
4. Rotate and Audit Regularly
# Audit all runners for stale auths blocks
jq '.auths | keys[]' ~/.docker/config.json 2>/dev/null && \
echo "WARNING: Static auth entries present — migrate to credHelpers"
Add this check as a startup script on every CI runner AMI. Fail the runner bootstrap if auths contains non-empty entries for ECR domains.