Container Registry

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Commands and behaviour below are current for AWS CLI v2 (2.37 line) and the aws-actions/amazon-ecr-login v2 and aws-actions/configure-aws-credentials v6 actions (checked 2026-10-01). ECR changed quickly in 2025 and 2026 (an archive storage class, tag-immutability exclusions, managed signing, the end of Clair-based scanning), so re-verify flags with aws ecr help and the linked reference pages before relying on them in a pipeline.

Every Bookshelf service image is built once and stored in a private Amazon Elastic Container Registry (ECR) repository: bookshelf/catalog-api, bookshelf/orders-api and bookshelf/notifier. ECS (see ECS and Fargate), EKS (see EKS Clusters) and container-image Lambda functions (see AWS Lambda) pull from there using IAM roles, never a registry password. This page covers the registry side: creating repositories, authenticating and pushing, the repository features that keep the images safe and cheap (immutability, scanning, lifecycle, signing, replication, pull through cache), and a GitHub Actions workflow that pushes with a short-lived OIDC role. The Docker mechanics — image layers, tags, registries in general — are in Registries and Docker Hub and the pipeline mechanics are in Docker CI/CD with GitHub Actions; the Azure counterpart is Azure Container Registry.

Diagram of an ECR private registry containing a repository with tagged and archived images, scanning, lifecycle policy, managed signing and replication, fed by builds and upstream registries and pulled by ECS, EKS and Lambda

Registries and repositories

ECR gives each AWS account one private registry per Region, addressed as <account-id>.dkr.ecr.<region>.amazonaws.com. A registry holds repositories; a repository holds OCI images and artifacts (including Helm charts, signatures and SBOMs) identified by tag and by digest. Repository names can contain slashes, so bookshelf/catalog-api is a single repository whose bookshelf/ prefix is a naming convention that also drives repository creation templates, pull through cache rules and replication filters.

Private registry

Authenticated pull and push, IAM and repository policies, KMS or AES-256 encryption at rest, scanning, lifecycle, replication. This is where Bookshelf images live.

ECR Public

A separate service (aws ecr-public, authenticated through us-east-1) for images you want to publish to anyone, served from public.ecr.aws. Public repositories have no lifecycle policy, scanning or replication features comparable to the private registry; use them for open-source images, not for Bookshelf.

Two ECR features are worth knowing before you start because older guides do not mention them: images can be moved to a low-cost archive storage class (archived images cannot be pulled until restored), and ECR can create repositories for you on push, through a pull through cache rule or by replication, using repository creation templates that apply your standards (immutability, encryption, policies, lifecycle) to each new repository.

In the AWS Management Console

  1. Choose the Region in the navigation bar (us-east-1 here is a placeholder) and open Elastic Container Registry.

  2. Under Private registry, choose Repositories → Create repository.

  3. Leave Repository visibility on Private, enter bookshelf/catalog-api as the name, set Tag immutability to Immutable, and keep AES-256 encryption (choose AWS KMS if your policy requires a customer managed key; the choice cannot be changed later).

  4. Choose Create. Select the repository and choose View push commands to see the login, tag and push commands for your account.

With the AWS CLI

export AWS_REGION=us-east-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export REGISTRY=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com

for repo in catalog-api orders-api notifier; do
  aws ecr create-repository \
    --repository-name bookshelf/$repo \
    --image-tag-mutability IMMUTABLE \
    --encryption-configuration encryptionType=AES256 \
    --region $AWS_REGION
done

aws ecr describe-repositories --query 'repositories[].repositoryUri' --output text

Public repositories are created with a different command set, and always through us-east-1:

aws ecr-public create-repository \
  --repository-name bookshelf-demo \
  --region us-east-1

Authenticating Docker and pushing images

A registry login is an authorization token that the AWS CLI obtains with your current IAM credentials and that Docker presents as the password for the fixed user name AWS. The token is valid for 12 hours and is scoped to the registry, not to one repository; what you can actually do is decided by IAM and repository policies. Older guides and tutorials still show aws ecr get-login, which was removed in AWS CLI v2; the current command is aws ecr get-login-password.

sequenceDiagram participant Dev as Developer or CI job participant CLI as AWS CLI participant ECR as ECR registry participant Docker as Docker daemon Dev->>CLI: aws ecr get-login-password CLI->>ECR: GetAuthorizationToken (SigV4, IAM credentials) ECR-->>CLI: token valid for 12 hours CLI-->>Dev: password Dev->>Docker: docker login --username AWS Docker->>ECR: push layers (InitiateLayerUpload, UploadLayerPart, PutImage) ECR-->>Docker: manifest stored, tag created

In the AWS Management Console

The console does not push images; it shows the commands. Open the repository and choose View push commands, then copy the four commands for your operating system. You can also open AWS CloudShell from the console toolbar, which already has the AWS CLI configured for your signed-in identity.

With the AWS CLI

aws ecr get-login-password --region $AWS_REGION \
  | docker login --username AWS --password-stdin $REGISTRY

docker build -t bookshelf/catalog-api:1.4.2 .
docker tag bookshelf/catalog-api:1.4.2 $REGISTRY/bookshelf/catalog-api:1.4.2
docker push $REGISTRY/bookshelf/catalog-api:1.4.2

aws ecr describe-images \
  --repository-name bookshelf/catalog-api \
  --query 'imageDetails[].[imageTags[0],imageDigest,imageSizeInBytes]' \
  --output table

Pull the image on another machine the same way (docker pull $REGISTRY/bookshelf/catalog-api:1.4.2). On a workstation that logs in many times a day, the open-source amazon-ecr-credential-helper lets Docker fetch tokens on demand instead of storing one in ~/.docker/config.json; Finch and Podman accept the same get-login-password pipe. For a Spring Boot service, Jib can push straight from Maven or Gradle without a Docker daemon: after the login above, mvn compile jib:build -Dimage=$REGISTRY/bookshelf/catalog-api:1.4.2 reuses the Docker credentials.

Multi-architecture images for Graviton

Graviton (arm64) instances and Fargate ARM tasks are cheaper per unit of work for many Java services, but an image built on an x86 laptop will not start on them. Build a multi-platform image so one tag works on both; ECR stores the resulting image index and the per-platform manifests.

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag $REGISTRY/bookshelf/catalog-api:1.4.2 \
  --push .

aws ecr batch-get-image \
  --repository-name bookshelf/catalog-api \
  --image-ids imageTag=1.4.2 \
  --query 'images[].imageManifest' --output text | head -c 400

Tag immutability

With mutable tags, pushing 1.4.2 twice silently repoints the tag, so the image a deployment pulled yesterday is not the one it pulls today. With immutable tags, ECR rejects a push to an existing tag with an ImageTagAlreadyExistsException, which is what you want for release tags. Immutability applies to every tag in the repository, with one refinement: exclusion filters (wildcards such as latest or dev-*) let a few tags stay mutable in an otherwise immutable repository, or the reverse (MUTABLE_WITH_EXCLUSION keeps a few tags immutable in a mutable repository).

Two practical consequences. First, a moving tag such as latest and an immutable repository cannot coexist unless latest is an exclusion. Second, repositories populated by a pull through cache rule (below) should stay mutable, because ECR needs to update the cached tag when the upstream tag moves.

In the AWS Management Console

  1. Open the repository list, select bookshelf/catalog-api, then choose Actions → Repository under Edit.

  2. Set Image tag immutability to Immutable and, if needed, add patterns such as latest or dev- to the *Immutable tag exclusion box.

  3. Choose Save.

With the AWS CLI

aws ecr put-image-tag-mutability \
  --repository-name bookshelf/catalog-api \
  --image-tag-mutability IMMUTABLE_WITH_EXCLUSION \
  --image-tag-mutability-exclusion-filters filterType=WILDCARD,filter=dev-*

Pair immutable tags with a tagging convention that never reuses a tag: the version, the Git commit SHA, or both (1.4.2, 1.4.2-3f9c2ab). Deployments should reference the digest or an immutable tag; see Docker CI/CD with GitHub Actions for the workflow side of that rule.

Lifecycle policies and the archive storage class

Without a lifecycle policy, every CI build adds an image that is stored (and billed) forever. A lifecycle policy is a JSON document of prioritised rules; ECR evaluates every rule at once, then applies them in priority order (lower number first), and an image is acted on by at most one rule. Actions run within about 24 hours of an image meeting the criteria, so test a policy with the preview before applying it.

A rule selects images by tagStatus (tagged, untagged or any), optionally by a tagPatternList or tagPrefixList (a rule may use one or the other, with at most four wildcards per string), and by a countType:

countType Meaning

imageCountMoreThan

Keep the newest N images; act on older ones.

sinceImagePushed

Act on images pushed more than N days ago.

sinceImagePulled

Act on images not pulled for N days (used with the transition action to archive; an image never pulled counts from its push time).

sinceImageTransitioned

Act on images archived for more than N days (used with expire on the archive storage class).

The action is expire (delete) or transition to the archive storage class. Archived images are cheap to keep but cannot be pulled: a pull returns 404 until the image is restored (about 20 minutes) or pushed again. Archived images have a minimum storage duration of 90 days, so a lifecycle rule cannot delete them sooner. Archived images cannot be scanned either; restore them first. Older guides describe only expiry by count or age; archiving is the newer way to retain release images for compliance without paying standard-storage rates.

The Bookshelf policy below keeps the 20 newest release-tagged images, expires untagged images (typically layers orphaned by a re-push) after a day, and archives everything else not pulled for 90 days:

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep the 20 newest release images",
      "selection": {
        "tagStatus": "tagged",
        "tagPatternList": ["[0-9]*"],
        "countType": "imageCountMoreThan",
        "countNumber": 20
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "Expire untagged images after one day",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 1
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 3,
      "description": "Archive images not pulled for 90 days",
      "selection": {
        "tagStatus": "any",
        "countType": "sinceImagePulled",
        "countUnit": "days",
        "countNumber": 90
      },
      "action": { "type": "transition", "targetStorageClass": "archive" }
    }
  ]
}

In the AWS Management Console

  1. Open the repository, then choose Lifecycle Policy in the left pane (under the repository name) → Create rule.

  2. For each rule enter the priority, description, image status (tagged, untagged, any), the match criteria and the action (Expire or Archive), then Save.

  3. Use Edit test rules → Save and run test to see which images each rule would affect, review the preview, and choose Apply as lifecycle policy.

  4. To archive a single image by hand, open it in the repository, choose Archive and confirm; an Archived images tab then appears for the repository.

With the AWS CLI

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

aws ecr get-lifecycle-policy-preview \
  --repository-name bookshelf/catalog-api \
  --query 'previewResults[].[imageTags,action.type]' --output table

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

# archive and restore one image by hand
aws ecr update-image-storage-class \
  --repository-name bookshelf/catalog-api \
  --image-id imageTag=1.3.0 \
  --target-storage-class ARCHIVE
aws ecr update-image-storage-class \
  --repository-name bookshelf/catalog-api \
  --image-id imageTag=1.3.0 \
  --target-storage-class STANDARD

Two details bite in practice. Signatures and other reference artifacts attached to an image count against the images-per-repository quota and are cleaned up by lifecycle policies within 24 hours of the image they refer to being deleted or archived. And an image that is part of a multi-architecture image index cannot be expired or archived until the index itself is.

Image scanning

ECR offers two scan types, set at the private-registry level so that one setting covers every repository:

Basic scanning

Operating-system packages are checked against the CVE database with AWS-native scanning, on push or manually. Clair-based basic scanning was deprecated and all accounts were moved to the AWS-native implementation on 2026-02-02, so older guides that mention Clair, or read results from imageScanFindingsSummary in describe-images, are out of date: read results with describe-image-scan-findings.

Enhanced scanning

An integration with Amazon Inspector that scans both operating-system and programming-language packages (for Bookshelf, the Maven dependencies baked into the Spring Boot image), on push and continuously as new vulnerabilities are published, and emits findings to EventBridge. ECR adds no charge for it but Amazon Inspector does. Switching between the two types makes earlier results unavailable until you switch back.

For an application that ships Java libraries, enhanced scanning is the useful one; basic scanning is a free baseline. Manual scans are not available with enhanced scanning, and images pushed more than 14 days before enhanced scanning was first enabled show SCAN_ELIGIBILITY_EXPIRED until they are pushed again.

In the AWS Management Console

  1. Open Private registry → Features & settings → Scanning and choose Edit.

  2. Choose Enhanced scanning (Inspector is enabled for you) or Basic scanning.

  3. Add a filter such as bookshelf/ and choose *Scan on push (basic or enhanced) or Continuous scan (enhanced only), then Save.

  4. Open an image in a repository to see its findings and severity counts.

With the AWS CLI

aws ecr put-registry-scanning-configuration \
  --scan-type ENHANCED \
  --rules '[{"scanFrequency":"CONTINUOUS_SCAN","repositoryFilters":[{"filter":"bookshelf/*","filterType":"WILDCARD"}]}]'

aws ecr describe-image-scan-findings \
  --repository-name bookshelf/catalog-api \
  --image-id imageTag=1.4.2 \
  --query 'imageScanFindings.findingSeverityCounts'

Use --scan-type BASIC with SCAN_ON_PUSH rules for the free option. Scanning is a signal, not a gate: to block a release on findings, have the pipeline call describe-image-scan-findings (or consume the Inspector EventBridge event) and fail the job. The general practice of scanning and slimming images is in Image Best Practices and Supply-Chain Security.

Image signing

Managed signing signs images automatically as they are pushed, using an AWS Signer signing profile, so you do not install or script the Notation CLI in every pipeline. You create a registry-level signing configuration of up to 10 rules, each pairing a signing profile with repository name filters. The identity pushing the image needs signer:SignPayload on the profile in addition to the ECR push actions, signing profiles must be in the registry’s Region (they may belong to another account), and signatures are stored in the repository and count toward its image quota. Manual signing with Notation and the AWS Signer plugin remains available for signing outside the push flow; verification still uses Notation.

In the AWS Management Console

  1. Open Private registry → Features & settings → Managed signing → Create rule.

  2. Create a new AWS Signer profile (name and signature validity period), choose Next, enter a repository name filter such as bookshelf/, and choose *Save.

With the AWS CLI

cat > signing-config.json <<EOF
{
  "rules": [
    {
      "signingProfileArn": "arn:aws:signer:$AWS_REGION:$ACCOUNT_ID:/signing-profiles/bookshelf_images",
      "repositoryFilters": [ { "filter": "bookshelf/*", "filterType": "WILDCARD_MATCH" } ]
    }
  ]
}
EOF

aws ecr put-signing-configuration --signing-configuration file://signing-config.json

aws ecr describe-image-signing-status \
  --repository-name bookshelf/catalog-api \
  --image-id imageTag=1.4.2

Replication and pull through cache

Replication

Replication copies images pushed to a repository into other Regions, other accounts, or both, for lower pull latency and disaster recovery. Configure it once on the source registry (up to 25 rules, 25 unique destinations); only content pushed or restored after the rule exists is replicated, repository names stay the same, the copy happens once per push (no chaining), and most images arrive within 30 minutes. Replication never deletes or archives, and repository policies, lifecycle policies and settings are not replicated unless a repository creation template with the REPLICATION purpose supplies them in the destination. For a cross-account destination, only the destination needs a registry permissions policy allowing ecr:ReplicateImage and ecr:CreateRepository for the source account.

cat > replication.json <<EOF
{
  "rules": [
    {
      "destinations": [ { "region": "eu-west-1", "registryId": "$ACCOUNT_ID" } ],
      "repositoryFilters": [ { "filter": "bookshelf/", "filterType": "PREFIX_MATCH" } ]
    }
  ]
}
EOF

aws ecr put-replication-configuration --replication-configuration file://replication.json

Derived from aws ecr put-replication-configuration and the Amazon ECR User Guide — Private image replication. In the console, use Private registry → Features & settings → Replication → Edit.

Pull through cache

A pull through cache rule maps a repository prefix in your registry to an upstream registry. The first pull of docker-hub/library/eclipse-temurin:25-jre through your registry host makes ECR create the repository and copy the image, and later pulls are served from ECR (ECR rechecks the upstream tag at most once every 24 hours). This keeps base images available when Docker Hub is rate limiting or down, and lets ECR scan and replicate them like any other repository.

Upstreams that need no credentials are Amazon ECR Public, the Kubernetes registry and Quay. Docker Hub, GitHub Container Registry, GitLab Container Registry, Azure Container Registry and Chainguard need credentials stored in a Secrets Manager secret whose name starts with ecr-pullthroughcache/ and which lives in the same account and Region (see Secrets Manager, Parameter Store and KMS). Another ECR registry can also be an upstream.

aws secretsmanager create-secret \
  --name ecr-pullthroughcache/docker-hub \
  --secret-string '{"username":"<docker-hub-user>","accessToken":"<docker-hub-access-token>"}'

aws ecr create-pull-through-cache-rule \
  --ecr-repository-prefix docker-hub \
  --upstream-registry-url registry-1.docker.io \
  --credential-arn arn:aws:secretsmanager:$AWS_REGION:$ACCOUNT_ID:secret:ecr-pullthroughcache/docker-hub-AbCdEf

docker pull $REGISTRY/docker-hub/library/eclipse-temurin:25-jre

Derived from aws ecr create-pull-through-cache-rule and the Amazon ECR User Guide — Sync an upstream registry. In the console use Private registry → Features & settings → Pull through cache → Add rule. Limits worth knowing: 50 rules per registry, the first pull may need a route to the internet (even with interface endpoints), Lambda cannot pull container images through a pull through cache rule, and pushing directly into a cache repository is unsupported.

Repository creation templates and create on push

Repositories that ECR creates for you start with default settings (mutable tags, AES-256, no policies) unless a repository creation template matches their name. A template has a prefix (ROOT matches everything not covered by a longer prefix), one or more purposes (PULL_THROUGH_CACHE, CREATE_ON_PUSH, REPLICATION), and the settings to apply: tag mutability, encryption, repository policy, lifecycle policy and resource tags. A template that sets tags or a KMS key also needs a repository creation role. With a CREATE_ON_PUSH template, docker push to a repository that does not exist yet creates it with those standards; without a matching template the push fails.

aws ecr create-repository-creation-template \
  --prefix bookshelf \
  --applied-for CREATE_ON_PUSH REPLICATION \
  --image-tag-mutability IMMUTABLE \
  --lifecycle-policy file://lifecycle.json

Derived from aws ecr create-repository-creation-template and the Amazon ECR User Guide — Repository creation templates. In the console, open Private registry → Features & settings → Repository creation templates.

Access control

Two layers decide whether a call succeeds. Identity-based IAM policies on the caller control what a user or role may do; repository policies (resource-based) attached to a repository control which principals, including other accounts, may use it. Within one account either layer is enough; across accounts both must allow. Everything starts with ecr:GetAuthorizationToken, which has no resource-level scope and must be allowed on *. The caller identities in the Bookshelf pipeline are the CI role (push) and the compute roles (pull). The push policy below is limited to the bookshelf/ repositories:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AuthToken",
      "Effect": "Allow",
      "Action": "ecr:GetAuthorizationToken",
      "Resource": "*"
    },
    {
      "Sid": "PushPullBookshelfImages",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload",
        "ecr:PutImage",
        "ecr:DescribeImages",
        "ecr:DescribeImageScanFindings"
      ],
      "Resource": "arn:aws:ecr:us-east-1:111122223333:repository/bookshelf/*"
    }
  ]
}

Pull-only identities need just ecr:GetAuthorizationToken, ecr:BatchGetImage, ecr:GetDownloadUrlForLayer (and ecr:BatchCheckLayerAvailability). For each Bookshelf consumer:

Consumer How it gets pull access

ECS on Fargate

The task execution role (not the task role) pulls the image at task start; the AWS managed AmazonECSTaskExecutionRolePolicy includes the ECR pull actions. See ECS and Fargate.

EKS

The node IAM role carries the AmazonEC2ContainerRegistryPullOnly managed policy (older clusters use AmazonEC2ContainerRegistryReadOnly); EKS Auto Mode manages the node role for you. See EKS Clusters.

Lambda container image

The image must be in a private ECR repository in the same Region; the deploying principal needs ECR read access, and for a repository in another account the repository policy must allow the Lambda service principal. See AWS Lambda.

EC2

An instance profile with the same pull actions; see Amazon EC2 and Auto Scaling.

To let another account pull Bookshelf images (for example a separate production account), attach a repository policy that names that account:

cat > repo-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowProdAccountPull",
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::<prod-account-id>:root" },
    "Action": [
      "ecr:BatchGetImage",
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchCheckLayerAvailability"
    ]
  }]
}
EOF

aws ecr set-repository-policy \
  --repository-name bookshelf/catalog-api \
  --policy-text file://repo-policy.json

Derived from aws ecr set-repository-policy and the Amazon ECR User Guide — Repository policy examples. In the console, use the repository’s Permissions tab → Edit policy JSON. The caller’s account administrator must still grant the same actions on its side; see Identity and Access for how the two policy types combine.

Networking

ECR traffic goes to the api.ecr and dkr.ecr endpoints and image layers are served from Amazon S3. A workload in a private subnet with no route to the internet therefore needs three VPC endpoints: interface endpoints for com.amazonaws.<region>.ecr.api and com.amazonaws.<region>.ecr.dkr, and the S3 gateway endpoint (whose policy must not block the regional ECR layer bucket). Fargate tasks that also write to CloudWatch Logs need that endpoint too. Endpoints, NAT gateways and their cost trade-offs are covered in VPC Networking; pulling over a NAT gateway works but bills data processing for every image pull.

Pushing from GitHub Actions with OIDC

A pipeline should never hold an access key. GitHub Actions can exchange its OIDC token for a short-lived role session; the AWS-side setup (the identity provider and a role trusted for one repository and branch) is in Identity and Access, and the workflow mechanics are in Docker CI/CD with GitHub Actions. Attach the push policy from the previous section to that role. The workflow then needs only the id-token: write permission:

name: catalog-api-image
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: us-east-1

jobs:
  push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::111122223333:role/bookshelf-github-actions-deploy
          aws-region: ${{ env.AWS_REGION }}

      - id: ecr
        uses: aws-actions/amazon-ecr-login@v2

      - uses: docker/setup-buildx-action@v3

      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.ecr.outputs.registry }}/bookshelf/catalog-api:${{ github.sha }}

Derived from the amazon-ecr-login and configure-aws-credentials action documentation. The registry output is the account’s registry host name; set registry-type: public to log in to ECR Public instead. Pin action versions the way your repository standard requires (the major-version tags above are convenient but mutable). Using the commit SHA as the tag keeps the repository immutable and traceable to a commit; the follow-up deployment step (aws ecs update-service or a new task definition revision) is in ECS Deployments, Scaling and Networking, and the wider CI/CD tooling is in Developer Tools and DevOps.

Pricing model, limits and monitoring

ECR has no per-repository or per-registry fee. The cost drivers are the amount of image data stored per month (a lower rate for the archive storage class, with a 90-day minimum storage duration), data transferred out of ECR to the internet or across Regions (pulls inside the same Region to compute in the same account are generally free of transfer charges, but NAT gateway data processing is not), and, if enabled, Amazon Inspector charges for enhanced scanning and AWS Signer charges for signing. Lifecycle policies are the main lever: most registries are dominated by old CI images nobody will ever pull again. See the Amazon ECR pricing page for current rates (no prices are repeated here because they change; check it before budgeting).

Default quotas (checked 2026-10-01; most rate quotas are adjustable in Service Quotas):

Quota Default

Repositories per Region

100,000

Images per repository

100,000

Tags per image

1,000

Maximum layer size

52,000 MiB

Rules per lifecycle policy

50

Lifecycle policy length

30,720 characters

Pull through cache rules per registry

50

Replication rules / unique destinations

25 / 25

GetAuthorizationToken requests per second

500

PutImage requests per second

10

BatchGetImage requests per second

2,000

The table is a subset; the full list is in the Amazon ECR service quotas page. Many nodes starting at once (an autoscaling event or a rollout) can reach the pull-rate quotas, which is a good reason to use pull through cache or a regional replica and to keep image layers small. ECR publishes usage metrics to CloudWatch under AWS/Usage, and API calls (including lifecycle and signing actions) appear in CloudTrail; set alarms on the usage metrics as described in the Amazon ECR usage metrics page and see Monitoring and Observability.

Recent changes and retired guidance

Item Status and current approach

Clair-based basic scanning

Deprecated; all accounts moved to AWS-native basic scanning on 2026-02-02. Use enhanced scanning for language packages.

aws ecr get-login

Removed in AWS CLI v2. Use aws ecr get-login-password | docker login.

Tag mutability

Exclusion filters (IMMUTABLE_WITH_EXCLUSION, MUTABLE_WITH_EXCLUSION) now let a few tags differ from the repository default.

Archive storage class

New: lifecycle rules can transition images to archive and later expire them; archived images are not pullable.

Managed signing

New: a registry-level signing configuration replaces hand-rolled Notation steps in every pipeline.

AWS Copilot CLI

Reached end of support on 2026-06-12. Older guides that let it create repositories and services should be replaced with explicit ECR repositories plus ECS Express Mode or a full ECS service (see ECS and Fargate).

Long-lived access keys in CI

Replace with an OIDC role as shown above.

Troubleshooting

Symptom Likely cause Check

no basic auth credentials or an expired-token error

The 12-hour token expired, or the login was for another Region or account.

Re-run aws ecr get-login-password for the registry host you push to; confirm aws sts get-caller-identity.

name unknown: the repository does not exist

The repository was not created, the name differs, or no CREATE_ON_PUSH template matches.

aws ecr describe-repositories; create the repository or a creation template.

ImageTagAlreadyExistsException

Pushing an existing tag to an immutable repository.

Use a new tag (version or commit SHA) or add an exclusion filter.

AccessDeniedException on GetAuthorizationToken or PutImage

The role lacks the action, or a repository policy or SCP denies it.

Policy simulator; check the repository policy and, for cross-account, both sides.

ECS task stops with CannotPullContainerError

Execution role missing pull permission, or no network path to ecr.api, ecr.dkr and S3.

Task stoppedReason; add the three VPC endpoints or a NAT gateway (see ECS Deployments, Scaling and Networking).

Pod ImagePullBackOff with 404 or not found

The image is archived, the tag was expired by a lifecycle rule, or the node role cannot pull.

aws ecr describe-images and its imageStatus; restore or re-push; check the node role.

Image scan shows UNSUPPORTED_IMAGE or SCAN_ELIGIBILITY_EXPIRED

Unsupported base OS, or the image is outside Inspector’s push-date window.

Re-push the image; check the Inspector supported-OS list.

Replication did not copy an image

Image pushed before the rule existed, missing destination registry policy, or Region not enabled.

aws ecr describe-image-replication-status; CloudTrail for ReplicateImage failures.

Lambda cannot use the image

Repository in another Region, an unsupported image format, or a pull through cache repository.

Use a same-Region private repository and a Lambda-supported image format.

Clean up

Repositories and images cost money only while they exist, and a repository that still holds images cannot be deleted without --force. Delete configuration first, then repositories, in every Region you replicated to:

aws ecr delete-lifecycle-policy --repository-name bookshelf/catalog-api
aws ecr delete-repository-policy --repository-name bookshelf/catalog-api

aws ecr delete-pull-through-cache-rule --ecr-repository-prefix docker-hub
aws secretsmanager delete-secret \
  --secret-id ecr-pullthroughcache/docker-hub \
  --recovery-window-in-days 7

aws ecr put-replication-configuration --replication-configuration '{"rules":[]}'
aws ecr delete-signing-configuration
aws ecr delete-repository-creation-template --prefix bookshelf

for repo in catalog-api orders-api notifier; do
  aws ecr delete-repository --repository-name bookshelf/$repo --force
done
aws ecr delete-repository --repository-name docker-hub/library/eclipse-temurin --force
aws ecr-public delete-repository --repository-name bookshelf-demo --force --region us-east-1

Derived from aws ecr delete-repository, aws ecr delete-pull-through-cache-rule, aws ecr delete-signing-configuration, aws ecr delete-repository-creation-template, aws ecr-public delete-repository and aws secretsmanager delete-secret. In the console, select each repository → Delete and type delete to confirm; remove rules under Private registry → Features & settings. Delete the replicated repositories in the destination Region as well, switch enhanced scanning back to basic if you no longer want Inspector charges, and delete the AWS Signer signing profile (aws signer cancel-signing-profile) if you created one only for testing.