What you will learn
- What ECR is and why teams use it instead of Docker Hub in AWS environments
- Private vs Public ECR repositories
- The ECR push and pull workflow from your CI/CD pipeline
- Image scanning — basic and enhanced scanning with Inspector
- Lifecycle policies — automatic deletion of old images to control costs
- Cross-account access — sharing images between AWS accounts
- Pull-through cache — caching public images inside ECR for reliability and speed
- Image immutability — preventing tag overwrites in production
- ECR in the ECS and EKS deployment pipeline
Why this matters
Every team running containers on AWS needs somewhere to store Docker images. Docker Hub is the public default — but it has rate limits, images are publicly visible by default, and pulling from Docker Hub adds external network dependency to every ECS or EKS deployment. ECR is the AWS-native answer. Images live in your AWS account, authentication is handled by IAM, images are private by default, no rate limits on pulls within AWS, and it integrates natively with ECS, EKS, and CodePipeline. Most importantly — your container deployments never depend on an external service being available.
What is Amazon ECR
ECR (Elastic Container Registry) is a fully managed Docker image registry. Think of it as Docker Hub but hosted inside your AWS account.
Docker Hub: Public registry — images visible to everyone by default Rate limits on pulls (100-200 pulls per 6 hours for free accounts) External dependency — your deployments depend on Docker Hub being up Manual authentication for private images Amazon ECR: Private by default — only IAM-authorised principals can access No pull rate limits within AWS — ECS and EKS pull freely Integrated with IAM — same credentials your team already uses No external dependency — images in your account, your region Integrates natively with ECS, EKS, CodePipeline, CodeBuildPrivate vs Public Repositories
Private ECR:
One registry per AWS account per regionMultiple repositories inside the registryEach repository holds one application's images with multiple tags Registry: 123456789012.dkr.ecr.ap-south-1.amazonaws.comRepositories: .../devops-orders-api → orders-api:v1.0, orders-api:v1.1, orders-api:latest .../devops-payment-api → payment-api:v2.3, payment-api:latest .../devops-notification → notification:v1.0Public ECR (ECR Public Gallery):
Images publicly accessible to anyoneURL: public.ecr.aws/your-alias/your-imageUse for: open-source projects, sharing base images publiclyFree to push and pull from AWS computeThe Push and Pull Workflow
The standard workflow for getting your Docker image into ECR and running on ECS or EKS:
Developer writes code ↓CI/CD pipeline (CodeBuild, GitHub Actions) runs: 1. docker build 2. docker tag 3. docker push to ECR ↓ECR stores the image ↓ECS or EKS pulls the image and runs itStep by step:
## Step 1 — Authenticate Docker to ECR (token valid 12 hours)aws ecr get-login-password --region ap-south-1 | \ docker login \ --username AWS \ --password-stdin \ 123456789012.dkr.ecr.ap-south-1.amazonaws.com ## Step 2 — Build your imagedocker build -t devops-orders-api:v1.2 . ## Step 3 — Tag with full ECR URIdocker tag devops-orders-api:v1.2 \ 123456789012.dkr.ecr.ap-south-1.amazonaws.com/devops-orders-api:v1.2 docker tag devops-orders-api:v1.2 \ 123456789012.dkr.ecr.ap-south-1.amazonaws.com/devops-orders-api:latest ## Step 4 — Push both tagsdocker push 123456789012.dkr.ecr.ap-south-1.amazonaws.com/devops-orders-api:v1.2docker push 123456789012.dkr.ecr.ap-south-1.amazonaws.com/devops-orders-api:latestECR stores layers. If two images share the same base layer, ECR stores it only once. Subsequent pushes with the same layers are instant — only new layers are uploaded.
Image Scanning — Vulnerability Detection
ECR scans your images for known security vulnerabilities in OS packages and application libraries.
Basic Scanning (free):
Uses the open-source Clair scannerScans on push or on demandChecks against CVE databasesShows: HIGH, MEDIUM, LOW severity findings per imageEnhanced Scanning (paid — uses Amazon Inspector):
Uses Inspector under the hoodContinuous scanning — re-scans automatically when new CVEs are discoveredCovers: OS packages AND application dependencies (Node.js, Python packages, Java jars)Risk score per findingFindings appear in Inspector console A new CVE is published for Log4jAll images in ECR are automatically re-scannedAny image containing the vulnerable Log4j version is flagged immediatelyEnable scanning at repository level:
ECR → Repositories → devops-orders-api → EditScan on push: Enable → Save After every push → ECR scans automaticallyView results: ECR → Repositories → devops-orders-api → Images → click image → VulnerabilitiesLifecycle Policies — Control Storage Costs
Every CI/CD push creates a new image. Without lifecycle policies, images accumulate indefinitely. A busy team pushing 10 images per day has 300 images per month — most of them old versions nobody will ever use.
Lifecycle policies automatically expire and delete old images based on rules you define.
Common policy patterns:
Keep only the last 10 images: Rule: expire images when count exceeds 10 Effect: always have the 10 most recent, older ones deleted automatically Keep tagged releases, expire untagged: Rule 1: keep any image tagged with v* (release tags) — never expire Rule 2: expire untagged images after 7 days Effect: production releases kept forever, dev builds cleaned up weekly Keep last 30 days only: Rule: expire images older than 30 days Effect: sliding window of recent imagesSetting up in the console:
ECR → Repositories → your-repo → Lifecycle Policy → Create ruleTag status: Any Image count more than: 10 Action: ExpireSave and runRememberSet up a lifecycle policy when you create the repository — not after 6 months when you notice storage costs have grown. Once a repository has thousands of images, cleaning up requires deleting them one by one.
Image Immutability — Prevent Tag Overwriting
By default, you can push a new image with the same tag and it overwrites the old one. latest pushed today replaces latest from yesterday.
Without immutability: Friday: push orders-api:v1.2 → ECS runs this Monday: push orders-api:v1.2 (buggy build) → same tag, different image ECS pulls "v1.2" and gets the buggy Monday build Friday's working build is gone With image immutability: Attempt to push orders-api:v1.2 again → ECR rejects it with an error Every tag is permanent — cannot be overwritten Rollback is always possible — the old image is guaranteed to still be thereEnable at repository level:
ECR → Repositories → your-repo → Edit → Image tag mutability: Immutable → SaveCross-Account Access
Teams often need to share images between accounts — a shared infrastructure account stores base images, application accounts pull from it.
Grant another account pull access via repository policy:
ECR → your-repo → Permissions → Edit policy JSON{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::987654321098:root" }, "Action": [ "ecr:GetDownloadUrlForLayer", "ecr:BatchCheckLayerAvailability", "ecr:BatchGetImage" ] } ]}Account 987654321098 can now pull images from this repository. They still need to authenticate using their own credentials and call ecr:GetAuthorizationToken in your account.
Pull-Through Cache — Cache Public Images Locally
Your ECS tasks need nginx:latest, node:18, or python:3.12 from Docker Hub. Every pull goes to Docker Hub — rate limits apply, and your deployment depends on Docker Hub availability.
Pull-through cache creates a mirrored copy of public registries inside your ECR.
Configure pull-through cache rule:Upstream: registry-1.docker.io (Docker Hub)ECR namespace: dockerhub First pull of nginx:latest:ECS → ECR (dockerhub/nginx:latest) → ECR fetches from Docker Hub → caches in your ECR Subsequent pulls:ECS → ECR (dockerhub/nginx:latest) → served from your ECR cache (no Docker Hub call)No Docker Hub rate limits. No external dependency during deployments. Images cached in your region.
Supports: Docker Hub, Quay, GitHub Container Registry, Kubernetes (registry.k8s.io), Amazon ECR Public.
Hands-on Lab — Create Repository, Push Image, Set Lifecycle Policy
Step 1 — Create an ECR repository
ECR → Repositories → Create repositoryVisibility: PrivateRepository name: devops-orders-apiImage tag mutability: ImmutableScan on push: EnableCreate repositoryStep 2 — Authenticate and push a Docker image
Click on devops-orders-api → View push commandsFollow the 4 commands shown (authenticated for your account and region):## The console shows your exact commands — copy from there## They follow this pattern: ## 1. Loginaws ecr get-login-password --region ap-south-1 | \ docker login --username AWS --password-stdin \ YOUR-ACCOUNT-ID.dkr.ecr.ap-south-1.amazonaws.com ## 2. Build (use any simple Dockerfile)echo "FROM nginx:alpine" > Dockerfiledocker build -t devops-orders-api . ## 3. Tagdocker tag devops-orders-api:latest \ YOUR-ACCOUNT-ID.dkr.ecr.ap-south-1.amazonaws.com/devops-orders-api:v1.0 ## 4. Pushdocker push \ YOUR-ACCOUNT-ID.dkr.ecr.ap-south-1.amazonaws.com/devops-orders-api:v1.0Step 3 — View the image and scan results
ECR → devops-orders-api → ImagesYour image appears with size, push time, and scan statusClick the image → Vulnerabilities tabSee any findings from the scan (nginx:alpine typically has very few)Step 4 — Create a lifecycle policy
ECR → devops-orders-api → Lifecycle Policy → Create ruleRule priority: 1Rule description: Keep only last 5 imagesImage status: AnyMatch criteria: Image count more than 5Action on review: ExpireSave and run test Review results shows which images would be deleted if the policy ran now.Save → policy is now active for all future pushes.Step 5 — Cleanup
ECR → devops-orders-api → select all images → DeleteECR → Repositories → devops-orders-api → Delete repositoryConfirm deletionCommon Mistakes to Avoid
Common MistakeNot setting a lifecycle policy and discovering 6 months later that image storage costs are significant. ECR charges per GB stored. A repository with 2,000 old images adds up. Set the policy when you create the repository.
Common MistakeThe most common ECS deployment failure is an ECR image pull error. The cause is almost always a missing IAM permission on the ECS Task Execution Role. It needs: ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, and ecr:GetDownloadUrlForLayer. Check the execution role first when a container fails to start.
TipUse versioned tags alongside latest. Always push both
:v1.2and:latest. ECS and EKS should reference the specific version tag in production — not latest. The:latesttag is for humans to see what the current version is. Deployments referencing:latestmake rollbacks impossible and create unpredictable behaviour.