AWS for DevOps — ECR: Elastic Container Registry

3 minute read

7.2 — ECR: Elastic Container Registry

Amazon ECR is a fully managed Docker container registry. It stores, manages, and deploys your Docker images — tightly integrated with ECS, EKS, CodeBuild, and Lambda.

Why ECR Over Docker Hub?

Feature Docker Hub (Free) ECR
Private repos 1 Unlimited
Pull rate limits 100/6h (anonymous) No limits within AWS
IAM integration No Yes — fine-grained access
Vulnerability scanning Basic Enhanced (Inspector)
Image signing No Sigstore support
Encryption No AES-256 or KMS

Creating a Repository

$ aws ecr create-repository \
    --repository-name my-app \
    --image-scanning-configuration scanOnPush=true \
    --encryption-configuration encryptionType=AES256
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:ap-south-1:123456789012:repository/my-app",
        "repositoryUri": "123456789012.dkr.ecr.ap-south-1.amazonaws.com/my-app",
        "imageScanningConfiguration": { "scanOnPush": true }
    }
}

Push & Pull Workflow

  Developer / CI                          ECR
  ┌───────────────┐                    ┌─────────┐
  │ docker build  │                    │ my-app  │
  │ docker tag    │───── docker push──▶│  :v1    │
  │               │                    │  :v2    │
  └───────────────┘                    │  :latest│
                                       └─────────┘
                                            │
  ECS / EKS / Lambda ◀──── docker pull ─────┘

Authenticate and Push

# Step 1 — Authenticate Docker to ECR
$ aws ecr get-login-password --region ap-south-1 \
    | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-south-1.amazonaws.com
Login Succeeded

# Step 2 — Build the image
$ docker build -t my-app:v1 .

# Step 3 — Tag for ECR
$ docker tag my-app:v1 123456789012.dkr.ecr.ap-south-1.amazonaws.com/my-app:v1
$ docker tag my-app:v1 123456789012.dkr.ecr.ap-south-1.amazonaws.com/my-app:latest

# Step 4 — Push
$ docker push 123456789012.dkr.ecr.ap-south-1.amazonaws.com/my-app:v1
$ docker push 123456789012.dkr.ecr.ap-south-1.amazonaws.com/my-app:latest

Listing Images

$ aws ecr list-images --repository-name my-app
{
    "imageIds": [
        { "imageDigest": "sha256:abc123...", "imageTag": "v1" },
        { "imageDigest": "sha256:abc123...", "imageTag": "latest" },
        { "imageDigest": "sha256:def456...", "imageTag": "v2" }
    ]
}

# Get image details
$ aws ecr describe-images --repository-name my-app \
    --query 'imageDetails[].{Tag:imageTags[0],Size:imageSizeInBytes,Pushed:imagePushedAt}' \
    --output table

Vulnerability Scanning

# Scan an image manually
$ aws ecr start-image-scan \
    --repository-name my-app \
    --image-id imageTag=v1

# Get scan results
$ aws ecr describe-image-scan-findings \
    --repository-name my-app \
    --image-id imageTag=v1 \
    --query 'imageScanFindings.findingSeverityCounts'
{
    "CRITICAL": 0,
    "HIGH": 2,
    "MEDIUM": 5,
    "LOW": 12,
    "INFORMATIONAL": 8
}

Lifecycle Policies

Lifecycle policies automatically clean up old images to save storage costs:

$ aws ecr put-lifecycle-policy \
    --repository-name my-app \
    --lifecycle-policy-text '{
        "rules": [
            {
                "rulePriority": 1,
                "description": "Keep only last 10 tagged images",
                "selection": {
                    "tagStatus": "tagged",
                    "tagPrefixList": ["v"],
                    "countType": "imageCountMoreThan",
                    "countNumber": 10
                },
                "action": { "type": "expire" }
            },
            {
                "rulePriority": 2,
                "description": "Remove untagged images older than 7 days",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "sinceImagePushed",
                    "countUnit": "days",
                    "countNumber": 7
                },
                "action": { "type": "expire" }
            }
        ]
    }'

Repository Policies (Cross-Account Access)

Allow another AWS account to pull images:

$ aws ecr set-repository-policy \
    --repository-name my-app \
    --policy-text '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowPullFromAccount2",
                "Effect": "Allow",
                "Principal": { "AWS": "arn:aws:iam::987654321098:root" },
                "Action": [
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:BatchGetImage"
                ]
            }
        ]
    }'

Image Immutability

Prevent tags from being overwritten (good practice for production):

$ aws ecr put-image-tag-mutability \
    --repository-name my-app \
    --image-tag-mutability IMMUTABLE

With immutability enabled:

  • docker push my-app:v1 works the first time
  • docker push my-app:v1 again → fails (tag already exists)
  • Forces unique tags like v1.0.1, v1.0.2, or Git commit SHA

Common Mistakes

Mistake Why It Hurts Fix
No lifecycle policy Storage costs grow forever Set a policy to expire old images
Using :latest only Can’t track which version is deployed Tag with version or Git SHA
Auth token expired Push/pull fails after 12 hours Re-run aws ecr get-login-password
No vulnerability scanning Deploying images with known CVEs Enable scanOnPush=true

Quick Reference

Task Command
Create repository aws ecr create-repository --repository-name NAME
Authenticate aws ecr get-login-password \| docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.REGION.amazonaws.com
Push image docker push ACCOUNT.dkr.ecr.REGION.amazonaws.com/NAME:TAG
List images aws ecr list-images --repository-name NAME
Delete image aws ecr batch-delete-image --repository-name NAME --image-ids imageTag=TAG
Set lifecycle policy aws ecr put-lifecycle-policy --repository-name NAME --lifecycle-policy-text '...'

Next → ECS Task Definitions

Updated: