ECR's advertised price is $0.10 per GB per month for storage. That's what every guide leads with, and that's where most of them stop. But if you've ever seen a bigger-than-expected AWS bill and traced it back to containers, the charges were probably hiding on a different line: NAT Gateway, Amazon Inspector, or KMS. None of those line items say "ECR."
Amazon ECR pricing has six billing dimensions, and three of them don't appear under the ECR service on your bill.
By the end of this guide, you'll understand what every ECR-related charge corresponds to, have real cost breakdowns for common architectures, and have CDK code you can drop in today to cap the costs that grow automatically. If you want to model your own numbers while you read, use the ECR pricing calculator. All pricing reflects US East (N. Virginia) rates from the Amazon ECR pricing page as of March 2026.
ECR Pricing at a Glance (Quick Reference)
Before going deep on any one dimension, here's the full picture. This table is the one I wish every ECR cost guide started with.
| Cost Dimension | Rate | Notes |
|---|---|---|
| Private storage | $0.10/GB/month | Compressed layer storage |
| Public storage | $0.10/GB/month | 50 GB/month always free |
| Same-region data transfer | $0.00 | Free to EC2, ECS, EKS, Fargate, Lambda, App Runner |
| Cross-region data transfer | $0.09/GB | Applies to Cross-Region Replication source |
| Internet data transfer (first 1 GB) | $0.00 | |
| Internet data transfer (up to 10 TB) | $0.09/GB | Tiered; see full rates below |
| NAT Gateway data processing | $0.045/GB | Only if pulling via NAT, not via VPC endpoints |
| Amazon Inspector (initial scan) | $0.09/image | Enhanced scanning only; basic scanning is free |
| Amazon Inspector (rescan) | $0.01/rescan | Continual scanning mode only |
| KMS encryption | $0.03/10,000 API calls | Only if KMS enabled; default AES-256 is free |
| Managed signing | Check aws.amazon.com/ecr/pricing/ | AWS Signer pricing applies |
For the most common pattern (single-region CI/CD with ECS or Fargate pulling from ECR in the same region), your only bill item is storage. Data transfer is $0.00. The table above starts to matter as soon as you add private subnets, Inspector scanning, multi-region deployments, or KMS encryption.
The quickest way to understand the bill is to understand how ECR calculates storage first - because the storage model is less obvious than it looks.
How ECR Storage Billing Actually Works
ECR bills for the total compressed size of unique image layers in your repository, not for the number of images you've pushed. This distinction matters more than most guides acknowledge. The AWS re:Post Q&A thread that ranks in the top 10 for "amazon ecr pricing" exists specifically because this is never explained clearly anywhere.
Storage is metered on compressed layer size. And layers shared between images are stored once, not once per image.
What "Per GB" Really Means (Layer Deduplication)
Every Docker image is a stack of layers, each identified by a SHA256 content hash. If that hash already exists in your repository, ECR does not store it again. You pay for the bytes once, regardless of how many images reference that layer.
A concrete example: you push a 500 MB image 100 times with no changes. You pay for 500 MB, not 50 GB. Now you update only the application layer (50 MB) while the base OS layer stays the same (450 MB). After the update, you're paying for 500 MB total: the original base layer (450 MB, still shared) plus the new application layer (50 MB). Not 1 GB as if both image versions were stored in full.
Since January 2026, this deduplication extends across repositories in the same private registry via cross-repository layer sharing (blob mounting). Before that, two repositories using the same Node.js base image stored that base layer twice. Now it's stored once. More on this in the optimization section.
To audit actual storage in any repository:
aws ecr describe-images \
--repository-name <your-repo-name> \
--query 'imageDetails[*].[imagePushedAt,imageSizeInBytes,imageTags]' \
--output table
The imageSizeInBytes field shows compressed layer storage per image. Add them up and you have your repository's billed storage (minus shared layers, which you won't be double-counted for).
Private vs. Public Repository Storage
Both private and public repositories charge the same $0.10/GB/month storage rate. The difference is entirely in the free tier and data transfer behavior.
Public repositories get 50 GB/month of storage free, with no 12-month expiry. Private repositories get 500 MB/month free, but only for the first 12 months of a new account. Beyond those free tiers, the per-GB rate is identical.
Data transfer from public repositories is free up to 500 GB/month for anonymous users and 5 TB/month for authenticated AWS accounts. Private repositories have no equivalent free transfer tier for internet access.
For internal workloads, always use private repositories. Public repositories are for open-source software you want widely accessible without requiring an AWS account to download.
The ECR Free Tier: What It Covers and What It Doesn't
Private repositories: 500 MB/month free for the first 12 months after account creation. After month 12, every byte is billed at $0.10/GB. The free usage applies monthly and does not accumulate - unused free storage in one month does not roll over.
Public repositories: 50 GB/month free, always. No expiry. Authenticated AWS accounts also get 5 TB/month free data transfer to the internet from public repos.
As of July 15, 2025, new accounts receive up to $200 in AWS Free Tier credits, usable within 12 months.
Here's the thing about the 500 MB private free tier: a single Node.js application image with its dependencies regularly exceeds 200-300 MB compressed. A Java service with a bundled JRE can hit 500 MB on its own. For production workloads, assume you're outside the free tier from day one.
Data Transfer Costs: Where "Free" Ends
The boundary between free and paid ECR data transfer is clean: if traffic stays within a single AWS region, it's free. If it crosses a region boundary or goes to the internet, you pay. Pushes (data transfer into ECR) are always free regardless of source.
Most teams pay $0.00 in ECR data transfer because most workloads follow the same-region pattern. But there's a catch that adds a hidden cost even when ECR transfer itself is free - I'll cover that in the next section.
Same-Region Pulls Are Free (But There's a Catch)
Pulling images from ECR to any of the following services in the same region costs nothing on the ECR bill:
- ECS on EC2
- ECS on Fargate
- EKS
- AWS Lambda
- AWS App Runner
This is the most important ECR pricing fact for teams running standard AWS container workloads. If your compute and your registry are in the same region, data transfer is $0.00.
The catch: if your compute runs in a private subnet and uses a NAT Gateway to reach ECR (instead of VPC endpoints), you pay NAT Gateway data processing at $0.045/GB on top of the $0.00 ECR transfer cost. That's the hidden line item. The charge appears under NAT Gateway data processing charges in Cost Explorer, not under ECR, which is why it goes unnoticed.
Cross-Region and Internet Transfer Rates
Cross-region transfers (for example, ECS in us-west-2 pulling images from an ECR repository in us-east-1) cost $0.09/GB. Same rate applies to images replicated via Cross-Region Replication from the source region.
Internet transfer tiers for data out from ECR private repositories:
| Volume | Rate |
|---|---|
| First 1 GB/month | $0.00 |
| Up to 10 TB/month | $0.09/GB |
| Next 40 TB/month | $0.085/GB |
| Next 100 TB/month | $0.07/GB |
| Over 150 TB/month | $0.05/GB |
These rates aggregate across EC2, EBS, S3, and other services toward the tiered thresholds - it's account-level aggregation, not per-service.
One important note: the $0.09/GB rate applies to standard US regions. Rates are higher in some regions, including Africa, Asia Pacific, South America, and GovCloud. Always check the live ECR pricing page for your specific region before estimating costs.
Multi-Region Deployments and Replication
If you run workloads in multiple regions, there are two approaches to image distribution. The math heavily favors replication once you pull more than once per region per month.
Pattern A - No replication (cross-region pulls on every deployment): Each pull from a remote region costs $0.09/GB. For a 500 MB image pulled 100 times per month to us-west-2 from us-east-1: 50 GB x $0.09 = $4.50/month in data transfer alone.
Pattern B - Cross-Region Replication: One-time replication cost ($0.09/GB from source) plus storage in each destination region ($0.10/GB/month) plus $0.00 per local pull after that. Same 500 MB image replicated once to us-west-2: $0.045 replication + $0.05 storage/month + $0.00 per pull.
Replication pays off within the first pull cycle for any image accessed more than once in the destination region. For high-frequency deployments pulling the same image dozens of times per day, the difference is substantial.
The ECR-to-ECR pull-through cache (announced March 2025) is a newer alternative that automatically syncs images between ECR registries across regions and accounts, which reduces the need for manual replication configuration.
The Hidden Costs on Your ECR Bill
Storage and data transfer are the visible costs - they appear on the ECR service line and are documented clearly. The costs in this section are documented too, but they appear on different service lines, which is why they catch teams off guard.
There are four of them. NAT Gateway data processing is the biggest for teams running private subnets. Amazon Inspector is the most commonly missed because it shows up on a completely different service. KMS and managed signing are smaller but worth knowing about before you enable them.
NAT Gateway Data Processing ($0.045/GB)
If your ECS tasks or EKS nodes live in a private subnet and pull ECR images, the traffic routes through a NAT Gateway by default. NAT Gateways charge $0.045/GB for every byte they process - on top of whatever the ECR transfer cost is.
For same-region pulls (normally $0.00): traffic through NAT costs $0.045/GB. That charge appears under "Amazon Virtual Private Cloud" in Cost Explorer, not under ECR.
The math at common pull volumes:
| Monthly ECR Pull Volume | NAT Processing Cost | Annual NAT Cost |
|---|---|---|
| 10 GB/month | $0.45 | $5.40 |
| 100 GB/month | $4.50 | $54.00 |
| 500 GB/month | $22.50 | $270.00 |
| 1 TB/month | $45.00 | $540.00 |
The fix is deploying three VPC endpoints to route ECR traffic directly without touching the NAT Gateway. I'll cover the setup in the optimization section. But first, it helps to understand why you need three.
The reason you need three endpoints is how ECR works internally. The Docker Registry API (ecr.dkr) handles push and pull operations. The ECR management API (ecr.api) handles operations like GetAuthorizationToken and DescribeImages. And ECR stores actual image layer data in Amazon S3 - so an S3 Gateway endpoint is required to route that layer traffic. Skip the S3 endpoint and image pulls still fail even with the two ECR interface endpoints in place.
For teams running EKS, the EKS cost optimization best practices guide explicitly recommends VPC endpoints to avoid cross-AZ NAT Gateway costs for ECR image pulls. This is worth checking if your NAT Gateway charges are distributed across multiple Availability Zones.
Amazon Inspector Enhanced Scanning
ECR's basic scanning is free. It runs OS-level CVE checks when triggered manually or on push, uses AWS native technology, and costs nothing.
Enhanced scanning is different. It integrates with Amazon Inspector, runs deeper package-level vulnerability analysis, and the cost does not appear on your ECR bill - it shows up under Amazon Inspector.
Initial scan when an image is pushed: $0.09 per image. Rescan (triggered by new CVE database updates in continual scanning mode): $0.01 per rescan per image.
The rescan cost is where things can get expensive quietly. In continual scanning mode, every retained image is rescanned each time the vulnerability database updates. If you have 500 retained images and rescans happen 15 times per month:
- Initial scans for new images: depends on push volume
- Rescans: 500 images x 15 rescans x $0.01 = $75/month
At 1,500 total images with 15 rescans per month, that's $225/month in rescans alone. For a team that enabled enhanced scanning, ran it for a few months, and assumed costs stayed low, this is the charge that generates confusion.
On-push mode eliminates rescans entirely: $0.09 per image pushed, no ongoing charges. You lose real-time CVE updates between pushes, but for teams with aggressive deployment cadences (pushing new images frequently), on-push mode provides fresh scans on every deploy at lower cost.
Check the Amazon Inspector pricing page for your region-specific rates and for on-demand CI/CD scanning pricing ($0.03/image, with 25 free one-time assessments per account).
KMS Encryption Charges
By default, ECR uses AES-256 encryption managed by Amazon S3. This is free.
Enabling KMS encryption adds AWS KMS API call charges at $0.03 per 10,000 requests. AWS KMS provides 20,000 free API calls per month, so for most ECR workloads (pushing and pulling images a few hundred times daily), the added cost stays within the free tier or stays negligible.
Where KMS charges become visible: very high-frequency CI/CD pipelines pushing and pulling images thousands of times per day across many repositories. Each push and pull generates multiple KMS API calls. At that volume, the $0.03/10,000 rate adds up.
If you use a customer-managed KMS key for ECR rather than the AWS managed key, add $1/month for key storage. See the AWS KMS pricing guide for the full breakdown on key types and their cost implications.
Managed Signing (AWS Signer)
Introduced in November 2025, ECR managed signing automatically generates cryptographic signatures when images are pushed. It uses AWS Signer for key material and certificate lifecycle management.
AWS Signer pricing applies for signing operations. The specific per-signature rate wasn't fully captured in the pricing page data at research time - check aws.amazon.com/ecr/pricing/ and aws.amazon.com/signer/pricing/ for current rates before factoring this into your budget.
All signing operations are logged in CloudTrail. If you have CloudTrail data events enabled at scale, that adds a secondary cost consideration.
The use case is supply chain security and compliance: verify that only cryptographically signed images are deployed to production environments. For teams with those requirements, the cost is worth understanding upfront.
Real-World Cost Examples
The pricing model makes more sense with concrete numbers. Here are three patterns that cover most team setups, with all the math visible.
Single-Region CI/CD on ECS Fargate
Setup: Images built in CodeBuild, pushed to ECR, pulled by ECS Fargate. 10 repositories, lifecycle policy keeping the last 10 images per repo, 500 MB average image size.
| Component | Calculation | Cost |
|---|---|---|
| Storage | 10 repos x 10 images x 0.5 GB x $0.10 | $5.00/month |
| Data transfer | Same-region Fargate pulls | $0.00 |
| NAT Gateway | VPC endpoints or public subnets | $0.00 |
| Inspector | Basic scanning only | $0.00 |
| KMS | Default AES-256 | $0.00 |
| Total | $5.00/month |
For this pattern, storage is the entire bill. Lifecycle policies are the only cost lever. I've seen teams running dozens of services this way pay $8-15/month total once they implement proper lifecycle rules.
For the compute side of this pattern, see the Fargate pricing guide - that's the other cost component in a Fargate-based workflow.
The Unbounded Storage Problem (Large Org Without Lifecycle Policies)
This is the scenario I keep running into with teams that are surprised by their ECR bill.
Setup: 20 repositories, 10 image pushes per day per repository, 500 MB average image size, no lifecycle policies.
| Month | New Storage Added | Cumulative Storage | Monthly Cost |
|---|---|---|---|
| 1 | 20 repos x 10 pushes x 30 days x 0.5 GB = 3,000 GB | 3,000 GB | $300 |
| 2 | 3,000 GB more | 6,000 GB | $600 |
| 6 | 3,000 GB more | 18,000 GB | $1,800 |
After implementing a lifecycle policy keeping the last 10 images: 20 repos x 10 images x 0.5 GB = 100 GB stored = $10/month, steady state.
That's a $290/month reduction by month 2. The longer the team waited, the worse it got.
Storage is the only ECR cost dimension that grows automatically with no human action required. Every CI/CD build adds to it. Without a lifecycle policy, it compounds indefinitely. This is why the Well-Architected Container Build Lens recommends "implementing deletion policies based on image count limits or age limits" as a baseline requirement, not an optional optimization.
Multi-Region Active-Active Deployment
Setup: 50 GB of images in us-east-1, Cross-Region Replication to eu-west-1.
With replication:
| Component | Calculation | Cost |
|---|---|---|
| One-time replication | 50 GB x $0.09 | $4.50 (once) |
| Storage in us-east-1 | 50 GB x $0.10 | $5.00/month |
| Storage in eu-west-1 | 50 GB x $0.10 | $5.00/month |
| Local pulls in each region | Free | $0.00 |
| Ongoing monthly cost | $10.00/month |
Without replication (direct cross-region pulls): Every pull from eu-west-1 to us-east-1 costs $0.09/GB. Five full image set pulls per month: 5 x 50 GB x $0.09 = $22.50/month in transfer alone, on top of $5.00 storage.
Replication pays for itself on the first pull cycle if you pull the full image set more than once in the destination region per month. For active production deployments pulling images dozens of times daily, the math is not close.
How to Cut Your ECR Costs
Now that you understand what each cost is and where it comes from, here's what to actually do about it. Ordered by impact.
Lifecycle Policies (Highest Impact)
If you do nothing else from this article, add lifecycle policies. They prevent the unbounded storage growth that turns a $10/month ECR bill into a $500/month surprise.
A lifecycle policy can expire images by count, age, or last pull time. The standard starting point: keep the last 10 tagged images, delete untagged images after 1 day.
JSON policy you can apply directly:
{
"rules": [
{
"rulePriority": 1,
"description": "Keep last 10 images",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["*"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Delete untagged images after 1 day",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 1
},
"action": { "type": "expire" }
}
]
}
The same logic in CDK TypeScript:
import * as ecr from 'aws-cdk-lib/aws-ecr';
import { Duration } from 'aws-cdk-lib';
// Keep last 10 images for all tags
repository.addLifecycleRule({
maxImageCount: 10,
});
// Delete untagged images after 1 day
repository.addLifecycleRule({
tagStatus: ecr.TagStatus.UNTAGGED,
maxImageAge: Duration.days(1),
});
Before applying a policy to production, use the AWS Console "Test lifecycle policy" feature to preview which images the rules would expire. It runs the evaluation without making any changes.
A note on limits: each lifecycle policy supports up to 50 rules. That's enough for any reasonable cleanup strategy.
See the ECR lifecycle policy documentation for the full parameter reference, including the sinceImagePulled option that's useful for compliance scenarios where you want to retain recently-pulled images regardless of age.
If you want automated enforcement that flags repositories missing lifecycle policies before a stack deploys, CloudBurn catches this pattern in CI as part of its cost rules for IaC.
VPC Endpoints to Eliminate NAT Charges
If your compute runs in private subnets, check whether ECR traffic is routing through a NAT Gateway. If it is, three VPC endpoints remove that cost:
com.amazonaws.[region].ecr.dkr(interface endpoint) - handles Docker push/pull operationscom.amazonaws.[region].ecr.api(interface endpoint) - handles management API calls- S3 Gateway endpoint - routes image layer data (ECR stores layers in S3)
The security group for the two interface endpoints must allow inbound on port 443 from your private subnet CIDR range.
Cost of the endpoints: the S3 Gateway endpoint is free. Each interface endpoint costs $0.01/AZ/hour. For a single-AZ setup: 2 endpoints x $0.01 x 24 hours x 30 days = $14.40/month.
Break-even: if you're pulling more than 320 GB/month through NAT ($14.40 / $0.045), VPC endpoints pay for themselves in that month. At higher pull volumes, the savings compound quickly.
For the full VPC endpoint setup, configuration options, and IAM policy requirements, see the ECR VPC endpoint setup guide. For EKS teams, the EKS cluster costs covers the cross-AZ aspect of this problem, since NAT Gateway charges for EKS also aggregate across Availability Zones.
Two caveats worth knowing: pull-through cache rules still require internet access for the first pull from an upstream registry. Windows images with Microsoft foreign layers also require internet access regardless of endpoint configuration.
Image Size Optimization
Smaller images reduce storage at $0.10/GB/month and reduce pull time during deployments. The techniques that consistently produce the biggest size reductions:
Multi-stage Docker builds separate your build environment from your runtime image. Your CI tools, compilers, and test dependencies stay in the build stage and never end up in the image that ships to ECR.
Smaller base images make a bigger difference than most people expect. Switching from ubuntu:latest (about 77 MB compressed) to alpine:latest (about 3 MB) or a Distroless image can cut base layer size by 90%.
Layer ordering for cache efficiency reduces what gets re-uploaded on each push. Place rarely-changing layers early (base image, system packages, dependency installation) and frequently-changing layers late (application code). Docker layer caching means unchanged layers aren't re-uploaded, and ECR's content-addressed storage means layers with the same digest aren't billed again.
Chain RUN instructions to avoid intermediate layers: RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* creates one layer instead of three.
One Windows-specific note: mcr.microsoft.com/windows/servercore is approximately 1.7 GiB compressed in ECR. Windows container workloads carry significantly higher storage overhead than equivalent Linux images, and that base layer cost applies to every repository that uses it (unless blob mounting eliminates duplication across repos - see the next section).
ECR Archive Storage Class (November 2025)
For teams with compliance requirements to retain images for years but rarely access them, ECR introduced an archive storage class in November 2025.
Archived images cannot be pulled until restored. Restoration takes under 20 minutes. They don't count toward the per-repository 100,000-image limit, which matters for teams with large historical backlogs.
The key constraint: 90-day minimum storage duration. Delete an archived image before 90 days and you're charged for the full 90 days. This makes the archive class suitable for genuine long-term retention, not for images you might want back soon.
Archival can be triggered manually or via lifecycle policies based on last pull time. All operations are logged in CloudTrail.
The per-GB rate for archived storage wasn't fully captured in the research data - check aws.amazon.com/ecr/pricing/ for current archived storage pricing. For compliance-driven retention of inactive images, this feature is worth evaluating against your current storage cost.
Cross-Repository Layer Sharing (January 2026)
Before January 2026, ECR deduplicated layers within a single repository. The same base layer used across 50 different microservice repositories was stored 50 times.
As of January 2026, ECR stores each unique layer once across all repositories in the same private registry. If 50 of your microservices share a common Python or Node.js base image, that base layer is stored once and referenced by all 50 repositories. The storage cost reflects one copy, not 50.
This happens automatically. You don't need to change how you build or push images - ECR handles the deduplication.
For teams with many microservices sharing a base image, this can meaningfully reduce your storage footprint. Run the describe-images CLI command across your repositories to see how much of your current storage is in shared base layers and estimate the savings.
One note: the billing mechanic (whether storage is charged once per unique layer or per repository reference) wasn't explicitly confirmed in the announcement documentation. Check the What's New announcement for the current billing clarification.
Pull-Through Cache to Avoid Upstream Limits
If you're hitting Docker Hub pull rate limits or want to avoid direct upstream registry dependencies, ECR's pull-through cache can help.
Pull-through cache rules cache images from Docker Hub, ECR Public, Quay, Azure Container Registry, GitHub Container Registry, GitLab Container Registry, and the Kubernetes registry in your private ECR registry. After the initial pull, subsequent pulls use your cached copy and can route through VPC endpoints.
The March 2025 ECR-to-ECR pull-through cache extends this to syncing between ECR private registries across accounts and regions, which replaces manual cross-region image copying workflows.
Cost consideration: cached images add to your ECR storage bill at the same $0.10/GB/month rate. Factor in the storage overhead when deciding whether to cache large public images. The tradeoff is usually worth it if you're paying for Docker Hub rate limit overages or if upstream registry availability affects your deployment reliability.
CloudBurn
Catch Expensive ECR Patterns Before They Ship
CloudBurn scans your Terraform and CloudFormation for missing lifecycle policies, unoptimized image configurations, and costly ECR patterns directly in CI. Open source, installs in seconds.
ECR Pricing vs. Docker Hub vs. GitHub Container Registry
This question comes up when teams are evaluating their container workflow before committing to a registry. The short answer: ECR is the right default for AWS-native workloads. The nuance is in the details.
Amazon ECR charges $0.10/GB/month for storage with no pull rate limits and native IAM integration. Same-region transfers to AWS compute are free. The hidden costs (NAT Gateway, Inspector) apply based on your architecture choices, not by default.
Docker Hub Pro includes unlimited storage and a flat pull limit of 5,000 pulls per day. The per-seat pricing means costs scale with team size rather than usage volume. No native IAM integration - credentials are managed separately. Data transfer from Docker Hub to AWS compute is charged as regular internet egress, which means every CI/CD pipeline pull from Docker Hub costs something.
GitHub Container Registry (GHCR) is effectively included for GitHub Teams and Enterprise users. Storage and bandwidth come out of your GitHub Actions minutes and storage quotas. No pull rate limits for authenticated org members. No IAM integration - access is managed via GitHub tokens and package permissions.
| Feature | Amazon ECR | Docker Hub (Pro) | GitHub Container Registry |
|---|---|---|---|
| Storage pricing | $0.10/GB/month | Unlimited (included in plan) | Included in GitHub plan |
| Data transfer (AWS same-region) | Free | Charged as egress | Charged as egress |
| Data transfer (internet) | $0.09-$0.05/GB (tiered) | Included up to plan limits | Included in plan |
| Pull rate limits | None | 5,000 pulls/day (Pro) | None for org members |
| IAM integration | Native | None | GitHub Actions only |
| Private repos | Unlimited | Included in plan | Included in plan |
Decision framework:
- AWS-native workload (ECS, EKS, Lambda): ECR. The IAM integration, zero-transfer-cost same-region pulls, and tight service integrations outweigh the per-GB storage cost.
- GitHub-primary, not AWS-locked: GHCR. If you're already paying for GitHub Teams or Enterprise, you're already paying for registry storage.
- Public open-source images: ECR Public (50 GB free) or Docker Hub (free plan). Both work well for open-source distribution.
Verify current Docker Hub and GHCR pricing from hub.docker.com/billing and GitHub Packages docs before making budget projections - rates for both services have changed frequently.
Key Takeaways
Amazon ECR pricing is more predictable once you know where all six charges appear.
-
ECR storage is billed per compressed unique layer, not per image count. Layers shared across images are stored once. Since January 2026, this deduplication extends across repositories in the same registry. Your real storage footprint is smaller than you might think - but it still grows with every build unless you set lifecycle policies.
-
Same-region data transfer is free. If your ECS, EKS, or Fargate workloads pull images from ECR in the same region, you pay nothing for data transfer. Cross-region and internet transfers start at $0.09/GB.
-
The surprising charges appear on other bill lines. NAT Gateway data processing ($0.045/GB) shows up under VPC if you pull via NAT in a private subnet. Amazon Inspector charges show up under Inspector if you enabled enhanced scanning. KMS charges show up under KMS if you use customer-managed encryption. None of these say "ECR."
-
Lifecycle policies are not optional. Without them, storage compounds indefinitely with every CI/CD build. The CDK code in this article takes five minutes to add and prevents the most common ECR billing surprise.
-
VPC endpoints for ECR require three of them (ecr.dkr + ecr.api + S3 Gateway) to fully route ECR traffic without touching the NAT Gateway. The break-even is around 320 GB/month in pull volume for a single-AZ setup.
Start here: Run this command against any repository you suspect is accumulating images:
aws ecr describe-images \
--repository-name <your-repo-name> \
--query 'length(imageDetails)' \
--output text
If the count is higher than you expected, lifecycle policies should be your first action. Then check your NAT Gateway data transfer in Cost Explorer (filter by NAT Gateway, group by resource ID) to see whether VPC endpoints would pay off for your pull volumes.
If you're using the full NAT Gateway cost breakdown to evaluate this more carefully, the NAT Gateway pricing article walks through the per-hour charges and cross-AZ costs that stack on top of the data processing fees covered here.