AWS Elastic Container Registry (ECR) is the default private Docker registry for AWS workloads. Teams interact with it dozens of times a day, but most only know the basics. This guide covers the full workflow: authenticate securely, pull and inspect images, extract filesystem layers without running a container, scan for CVEs with Amazon Inspector v2, manage costs through lifecycle policies, and automate everything with GitHub Actions OIDC.
Prerequisites
- AWS CLI v2 installed and configured (
aws configure) - Docker Engine ≥ 24 running locally
- IAM user or role with ECR read permissions
Step 1: Authenticate to ECR
ECR uses short-lived tokens (12-hour TTL) tied to your AWS identity. Authenticate before any pull or push:
# Replace <region> and <account-id> with your actual values
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
In automation, use the aws-actions/amazon-ecr-login GitHub Action (Step 7) instead of running this manually.
Step 2: Find Available Tags
# List all image tags in a repository
aws ecr list-images \
--repository-name my-repo \
--query 'imageIds[?imageTag!=null].imageTag' \
--output table
Step 3: Pull the Image
docker pull 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest
Step 4: Inspect & Run Commands in the Image
Run a one-off command inside the image without a persistent container:
# List root filesystem
docker run --rm \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest \
ls -la /
# Inspect baked-in environment variables
docker run --rm \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest \
env
# Override entrypoint for interactive exploration
docker run --rm -it \
--entrypoint /bin/sh \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest
Step 5: Extract the Full Filesystem Without Running the Container
Useful for recovering lost Dockerfiles, auditing third-party images, or forensic investigation:
# Save the image as a tar archive
docker image save \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest \
> my-image.tar
# Extract into a directory
mkdir -p image-fs && tar -xf my-image.tar -C image-fs
# Read the layer manifest (shows layer order)
cat image-fs/manifest.json | python3 -m json.tool
# Extract a specific layer to inspect its files
mkdir layer0 && tar -xf image-fs/<layer-digest>/layer.tar -C layer0
# Search for env files or config across all layers
find image-fs -name "*.env" -o -name "Dockerfile" 2>/dev/null
Each layer corresponds to a RUN, COPY, or ADD instruction in the original Dockerfile. Stacking them in manifest order reconstructs the final container filesystem.
Step 6: Security: Scan with Amazon Inspector v2
Amazon Inspector v2 continuously monitors ECR images for CVEs, scanning on push and re-scanning when new vulnerabilities are published.
Enable Enhanced Scanning
# Enable Inspector v2 for ECR
aws inspector2 enable --resource-types ECR
# Configure continuous enhanced scanning for all repos
aws ecr put-registry-scanning-configuration \
--scan-type ENHANCED \
--rules '[{
"repositoryFilters": [{"filter": "*", "filterType": "WILDCARD"}],
"scanFrequency": "CONTINUOUS_SCAN"
}]'
View Findings
aws inspector2 list-findings \
--filter-criteria '{
"ecrImageRepositoryName": [{"comparison": "EQUALS", "value": "my-repo"}]
}' \
--query 'findings[*].{
Severity: severity,
CVE: packageVulnerabilityDetails.vulnerabilityId,
Package: packageVulnerabilityDetails.vulnerablePackages[0].name
}' \
--output table
Align remediation with severity: patch CRITICAL and HIGH immediately; schedule MEDIUM for the next sprint.
Step 7: Lifecycle Policies: Control Storage Costs
Without policies, ECR silently accumulates thousands of images. One lifecycle rule pays for itself immediately:
aws ecr put-lifecycle-policy \
--repository-name my-repo \
--lifecycle-policy-text '{
"rules": [
{
"rulePriority": 1,
"description": "Keep last 10 versioned releases",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["v"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Expire untagged images after 7 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": { "type": "expire" }
}
]
}'
Step 8: Full CI/CD Pipeline with GitHub Actions (OIDC)
Use OIDC federation, no static AWS credentials stored in GitHub Secrets:
# .github/workflows/ecr-deploy.yml
name: Build & Push to ECR
on:
push:
branches: [main]
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-ecr-push
aws-region: us-east-1
- name: Login to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag & push
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $REGISTRY/my-repo:$IMAGE_TAG .
docker build -t $REGISTRY/my-repo:latest .
docker push $REGISTRY/my-repo:$IMAGE_TAG
docker push $REGISTRY/my-repo:latest
The OIDC role should have only the minimum ECR permissions: no full AmazonEC2ContainerRegistryFullAccess, scope it to ecr:GetAuthorizationToken + push operations on your specific repositories.
Quick Reference
| Task | Command |
|---|---|
| Authenticate | `aws ecr get-login-password \ |
| List tags | {% raw %}aws ecr list-images --repository-name <repo> --query 'imageIds[?imageTag!=null].imageTag'
|
| Pull image | docker pull <ecr-url>/<repo>:<tag> |
| Run command | docker run --rm --entrypoint /bin/sh <image> |
| Extract FS | docker image save <image> > img.tar && tar -xf img.tar |
| View CVEs | aws inspector2 list-findings ... |
| Set lifecycle | aws ecr put-lifecycle-policy ... |
With Inspector v2 scanning every image on push and lifecycle policies preventing storage sprawl, ECR becomes a fully governed container supply chain, not just a registry.
Top comments (0)