aws ecr ci security claude-curated
ECR (Elastic Container Registry) is AWS’s managed Docker registry. Each repository is an authentication and authorization boundary, so the way repos are organised and the way CI is granted access matter for both blast radius and operational hygiene.
Repository organisation
Two common shapes:
- Per-service repos — one repo per deployable image (
payments-api,payments-worker,frontend-web). Easiest to reason about, fine-grained IAM, lifecycle policies tuned per service. - Per-team repos with image tags identifying the service. Fewer repos to manage but harder to scope IAM and lifecycle.
Per-service is the default for most teams. The cost of an extra repo is zero; the clarity is worth it.
CI permissions to push
A push from CI requires two layers of permissions.
Account-level (no resource):
ecr:GetAuthorizationToken— returns the docker login credentials for the registry.
Repo-scoped:
ecr:BatchCheckLayerAvailabilityecr:InitiateLayerUploadecr:UploadLayerPartecr:CompleteLayerUploadecr:PutImage
A least-privilege CI policy:
{
"Statement": [
{
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:REGION:ACCOUNT:repository/payments-api"
}
]
}Granting Resource: * on the push actions is a common oversight. It means any repo in the account can be written to from CI. Scope it.
Authentication
For the docker daemon:
aws ecr get-login-password --region eu-west-1 \
| docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.eu-west-1.amazonaws.comThe token is valid for 12 hours. CI typically gets a fresh token at the start of each job.
For OIDC-federated CI (GitHub Actions, GitLab, CircleCI), the CI provider trades a workload identity token for short-lived AWS credentials and never holds long-lived access keys.
Tag immutability
Set on the repo:
aws ecr put-image-tag-mutability \
--repository-name payments-api \
--image-tag-mutability IMMUTABLEWith immutability on, a tag (e.g. v1.2.3) cannot be overwritten. Pushing the same tag twice fails. This:
- Prevents the “what is actually running?” question after a force-push.
- Makes deployments truly reproducible — the digest behind a tag never silently changes.
- Means CI must use unique tags per build (commit SHA is conventional). Floating tags (
latest,main) are incompatible with immutability and should be avoided in production.
Lifecycle policies
Untagged and old images accumulate quickly. ECR charges for storage. Lifecycle policies expire images automatically.
{
"rules": [
{
"rulePriority": 1,
"description": "Expire untagged after 14 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 14
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Keep only 50 most recent tagged",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 50
},
"action": { "type": "expire" }
}
]
}Rules apply on a schedule, not on every push.
Cross-account access
Two patterns:
- Repository policy on the source ECR repo allowing the consumer account to pull. The consumer’s task execution role (see ECS Task Roles vs Execution Roles) still needs
ecr:GetAuthorizationTokenand the repo-scoped pull actions. - ECR pull-through cache for upstream registries (Docker Hub, public ECR, GHCR). The cache repo lives in your account; first pull populates from upstream, subsequent pulls are local.
Vulnerability scanning
Two tiers:
- Basic scanning — free, on-push, uses the open-source Clair database. Limited to OS package CVEs.
- Enhanced scanning — backed by Amazon Inspector. Continuous (rescans on new CVE data), covers OS packages and language-level dependencies (npm, pip, gem, maven, etc.), and integrates with Security Hub.
Enhanced is paid per image scanned. For production-bound images it is usually worth it; for ephemeral dev images, basic is fine.
Scan findings are surfaced in the ECR console and via the DescribeImageScanFindings API. Wire critical findings into Security Hub or a ticketing system rather than expecting humans to check the console.
Common gotchas
- Repo must exist before first push — ECR does not auto-create. CI fails on first run for new services unless the repo is provisioned in IaC.
- Tagging strategy mismatch with immutability — teams turn on immutability then their CI tries to re-push
:latestand breaks. - Cross-region pulls — ECR is regional. A task in
eu-west-1pulling from aus-east-1repo crosses regions and is slow plus billed for transfer. - VPC endpoints required for private subnets — tasks in subnets without NAT or internet need
com.amazonaws.REGION.ecr.api,com.amazonaws.REGION.ecr.dkr, and an S3 gateway endpoint (image layers live in S3).
See also
- ECS
- Fargate
- ECS Task Roles vs Execution Roles
- ALB Target Group Health Checks
- ECS Scheduled Tasks
- ECS Exec Remote Sessions
- Bastion