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-1mirrored tous-west-2automatically. - 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 tags —
orders: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 tags —
orders:abc123for traceability. - Environment tags —
orders: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:GetAuthorizationTokenand 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 toIMMUTABLEat the repo level. Avoidlatest. - “How do you handle multi-arch (ARM/x86)?” —
docker buildxwith--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.