Home / AWS

AWSCheat sheetBeginner

Amazon ECR cheat sheet: login, push errors, lifecycle policies and cross-account pulls

Day-to-day Amazon ECR commands - logging in, pushing and pulling, decoding common errors, cleaning up with lifecycle policies and sharing images across accounts.

Deepak Bhardwaj5 min read

Applies toAmazon ECR private repositories, AWS CLI v2, Docker or any OCI-compatible client

When you need this

A pipeline fails with no basic auth credentials, a repository has grown to thousands of untagged images, or another account needs to pull your image. Below are the commands for each case. Set these once per shell:

ACCOUNT=111122223333
REGION=eu-west-1
REGISTRY=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com

Log in, push and pull

# Log in (the token lasts 12 hours)
aws ecr get-login-password --region $REGION | \
  docker login --username AWS --password-stdin $REGISTRY

# Create a repository with scan on push and immutable tags
aws ecr create-repository --repository-name payments/api --region $REGION \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE

# Tag and push
docker tag payments-api:1.4.2 $REGISTRY/payments/api:1.4.2
docker push $REGISTRY/payments/api:1.4.2

# Pull
docker pull $REGISTRY/payments/api:1.4.2

To build one image for both x86 and Graviton:

docker buildx build --platform linux/amd64,linux/arm64 \
  -t $REGISTRY/payments/api:1.4.2 --push .

Inventory

# Repositories and their settings
aws ecr describe-repositories \
  --query "repositories[].{name:repositoryName,mutability:imageTagMutability,scan:imageScanningConfiguration.scanOnPush}" \
  --output table

# Newest 10 images in a repository
aws ecr describe-images --repository-name payments/api \
  --query "reverse(sort_by(imageDetails,&imagePushedAt))[:10].{tags:imageTags[0],pushed:imagePushedAt,sizeMB:imageSizeInBytes}" \
  --output table

# Does a tag exist?
aws ecr describe-images --repository-name payments/api --image-ids imageTag=1.4.2

# Count untagged images
aws ecr list-images --repository-name payments/api --filter tagStatus=UNTAGGED \
  --query "length(imageIds)"

# Digest behind a tag (pin deployments to this)
aws ecr describe-images --repository-name payments/api --image-ids imageTag=1.4.2 \
  --query "imageDetails[0].imageDigest" --output text

Common errors

Error Cause Fix
no basic auth credentials Not logged in, token expired after 12 hours, or logged in to another Region's registry Run get-login-password again for the registry's Region
denied: ... not authorized to perform: ecr:InitiateLayerUpload Pushing principal lacks push actions Grant ecr:InitiateLayerUpload, ecr:UploadLayerPart, ecr:CompleteLayerUpload, ecr:PutImage, ecr:BatchCheckLayerAvailability
name unknown: The repository with name '...' does not exist Repository not created, typo, or wrong account/Region in the URI aws ecr create-repository, check the registry URI
tag invalid: The image tag '...' already exists ... immutable Repository has IMMUTABLE tags Push a new tag. Don't reuse version tags
manifest unknown / not found on pull Tag never pushed, or a lifecycle policy expired it describe-images --image-ids imageTag=..., check the lifecycle policy
exec format error when the container starts Image architecture doesn't match the node (amd64 vs arm64) Build multi-arch with buildx --platform
403 Forbidden from a cross-account pull Repository policy missing, or the puller's IAM lacks ecr:GetAuthorizationToken See the cross-account section below
Pull works locally, fails from a private subnet No NAT gateway and no VPC endpoints Add ecr.api, ecr.dkr interface endpoints and an S3 gateway endpoint

To see which principal is calling, run aws sts get-caller-identity with the same credentials the pipeline uses. For ECS failures, read the task's stopped reason first. The CannotPullContainerError text usually names the cause.

Clean up with lifecycle policies

Untagged layers from every CI build add up quickly in storage cost. A lifecycle policy expires them automatically.

lifecycle.json:

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Expire untagged images after 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "Keep the last 30 build images",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["build-"],
        "countType": "imageCountMoreThan",
        "countNumber": 30
      },
      "action": { "type": "expire" }
    }
  ]
}

Always preview before you apply:

aws ecr start-lifecycle-policy-preview --repository-name payments/api \
  --lifecycle-policy-text file://lifecycle.json

aws ecr get-lifecycle-policy-preview --repository-name payments/api \
  --query "{status:status,expiring:previewResults[].imageTags}"

aws ecr put-lifecycle-policy --repository-name payments/api \
  --lifecycle-policy-text file://lifecycle.json

Caution

Lifecycle expiry is permanent. A rule like "keep the last 10 images" with tagStatus: any can delete the image that production is running or rolls back to. Exclude release tags (for example, use tagPrefixList only on build- tags), and check the preview for any tag currently deployed.

Manual one-off delete of untagged images (up to 100 IDs per call):

IDS=$(aws ecr list-images --repository-name payments/api --filter tagStatus=UNTAGGED \
  --query "imageIds[:100]" --output json)
aws ecr batch-delete-image --repository-name payments/api --image-ids "$IDS"

Cross-account pulls

The pulling account needs two things. First, a repository policy in the owning account that allows it:

cat > repo-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowPullFromProd",
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
    "Action": [
      "ecr:BatchGetImage",
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchCheckLayerAvailability"
    ]
  }]
}
EOF
aws ecr set-repository-policy --repository-name payments/api --policy-text file://repo-policy.json

Second, IAM permissions in the pulling account: ecr:GetAuthorizationToken on "Resource": "*" plus the three pull actions above on the repository ARN. The ECS task execution role or EKS node role needs these, not the application role.

Tip

To share with every account in your organization, use a Condition on aws:PrincipalOrgID with "Principal": "*" instead of listing account IDs.

If the repository uses a customer managed KMS key, the pulling principal also needs kms:Decrypt on that key, granted in the key policy.

Scanning and replication

# Scan results for one image (basic scanning)
aws ecr describe-image-scan-findings --repository-name payments/api --image-id imageTag=1.4.2 \
  --query "imageScanFindings.findingSeverityCounts"

# Registry-level scanning configuration (basic or enhanced/Inspector)
aws ecr get-registry-scanning-configuration

# Replication rules (cross-Region or cross-account)
aws ecr describe-registry --query replicationConfiguration

Replication copies new pushes only. Images that existed before you created the rule are not copied, so push them again if the destination needs them.

Prevent it next time

  • Put a lifecycle policy on every repository when you create it, using IaC or repository creation templates.
  • Use immutable tags and deploy by digest so a re-push can't change what production runs.
  • Add ecr.api, ecr.dkr and S3 endpoints to every private VPC that pulls images.
  • Alarm on failed pulls in your orchestrator (ECS stopped reasons, Kubernetes ErrImagePull events) rather than waiting for a deploy to fail.