Amazon ECR
The managed registry where your container images live so ECS, App Runner, Lambda, and EC2 can pull them. Here's when ECR earns its place, what it costs, which role does the pulling, and the registry mistakes Design Beaver catches as you draw.
What ECR is
ECR is AWS’s own container registry. You push a built image to a repository, and your compute pulls it by URI when it deploys or starts a task. ECS and Fargate, App Runner, Lambda container functions, and plain Docker on EC2 all pull from it. It stores other OCI artifacts too, like Helm charts.
Past storage, it does the things you’d otherwise bolt on: vulnerability scanning on push, lifecycle policies that expire old images, tag immutability, cross-region and cross-account replication, and a pull-through cache that mirrors upstream registries like Docker Hub into your private registry.
Design Beaver models ECR as a regional, API-accessed registry rather than a subnet-resident resource, and it validates as you draw which services pull from it and what that pull actually needs.
When to use ECR (and when not to)
Reach for ECR when you’re already running containers on AWS and you want the registry next to the compute. IAM controls access, so there’s no second set of registry credentials to rotate. Scan-on-push, lifecycle cleanup, and replication come with it. If Docker Hub rate limits are breaking your builds, a pull-through cache rule solves that without changing how your images are referenced.
Don’t add it to an architecture that isn’t running containers — a registry only earns its place when something pulls images from it. It isn’t an object store for arbitrary files, and it isn’t a CDN. ECR serves the registry protocol, not a cacheable website.
Variants: private and public registries
The real choice is one registry type. Everything else — scan-on-push, tag immutability, lifecycle rules — is a per-repository toggle inside whichever you pick.
| Option | What it is |
|---|---|
| ECR Privatedefault | Per-account, per-region private registry. Access is controlled by IAM (identity policies) and per-repository resource policies. This is what ECS/Fargate, App Runner, and Lambda container functions pull from in a typical private architecture. Supports scan-on-push, lifecycle policies, tag immutability, and cross-region/cross-account replication. |
| ECR Public (Public Gallery) | A public registry (gallery.ecr.aws) for images you want anyone to pull anonymously, similar to Docker Hub public repos. Distinct pricing and data-transfer rules from private. Also usable as an upstream source for a pull-through cache rule. |
ECR pricing in plain English
You pay for what you store and what leaves the region. Pulls to same-region compute cost nothing, which makes co-location the single easiest saving.
Pay per GB of images stored per month, plus data-transfer-out charges. Data transfer IN is free; transfer between ECR and other AWS services in the same region (EC2, Lambda, Fargate, App Runner) is free. Cross-region and internet egress are charged.
| Option | Representative rate |
|---|---|
| private | ~$0.10/GB-month stored. New accounts get 500 MB/month free for 12 months. |
| public | 50 GB/month always-free storage. Egress to non-AWS destinations free up to 500 GB/month anonymous or 5 TB/month when authenticated with an AWS account; standard AWS data-transfer-out rates apply above those thresholds. |
Keeping the bill down
- Use lifecycle policies to expire old/untagged image versions automatically instead of paying to store every build forever
- Keep compute in the same region as the repository — same-region pulls to EC2/Lambda/Fargate/App Runner have no data-transfer charge
- Use pull-through cache instead of re-pulling upstream public images repeatedly across many builds
us-east-1 (rates vary by region). Rates as of 2026-09. Verify at the official pricing page before using for real cost estimates — this list is not kept in sync with AWS pricing changes.
How ECR connects to other services
Every ECR edge is a pull, and every pull is done by the platform — the ECS agent, the App Runner service, the Lambda service, or a Docker client using an instance profile. Your application code is not calling ECR through the SDK.
ECS (on EC2 or Fargate) pulls the container image from an ECR private repository when it starts a task, using the image URI in the task definition (aws_account_id.dkr.ecr.region.amazonaws.com/repo:tag)
App Runner deploys a service from a container image stored in an ECR private repository, pulling the image when the service is created or a new image is deployed
A Lambda function packaged as a container image (up to 10 GB) is deployed from an ECR private repository — during function create/update, the Lambda service pulls the image from ECR, optimizes it, and deploys it
An application or CI job running Docker on an EC2 instance pulls (or pushes) images to an ECR repository with `docker pull` / `docker push` after authenticating to the registry
Compute in a private subnet reaches ECR privately through AWS PrivateLink interface VPC endpoints, with no internet gateway or NAT needed for the pull
That distinction decides where the permission goes, and it’s the thing people get wrong:
- ECS and Fargate pull with the task execution role, not the task role. It needs
ecr:GetAuthorizationToken,ecr:BatchGetImage, andecr:GetDownloadUrlForLayer. - App Runner pulls with an access role trusting
build.apprunner.amazonaws.com. The managed policyAWSAppRunnerServicePolicyForECRAccesscovers it. - Lambda container functions are pulled by the Lambda service at deploy time.
- EC2 running Docker authenticates and pulls using the instance profile.
One detail that breaks hand-written policies: ecr:GetAuthorizationToken is registry-wide and must be granted on Resource: *. The auth token isn’t scoped to a single repository, so a policy that carefully limits it to one repo ARN just fails.
What ECR can’t connect to
A registry looks like it should plug into more things than it does. Design Beaver flags the edges that don’t exist rather than letting them onto the canvas.
Route 53 is DNS; ECR is not a DNS target you point a domain at. Image pulls go to the AWS-managed registry endpoint (aws_account_id.dkr.ecr.region.amazonaws.com) using the Docker registry protocol with AWS authentication — you don't front a registry with a custom Route 53 record, so there is no architectural edge between them.
A relational database and a container image registry have no native integration. Images are not stored in, pulled from, or otherwise exchanged with RDS — an application would use ECR (for its image) and RDS (for its data) independently, never as a direct connection.
CloudFront is a CDN for cacheable HTTP content; ECR is not a CloudFront origin. Container image pulls use the OCI/Docker registry protocol with per-request AWS auth (and store layers in S3), not cacheable HTTP objects delivered through an edge network, so ECR is not fronted by CloudFront.
Anti-patterns Design Beaver catches
These three account for most ECR incidents — two break deploys, one quietly inflates the bill.
ECS/Fargate tasks in a private subnet pulling from ECR with no NAT gateway and no VPC endpoints
Why it breaksThe image pull needs to reach the ECR API, the ECR Docker endpoint, and S3 (for layers); with no route to any of them the task fails with a CannotPullContainer error
Do this insteadAdd interface VPC endpoints for ecr.api and ecr.dkr plus an S3 gateway endpoint, or give the subnet egress via a NAT gateway
Putting ECR pull permissions on the ECS task role instead of the task execution role
Why it breaksThe ECS container agent pulls the image using the execution role before your container runs; permissions on the task role (used by your running code) don't help the agent fetch the image
Do this insteadGrant ecr:GetAuthorizationToken / ecr:BatchGetImage / ecr:GetDownloadUrlForLayer on the task execution role
Never expiring old images, so every CI build accumulates in the repository forever
Why it breaksYou pay per-GB storage for images nothing will ever pull again, and untagged layers pile up
Do this insteadAdd a lifecycle policy to expire untagged images and cap how many tagged versions are retained
Gotchas that bite in production
- Private-subnet pulls need three endpoints.
ecr.api,ecr.dkr, and the S3 gateway endpoint, because layers live in S3. Miss the S3 one and the manifest fetch succeeds while the layer download hangs — the confusing failure where connectivity “looks fine.” - Execution role, not task role. The ECS agent fetches the image before your container starts. ECR permissions on the task role produce
CannotPullContainerevery time. GetAuthorizationTokencan’t be scoped. Grant it onResource: *. Scoping it to a repository ARN is a policy that will never authorize a pull.- Images accumulate silently. Without a lifecycle policy every CI build stays forever, and you keep paying to store images nothing will pull again.
Further reading
- Official AWS documentation
- Using Amazon ECR images with Amazon ECS
- Amazon ECR interface VPC endpoints (PrivateLink)
Frequently asked questions
When should you use Amazon ECR?
Why does an ECS task fail with CannotPullContainer when pulling from ECR?
Which VPC endpoints do you need to pull from ECR in a private subnet?
ecr.api (the ECR API) and ecr.dkr (Docker pull and push), plus an S3 gateway endpoint, because ECR stores image layers in S3. Forgetting the S3 endpoint is the classic failure — the manifest fetch succeeds and the layer download hangs, so the pull fails even though ECR connectivity looks fine.How much does Amazon ECR cost?
Can CloudFront serve container images from ECR?
Validate your ECR architecture as you draw
Design Beaver checks your AWS design in real time — missing queues, invalid connections, and security anti-patterns, caught before you ship. It’s live in beta, free, and runs in your browser. Sign in with Google or GitHub to save your work.
Sign up for free! →Prefer email? Get new features in your inbox: