If you've ever had a 2 AM page because a pod in ap-southeast-1 couldn't pull an image fast enough, or you've watched a deploy stall because your dev account is quietly trying to pull from a registry three regions away — you already understand the problem this post is about.
Amazon ECR is great at storing and serving container images. But the moment your infrastructure grows past "one region, one account," you run into a very familiar wall: images live in one place, and your workloads don't.
You could solve this with a cron job that pulls and re-pushes images on a schedule. People have done it. It works, right up until someone forgets to update the script, or a region gets missed, or you're debugging why staging has a stale tag at 11 PM on a Friday.
The better answer is ECR's built-in replication feature — and honestly, once it's set up, you mostly forget it's there, which is exactly what you want from infrastructure.
This guide walks through two real scenarios: replicating across regions, and replicating across accounts. Then we'll get into the production-readiness details that tend to get skipped in a first pass — because "it worked in the demo" and "it survives a security review" are two very different bars.
A Few Things Worth Knowing Before You Touch the Console
A handful of details will save you a confused debugging session later:
- Replication is registry-wide, not per-repo. You're configuring a policy on the whole registry, then narrowing it down with filters — not toggling replication on individual repositories one by one.
- It's asynchronous. Push an image, and it won't show up in the destination instantly. Give it a few minutes before you assume something's broken.
- Private repositories only. ECR Public isn't part of this story.
- You pay for storage everywhere you replicate to. Convenience has a line item.
- It only replicates new pushes. Existing images stay put. If you're rolling this out to a registry with years of history in it, you'll need to backfill manually — more on that below.
With that out of the way, let's build something.
Scenario 1: Cross-Region Replication
The Situation
You're running workloads in multiple regions — maybe us-east-1 for your primary traffic and ap-southeast-1 because half your users are in APAC and pulling images across the Pacific was adding real latency to every deploy. Or maybe you just want a warm copy of your images sitting in a second region in case something goes sideways with your primary.
Here's the shape of it:
Source Registry (us-east-1)
│
│ ECR Replication Rule
▼
Destination Registry (ap-southeast-1)
Destination Registry (eu-west-1)
One rule, living on the source registry, fanning out to as many regions as you need — all within the same account.
Setting It Up
Through the console: head to ECR → Private registry → Registry settings → Replication → Edit → Add rule. Choose "This account," pick your destination regions, and optionally scope it down with a prefix filter (e.g., only replicate things under prod/).
Through the CLI, it's a single command:
aws ecr put-replication-configuration \
--replication-configuration '{
"rules": [
{
"destinations": [
{ "region": "ap-southeast-1", "registryId": "YOUR_ACCOUNT_ID" },
{ "region": "eu-west-1", "registryId": "YOUR_ACCOUNT_ID" }
],
"repositoryFilters": [
{ "filter": "prod/", "filterType": "PREFIX_MATCH" }
]
}
]
}' \
--region us-east-1
Swap in your actual account ID, obviously.
Prove It Works
Check the config landed:
aws ecr describe-registry --region us-east-1
Push something:
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
docker tag my-app:latest \
YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/prod/my-app:latest
docker push \
YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/prod/my-app:latest
Then wait a beat — a couple of minutes, not seconds — and check the destination:
aws ecr describe-images \
--repository-name prod/my-app \
--region ap-southeast-1
If it's there, you're done. If it's not there yet, that's usually just the async delay talking, not a broken config.
Scenario 2: Cross-Account Replication
The Situation
This one shows up in almost every org that's matured past "everything in one AWS account." You've split things out — dev, staging, prod, maybe a dedicated shared-services account that owns your container registry as a single source of truth. Now your workload accounts need to pull from it without you granting them broad cross-account access to everything.
Source Account (111111111111) — us-east-1
│
│ ECR Replication Rule + Registry Policy
▼
Destination Account (222222222222) — us-east-1
This one's got two moving parts instead of one, because trust has to flow in both directions: the source account needs a rule saying where to replicate to, and the destination account needs a policy saying it's actually okay with that.
Step 1: Open the Door on the Destination Account
Authenticated as the destination account (222222222222), grant the source account permission to push in:
aws ecr put-registry-policy \
--policy-text '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountReplication",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:root" },
"Action": [ "ecr:CreateRepository", "ecr:ReplicateImage" ],
"Resource": "*"
}
]
}' \
--region us-east-1
Step 2: Point the Source Account at It
Now, authenticated as the source account (111111111111), configure the replication rule:
aws ecr put-replication-configuration \
--replication-configuration '{
"rules": [
{
"destinations": [
{ "region": "us-east-1", "registryId": "222222222222" }
],
"repositoryFilters": [
{ "filter": "shared/", "filterType": "PREFIX_MATCH" }
]
}
]
}' \
--region us-east-1
Step 3: Double-Check the Policy Landed
aws ecr get-registry-policy --region us-east-1
Confirm the principal is scoped to exactly who you expect — no surprises here is a good thing.
Step 4 & 5: Push, Then Verify
Push from the source:
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
111111111111.dkr.ecr.us-east-1.amazonaws.com
docker tag my-service:v1.0 \
111111111111.dkr.ecr.us-east-1.amazonaws.com/shared/my-service:v1.0
docker push \
111111111111.dkr.ecr.us-east-1.amazonaws.com/shared/my-service:v1.0
Then check the destination account:
aws ecr describe-images \
--repository-name shared/my-service \
--region us-east-1 \
--profile destination-account-profile
If it shows up, your shared-services registry pattern is live.
Combining Both: New Account and New Region
You don't have to pick one or the other — a single rule can send images to a different account and a different region in the same breath:
aws ecr put-replication-configuration \
--replication-configuration '{
"rules": [
{
"destinations": [
{ "region": "ap-southeast-1", "registryId": "222222222222" }
],
"repositoryFilters": [
{ "filter": "prod/", "filterType": "PREFIX_MATCH" }
]
}
]
}' \
--region us-east-1
Same requirement applies: the destination account still needs to have granted permission via its registry policy, exactly as in Scenario 2.
Making This Production-Ready
Getting replication working is the easy 80%. The last 20% is what keeps it from becoming a liability. Here's what's worth doing before you call this "done":
Scope it with filters. Don't replicate your whole registry by default — that includes every throwaway dev image and half-finished feature branch build. A prod/ prefix filter keeps replication (and your storage bill) focused on what actually matters.
Backfill before you flip the switch. Replication only catches new pushes going forward. If there's existing history that needs to exist in the destination, you'll need to re-push it manually or script a copy using batch-get-image / put-image.
Keep registry policies tight. It's tempting to use "*" as the Principal and move on. Don't. Scope it to the exact source account or, better, a specific IAM role.
Turn on image scanning everywhere, not just the source. A vulnerability in the original image is still a vulnerability in every replicated copy. Enhanced scanning on the destination is a second set of eyes, not a redundant one.
Make tags immutable. Otherwise a v1.2.0 tag can drift between source and destination over time, which defeats a good chunk of the point of replicating in the first place.
Wire up alerting. ECR fires events to EventBridge on replication success and failure. Route the failures to Slack or email — you want to know within minutes, not when someone notices a stale image three weeks later.
Set lifecycle policies on the destination. Replicated images pile up fast. Expire untagged images after a week, cap tagged image history, and your storage costs stay sane.
Use customer-managed KMS keys for anything sensitive, and make sure the replication service role actually has permission to use the destination account's key — this is a common silent failure point.
Keep naming and tagging consistent across accounts so cost allocation and governance don't turn into archaeology.
Turn on CloudTrail everywhere. Push, pull, and replicate events are exactly the kind of thing you want a paper trail for when something goes wrong.
When Something's Not Replicating
A few things to check, roughly in order of how often they're actually the culprit:
-
Wrong prefix. The repository name has to match your filter exactly.
Prod/andprod/are not the same thing. - Not enough patience. Give it 2–5 minutes before assuming it's broken.
-
Rule didn't save. Run
aws ecr describe-registryand confirm the rule is actually there. -
"Access Denied" on cross-account: almost always a registry policy issue — either
ecr:CreateRepositoryorecr:ReplicateImageis missing, or the Principal doesn't exactly match the source account ID. - Terraform shows no diff, but nothing's replicating: Terraform only manages the rule, not individual image copies. It'll apply cleanly even if nothing has actually replicated yet — push a new image and check again.
Wrapping Up
ECR replication is one of those features that's boring in the best way — set it up once, get the guardrails right, and it just quietly does its job in the background. Whether you're chasing latency across regions or drawing clean lines between accounts in a multi-account setup, it removes a whole category of manual, error-prone image-shuffling from your workflow.
The setup itself takes minutes. The best practices are what make it something you can trust in production — so it's worth spending the extra hour getting those right before you call it done.
Top comments (0)