When setting up cross-account ECR access, many developers encounter policy validation errors that aren't immediately intuitive. The key issue lies in how AWS interprets the principal field in ECR repository policies.
For cross-account access, you must use the account ID as the principal - not IAM ARNs. Here's the proper JSON structure:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_B_ID:root"
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
}
]
}
Here's a full working example that grants pull permissions to Account B (123456789012):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:DescribeImages",
"ecr:DescribeRepositories",
"ecr:ListImages"
]
}
]
}
To set this policy using AWS CLI:
aws ecr set-repository-policy \
--repository-name your-repo \
--policy-text file://policy.json \
--region us-east-1
After applying the policy, test the access from Account B:
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin ACCOUNT_A_ID.dkr.ecr.us-east-1.amazonaws.com
docker pull ACCOUNT_A_ID.dkr.ecr.us-east-1.amazonaws.com/your-repo:latest
If you still encounter problems:
- Ensure no Service Control Policies (SCPs) are blocking cross-account access
- Verify that the IAM user in Account B has permission to call ECR actions
- Check that VPC endpoints (if used) allow cross-account traffic
For more granular control, you can specify specific IAM roles rather than the entire account:
{
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:role/ecr-pull-role1",
"arn:aws:iam::123456789012:role/ecr-pull-role2"
]
}
}
Many teams encounter issues when configuring Amazon ECR repositories to allow cross-account image pulls. The AWS console's policy editor limitations often compound these problems. Here's what actually works.
The key misunderstanding comes from principal formatting. For ECR cross-account access, you must use this format:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::TARGET_ACCOUNT_ID:root"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages"
],
"Condition": {
"StringEquals": {
"aws:SourceAccount": "TARGET_ACCOUNT_ID"
}
}
}
]
}
Common mistakes include:
- Using raw account numbers instead of ARN format for principals
- Missing the
:root
suffix in the ARN - Attempting to specify individual IAM users (which requires additional AssumeRole setup)
For reliable execution, use the CLI:
aws ecr set-repository-policy \
--repository-name YOUR_REPO \
--policy-text file://ecr-policy.json \
--region YOUR_REGION
After applying the policy:
- Create an IAM role in Account B with ECR read permissions
- Assume the role and test pulling an image:
aws ecr get-login-password --region YOUR_REGION | \
docker login --username AWS --password-stdin \
SOURCE_ACCOUNT_ID.dkr.ecr.YOUR_REGION.amazonaws.com
For production environments, consider adding these security measures:
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:iam::TARGET_ACCOUNT_ID:role/YOUR_ROLE_NAME"
},
"IpAddress": {
"aws:SourceIp": ["YOUR_APPROVED_IP_RANGE"]
}
}