Amazon ECR (Elastic Container Registry)

5 min read index source

Amazon ECR (Elastic Container Registry)

AWS’s managed Docker image registry. Where you push your orders:1.0.0 images for ECS / EKS / Lambda to pull from.

Basics

  • Private repositories — default; auth required.
  • Public repositories (ECR Public) — anyone can pull.
  • Cross-region replication — image pushed in us-east-1 mirrored to us-west-2 automatically.
  • Lifecycle policies — auto-delete old image versions.

Authentication

ECR uses IAM. Get a token, log Docker into the registry:

aws ecr get-login-password --region us-east-1 \
  | docker login --username AWS \
      --password-stdin 1234.dkr.ecr.us-east-1.amazonaws.com

For CI/CD, GitHub Actions provides aws-actions/amazon-ecr-login to handle this.

For ECS / EKS pulling, no manual login — the execution role (ECS) or kubelet IAM (EKS) has ecr:GetAuthorizationToken etc. and authenticates automatically.

Push workflow

# Build
docker build -t orders:1.0.0 .

# Tag for ECR
docker tag orders:1.0.0 \
  1234.dkr.ecr.us-east-1.amazonaws.com/orders:1.0.0

# Push
docker push 1234.dkr.ecr.us-east-1.amazonaws.com/orders:1.0.0

Or with docker buildx for multi-arch:

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t 1234.dkr.ecr.us-east-1.amazonaws.com/orders:1.0.0 \
  --push .

ARM (Graviton) is ~20% cheaper on Fargate / EC2 / Lambda. Multi-arch images let you run the same tag on both architectures.

Image scanning

ECR scans pushed images for known CVEs. Two modes:

  • Basic — free, scans on push.
  • Enhanced (Amazon Inspector) — continuous, deeper, includes OS + language packages. Costs per image per month.
# View scan findings
aws ecr describe-image-scan-findings \
  --repository-name orders \
  --image-id imageTag=1.0.0

For SDLC integration: fail the CI build if any HIGH/CRITICAL findings — typical pattern via a script that polls scan results and exits non-zero.

Lifecycle policies

Without cleanup, repos fill up with builds. Lifecycle policies auto-prune:

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep 10 most recent tagged images",
      "selection": {
        "tagStatus": "tagged",
        "tagPatternList": ["v*"],
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {"type": "expire"}
    },
    {
      "rulePriority": 2,
      "description": "Delete untagged after 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": {"type": "expire"}
    }
  ]
}

Pull-through caching

For public images (DockerHub, Quay, ECR Public), enable pull-through cache: ECR transparently pulls the image once, caches in a private repo, subsequent pulls hit ECR. Saves bandwidth costs at scale and survives DockerHub rate limits.

aws ecr create-pull-through-cache-rule \
  --ecr-repository-prefix docker-hub \
  --upstream-registry-url registry-1.docker.io

Then:

docker pull 1234.dkr.ecr.us-east-1.amazonaws.com/docker-hub/library/postgres:16

Cross-region replication

aws ecr put-replication-configuration --replication-configuration '{
  "rules": [{
    "destinations": [
      {"region": "us-west-2", "registryId": "1234567890"},
      {"region": "eu-west-1", "registryId": "1234567890"}
    ]
  }]
}'

Useful for multi-region deployments — your eu-west-1 cluster pulls from the local ECR copy, not the us-east-1 original. Latency and cross-region transfer cost win.

Tagging strategy

  • Immutable tagsorders:1.0.0, never overwritten. Best practice; rollback works because the tag still points at the old image.
  • latest — convenience but dangerous; rollback impossible (tag is overwritten on each push).
  • Git SHA tagsorders:abc123 for traceability.
  • Environment tagsorders:prod-current, mutable, updated by deploy pipeline; CI knows what’s deployed.

Enable image tag immutability on the repo to prevent overwriting:

aws ecr put-image-tag-mutability \
  --repository-name orders \
  --image-tag-mutability IMMUTABLE

After this, pushing orders:1.0.0 a second time fails.

ECR + EKS / ECS

EKS nodes use IRSA or instance profile to pull. The kubelet calls ECR with the node’s IAM identity. Permissions needed:

{
  "Effect": "Allow",
  "Action": [
    "ecr:GetAuthorizationToken",
    "ecr:BatchCheckLayerAvailability",
    "ecr:GetDownloadUrlForLayer",
    "ecr:BatchGetImage"
  ],
  "Resource": "*"
}

ECS execution role gets the same permissions on the AWS managed AmazonECSTaskExecutionRolePolicy.

VPC Endpoints for ECR

In private VPCs without NAT egress, pull will fail. Add two interface endpoints:

  • com.amazonaws.us-east-1.ecr.api — for auth + manifest.
  • com.amazonaws.us-east-1.ecr.dkr — for layer download.

Plus the gateway endpoint for S3 (image layers live in S3 internally). Without S3 endpoint, the layer pulls fail with “no route to host.”

Common gotchas

  • CannotPullContainerError on Fargate. No NAT + no VPC Endpoint for ECR/S3. Fix per above.
  • Pull-through cache requires registry pre-creation. First pull through the cache creates the repo; subsequent pulls hit it.
  • Image tag immutability accidentally enabled mid-development. “I just want to push 1.0.0 again…” — no. Use a new tag.
  • Layer size pricing. ECR storage is per-GB-month; uncleaned repos with hundreds of builds get expensive. Lifecycle policy is mandatory.
  • DockerHub rate limits in CI. Anonymous pulls capped at 100/6h per IP. ECR pull-through cache or DockerHub paid plan.

Cost

  • Storage: $0.10/GB/month.
  • Egress: $0.09/GB to internet (free within region for AWS services).
  • Cross-region replication: standard inter-region transfer ($0.02/GB).
  • Pull-through cache: standard storage + transfer.

A repo with 50 versions of a 500MB image = 25 GB = $2.50/month. Across a hundred services, this adds up. Lifecycle policies pay for themselves.

Interview angle

  • “How does ECS/EKS authenticate to ECR?” — execution role (ECS) or node/IRSA IAM identity (EKS) has ecr:GetAuthorizationToken and pull permissions. The runtime calls ECR with that identity; no static credentials in the cluster.
  • “Why is your Fargate task failing with CannotPullContainerError in a private subnet?” — no path to ECR. Either add VPC Endpoints (ecr.api, ecr.dkr, plus S3 gateway endpoint for layers), or add NAT, or move to a subnet with internet egress.
  • “What’s pull-through cache and when do you use it?” — ECR transparently caches images from upstream registries (DockerHub, Quay). First pull fetches + caches; subsequent pulls hit ECR. Useful at scale for bandwidth costs and DockerHub rate-limit survival.
  • “How do you do safe rollbacks with ECR?” — immutable tags (semver: 1.0.0, 1.0.1). Never overwrite. Set image tag mutability to IMMUTABLE at the repo level. Avoid latest.
  • “How do you handle multi-arch (ARM/x86)?”docker buildx with --platform linux/amd64,linux/arm64; push a manifest list. ECS/EKS/Lambda pulls the right arch automatically. ARM is ~20% cheaper across compute services.
  • “How do you stop ECR storage from growing forever?” — Lifecycle policies. Auto-delete untagged after N days, keep N most-recent tagged versions per major release. Set this on every repo from day one.