Amazon ECR Best Practices: Security, Tagging, Scanning & Lifecycle Policies

Amazon ECR stores your Docker images on AWS and serves them to ECS, EKS, Lambda, and App Runner using IAM roles. No Docker Hub credentials, no pull rate limits, no third-party registry traffic. The ECR overview covers the fundamentals. This guide covers what production configuration actually looks like: safer images, predictable deployments, controlled storage costs, and tight access control.

Simple explanation#

ECR is a private warehouse for container images. Your CI pipeline builds an image from your code, pushes it to ECR with a unique tag, and your ECS service pulls it from there when starting new containers.

Think of each image tag like a tracking number on a package. my-app:abc12345 means “the software built from commit abc12345, packaged and ready to ship.” Without that tracking number, you cannot tell which version of your app is running in production — and when something breaks, you are searching through boxes with no labels.

Production ECR configuration adds guardrails: preventing tags from being overwritten, automatically deleting old images, scanning for vulnerabilities, and restricting who can push and pull.

Mental model

ECR is just storage. The practices on this page are about making that storage trustworthy: knowing what is in it, keeping it clean, and controlling who can write to it.

How ECR fits into a CI/CD pipeline#

A typical AWS container pipeline works like this:

  1. Developer pushes code to GitHub or CodeCommit
  2. AWS CodeBuild builds the Docker image and runs tests
  3. CodeBuild authenticates to ECR and pushes the image tagged with the commit SHA
  4. The pipeline triggers an ECS service update referencing the new image tag
  5. ECS pulls the image from ECR and starts new tasks

ECR sits at step 3. Your choices here (how images are tagged, who can push, what vulnerabilities are acceptable) affect everything downstream. A misconfigured registry means unknown images in production, broken rollbacks, and undetected CVEs. For the full pipeline walkthrough, see CI/CD Pipelines for ECS.

Start here when creating a new ECR repository for production use:

The rest of this guide explains each setting, when it matters, and what tradeoffs to expect.

Image tagging and tag immutability#

Tag every image with the commit SHA#

Deploying with :latest is the most common ECR mistake. You cannot tell what is running in production, cannot roll back to a specific version, and cannot correlate an incident to a specific build.

Tag every image with the git commit SHA. In a CodeBuild buildspec:

IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-8)
docker build -t $REPO_URI:$IMAGE_TAG .
docker push $REPO_URI:$IMAGE_TAG

This gives you a stable, unique, traceable tag per build. If something breaks in production, you know exactly which commit caused it and can redeploy the previous tag immediately.

Watch out

If you deploy with :latest and a production incident happens at 2am, your only option is to dig through CloudWatch logs and piece together what commit was running. Commit SHA tags make incident response measurably faster. They cost nothing to implement.

Enable tag immutability#

Tag immutability prevents any push from overwriting an existing tag. Think of it like a printed edition number on a published book: once “978-X” is printed, that number always refers to that exact edition. You never go back and stamp a different book with the same number.

aws ecr put-image-tag-mutability \
  --repository-name my-app \
  --image-tag-mutability IMMUTABLE \
  --region us-east-1
ModeBehaviorWhen to use
IMMUTABLEPush to an existing tag fails with an errorProduction repos deploying by commit SHA
MUTABLEExisting tags can be overwritten silentlyDev repos or workflows that rebuild :latest

With immutable tags, you can no longer push :latest on every build. Most teams solve this by dropping :latest entirely for production repositories and deploying by commit SHA only.

Lifecycle policies#

Without lifecycle policies, ECR repositories grow indefinitely. Think of it like a garage with no “clear out” rule: every car you ever owned is still parked in there, taking up space. Every CI build pushes at least one image. After months of active development, you accumulate hundreds of old images with unpatched vulnerabilities and an increasing storage bill.

A practical starting policy for most teams:

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Expire untagged images after 1 day",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 1
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "Keep last 30 tagged images",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["v"],
        "countType": "imageCountMoreThan",
        "countNumber": 30
      },
      "action": { "type": "expire" }
    }
  ]
}
aws ecr put-lifecycle-policy \
  --repository-name my-app \
  --lifecycle-policy-text file://lifecycle-policy.json \
  --region us-east-1

Rule 1 cleans up untagged images quickly. When you push to an existing mutable tag, the old image loses its tag and becomes untagged. These orphaned images accumulate silently without this rule. Rule 2 caps the total count of tagged images to control storage costs over time.

ECR evaluates rules in priority order. Set lifecycle policies when you create the repository. Retroactive cleanup after a year of builds is more painful than starting correctly.

For Terraform for AWS users, manage lifecycle policies alongside the repository definition with the aws_ecr_lifecycle_policy resource.

Vulnerability scanning#

Basic vs enhanced scanning#

Basic scanningEnhanced scanning
EngineClairAmazon Inspector
CoverageOS packagesOS packages + language packages (npm, pip, Maven, Go)
Re-scans after pushNo (results go stale)Yes, continuous as new CVEs are published
CostFreeAdditional cost per image
Findings locationECR consoleAmazon Inspector + EventBridge
Recommended forDev and non-critical reposAll production repositories

Enable enhanced scanning at the registry level so it applies to all repositories automatically:

aws ecr put-registry-scanning-configuration \
  --scan-type ENHANCED \
  --rules '[{
    "repositoryFilters": [{"filter": "*", "filterType": "WILDCARD"}],
    "scanFrequency": "CONTINUOUS_SCAN"
  }]' \
  --region us-east-1
Security risk

Basic scanning is free and takes one line to enable. Skipping it means you are shipping containers to production with no awareness of known CVEs. Enhanced scanning costs money, but basic scanning has no excuse to skip.

Blocking deployments on critical findings#

Add this check to your CodeBuild buildspec after pushing the image. If critical vulnerabilities are found, the build fails before deployment begins:

CRITICAL=$(aws ecr describe-image-scan-findings \
  --repository-name my-app \
  --image-id imageTag=$IMAGE_TAG \
  --query 'imageScanFindings.findingSeverityCounts.CRITICAL' \
  --output text 2>/dev/null || echo "0")

[ "${CRITICAL:-0}" -gt "0" ] && { echo "Critical CVEs found. Failing build."; exit 1; }

This pairs with the Secure CI/CD Pipelines practice of making the build pipeline a security gate, not just a delivery mechanism.

Access control: IAM vs repository policies vs registry policies#

Three policy types control ECR access. Understanding which to use is one of the more confusing parts of ECR setup.

Policy typeAttached toControlsCross-account?
IAM policyRole or userWhat that principal can doSame account only
Repository policyECR repositoryWho can access this repositoryYes
Registry policyECR registryRegistry-wide rules (replication, pull-through cache)Yes

For most teams: IAM policies on the CodeBuild and ECS roles, with repository policies only when cross-account access is needed. Here is what least-privilege push and pull looks like as a repository policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCIRolePush",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::123456789012:role/CodeBuildRole"},
      "Action": [
        "ecr:BatchCheckLayerAvailability", "ecr:PutImage",
        "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload"
      ]
    },
    {
      "Sid": "AllowECSPull",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::123456789012:role/ECSTaskExecutionRole"},
      "Action": [
        "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer", "ecr:BatchCheckLayerAvailability"
      ]
    }
  ]
}

This restricts push to the CI role only. Every image in ECR has traceable build provenance. No other principal in the account can push without using that specific role. If IAM roles are new to you, that guide covers the mechanics of the push/pull role separation.

Single-account shortcut

In a single-account setup, you often do not need a repository policy at all. IAM policies on the CodeBuild role (push) and ECS task execution role (pull) are sufficient. Repository policies become necessary when you need cross-account access or want an extra explicit restriction on who can write to the registry.

Encryption and image signing#

Encryption at rest#

ECR encrypts images at rest by default using AWS-managed KMS keys. This is sufficient for most workloads. If compliance requires customer-managed keys (for rotation control, audit trails, or cross-account key management), configure KMS at repository creation:

aws ecr create-repository \
  --repository-name my-app \
  --encryption-configuration \
    encryptionType=KMS,kmsKey=arn:aws:kms:us-east-1:123456789012:key/mrk-abc123 \
  --region us-east-1
Permanent setting

You cannot change a repository’s encryption configuration after creation. Decide on KMS before creating the repository, not after.

Image signing with AWS Signer#

Image signing lets you verify that a container image was produced by your authorized pipeline and has not been modified since. AWS Signer attaches a cryptographic signature to each image, and a verification step in your deployment pipeline checks it before running the container.

This is an advanced practice, most valuable in regulated environments or when you need to enforce that only pipeline-produced images can run in production. Implement the foundational settings on this page before adding image signing.

Cross-account and cross-region patterns#

Cross-account pulls#

In a multi-environment setup, images are typically built in a central tooling account and deployed into separate production, staging, and dev accounts. Cross-account pulls require permission on both sides.

In the source account — add a repository policy granting pull access to the destination account:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowProductionAccountPull",
    "Effect": "Allow",
    "Principal": {"AWS": "arn:aws:iam::999888777666:root"},
    "Action": [
      "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer", "ecr:BatchCheckLayerAvailability"
    ]
  }]
}

In the destination account — the ECS task execution role also needs an IAM policy allowing ecr:GetAuthorizationToken and the pull actions against the source registry ARN. The repository policy alone is not sufficient.

Cross-region replication#

If ECS services run in multiple regions, pulling images across regions adds latency and data transfer costs. ECR replication copies images to destination regions automatically after each push:

aws ecr put-replication-configuration \
  --replication-configuration '{
    "rules": [{
      "destinations": [{"region": "eu-west-1", "registryId": "123456789012"}],
      "repositoryFilters": [{"filter": "my-app", "filterType": "PREFIX_MATCH"}]
    }]
  }' \
  --region us-east-1

Replication is asynchronous. Do not trigger a cross-region deployment immediately after a push without confirming the image has arrived in the target region.

Private networking with VPC endpoints#

By default, ECR API calls and image pulls use the public internet, even from within AWS. Without VPC endpoints, your private ECS tasks are like workers in a secured office building who must step outside onto a public street every time they need to collect a package from ECR. VPC endpoints are a private loading dock inside the building: the traffic never leaves AWS’s network.

ECR requires three endpoints for fully private access:

EndpointTypePurpose
com.amazonaws.region.ecr.apiInterfaceECR control plane (create repos, list images)
com.amazonaws.region.ecr.dkrInterfaceDocker registry protocol (image pulls)
com.amazonaws.region.s3GatewayImage layer storage (ECR stores layers in S3)
Easy to miss

The S3 gateway endpoint is the one teams most often forget. ECR stores image layers in S3, not directly in the ECR service. Adding only the ecr.api and ecr.dkr interface endpoints will leave image pulls failing with confusing network errors.

resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.ecr.api"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.us-east-1.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = aws_route_table.private[*].id
}

Interface endpoints have an hourly cost per Availability Zone. If you already have a NAT gateway for other traffic, routing ECR through NAT may be cheaper than adding dedicated endpoints. Run the numbers for your specific workload.

When to use these settings#

Not every team needs everything from day one. Here is a practical decision guide:

SettingUse whenSkip when
Tag immutabilityDeploying by commit SHA to productionFrequently rebuilding :latest during development
Lifecycle policiesAlways; set at creation
Basic scanningAlways; it is free
Enhanced scanningProduction repos; regulated workloadsDev and scratch repositories
Repository policy (push restriction)Multiple people or systems have ECR write permissionsSingle-pipeline, single-account setups
Cross-account policyMulti-account org with a shared image registrySingle-account workloads
Cross-region replicationMulti-region ECS deploymentsSingle-region workloads
VPC endpointsECS tasks in private subnets without NATTasks already behind a NAT gateway
KMS encryptionCompliance requires customer-managed keysStandard workloads
Image signingRegulated environments; enforcing pipeline provenanceMost teams (implement basics first)

For handling secrets that your build pipeline accesses alongside ECR pushes, see Secrets in CI/CD Pipelines.

Common mistakes#

  1. Deploying with :latest. You cannot tell what is running in production, cannot roll back to a specific version, and cannot correlate an incident to a specific build. Tag every image with the commit SHA.
  2. No lifecycle policies at repository creation. After a year of CI builds, you have thousands of images: most with known CVEs, all costing storage fees. Add lifecycle policies when you create the repository, not retroactively when costs get noticed.
  3. Skipping vulnerability scanning. Basic scanning is free and takes one setting to enable. Not scanning means you are unaware of publicly-disclosed CVEs in your production images. Enable it by default; use enhanced scanning for production repositories.
  4. Open push access. Without a repository policy restricting push, any IAM principal with ecr:PutImage can push images, including developers’ local machines. Restrict push to the CI/CD role so every image has traceable build provenance.
  5. Only adding the repository policy for cross-account access. The source account repository policy alone is not sufficient. The pulling account also needs an IAM policy allowing the pull actions. Both sides must grant permission.
  6. Missing the S3 gateway endpoint in private networking setups. ECR stores image layers in S3. Adding the ecr.api and ecr.dkr interface endpoints without the S3 gateway endpoint results in image pulls failing with misleading network errors.

Summary#

Frequently asked questions

What is ECR tag immutability and should I enable it?

Tag immutability prevents pushing a new image to an existing tag. Once you push my-app:abc12345, that tag always points to the same image. Enable it for production repositories that deploy by commit SHA. Keep it disabled only if your workflow requires overwriting mutable tags like latest.

How does an ECR lifecycle policy work?

Lifecycle policies define rules for automatically deleting images. Each rule matches images by tag status and a count or age threshold. ECR evaluates rules in priority order and expires matching images. Without lifecycle policies, repositories grow indefinitely and you pay for all stored layers, including old ones with unpatched vulnerabilities.

What is the difference between ECR basic and enhanced scanning?

Basic scanning uses Clair to check OS packages on push only, and results go stale as new CVEs are published. Enhanced scanning uses Amazon Inspector, covers OS packages and language packages (npm, pip, Maven, Go modules), and re-scans continuously. Use basic for dev and non-critical repos; use enhanced for anything running in production.

What is the difference between an IAM policy and an ECR repository policy?

IAM policies are identity-based: attached to a role, they define what that principal can do. Repository policies are resource-based: attached to the ECR repository, they define who can access it, including principals from other AWS accounts. Cross-account pulls require both a repository policy granting access from the source account and an IAM policy in the pulling account.

Do I need VPC endpoints to use ECR from private subnets?

Yes, if your ECS tasks run in private subnets with no internet access. Without endpoints, ECR calls and image pulls use the public internet, which requires a NAT gateway. Interface endpoints for ecr.api and ecr.dkr, plus an S3 gateway endpoint for image layers, let private subnets pull images without a NAT gateway.

Last verified: 25 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.