Initializing Enclave...

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.json has a stale auths block (or no credHelpers entry) for your ECR registry, so Docker never invokes docker-credential-ecr-login and falls back to nonexistent static credentials.
  • How to fix it: Remove the conflicting auths entry, install amazon-ecr-credential-helper, and add a correctly scoped credHelpers key pointing to ecr-login.
  • Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your ~/.docker/config.json and 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:

  1. amazon-ecr-credential-helper (docker-credential-ecr-login) is not installed or not on $PATH.
  2. A legacy auths block in ~/.docker/config.json takes precedence over the credHelpers entry, causing Docker to attempt static Basic Auth with empty or expired tokens.
  3. AWS CLI v2 was installed but the old v1 aws ecr get-login workflow left a hardcoded token in auths that 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):

  • credsStore applies to ALL registries. If docker-credential-ecr-login is called for Docker Hub, it will fail and break non-ECR pulls.
  • credHelpers is 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.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →