The path
Upstream images → pull-through cache / curated base repos → CI build → SBOM + scan
→ sign → ECR (immutable tags) → replicate → deploy (EKS/ECS) → verify signature → runtime monitoring
1. Repository and access design
| Decision | Common choice |
|---|---|
| Account | Central "shared services" ECR account; workload accounts pull via repository policies (org condition) |
| Naming | team/app repositories; base/ for curated base images |
| Write access | Only CI roles (OIDC federated) can push |
| Network | VPC endpoints ecr.api, ecr.dkr + S3 gateway endpoint |
| Replication | Cross-region replication to regions where workloads run (and DR region) |
Repository policy example (pull from any account in the organisation):
{
"Statement": [{
"Sid": "OrgPull",
"Effect": "Allow",
"Principal": "*",
"Action": ["ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"],
"Condition": {"StringEquals": {"aws:PrincipalOrgID": "o-abc123xyz"}}
}]
}
2. Base images through pull-through cache
aws ecr create-pull-through-cache-rule --ecr-repository-prefix docker-hub \
--upstream-registry-url registry-1.docker.io --credential-arn <secrets-manager-arn>
aws ecr create-pull-through-cache-rule --ecr-repository-prefix ecr-public --upstream-registry-url public.ecr.aws
Builds use <account>.dkr.ecr.<region>.amazonaws.com/docker-hub/library/python:3.12-slim. Benefits: upstream rate limits avoided, availability during upstream outages, scanning of base images, an audit trail. Pin versions; rebuild on base updates.
3. Scanning
- Amazon Inspector enhanced scanning for ECR: continuous scanning of images on push and when new CVEs are published; covers OS and language packages.
- CI scanning before push (Inspector SBOM generator / Trivy) to fail builds early.
- Findings routed to Security Hub; owners and SLAs by severity.
4. Immutable tags and lifecycle
aws ecr put-image-tag-mutability --repository-name team/orders-api --image-tag-mutability IMMUTABLE
aws ecr put-lifecycle-policy --repository-name team/orders-api --lifecycle-policy-text '{
"rules": [{"rulePriority": 1, "description": "expire untagged after 7 days",
"selection": {"tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7},
"action": {"type": "expire"}}]}'
Deploy by digest or immutable version tag. Never latest in production.
5. Sign and verify
- Sign in CI with Notation + AWS Signer plugin (signing profile per environment/team).
- Verify at admission on EKS (Kyverno
verifyImageswith the Notation/AWS Signer setup, or Ratify + Gatekeeper). ECS: verify in the deployment pipeline before updating the service, since ECS has no admission webhook. - Combine with a policy allowing only your ECR registries.
6. Promotion
Build once, promote the same digest: copy or retag into a prod repository that only the release role can write, or rely on signatures with environment-specific signing profiles.
7. Runtime
GuardDuty runtime monitoring for EKS/ECS, Inspector findings on running images, and an inventory of which images (digests) run where, so a new CVE can be mapped to workloads in minutes.
Design review questions
- Can a workload pull an image from Docker Hub directly? (It shouldn't.)
- Who can push to production repositories?
- How long from a critical CVE in a base image to all affected services redeployed?
- What stops an unsigned image from running?
Warning
Mutable tags undo every other control: a re-pushed
v1.4.2silently changes what runs on the next scale-out. Turn on tag immutability before anything else.
Checklist
- Central ECR with org-scoped pull and CI-only push
- Pull-through cache for upstream registries
- Inspector enhanced scanning + CI scanning, findings owned
- Immutable tags and lifecycle policies
- Signing in CI, verification at deploy
- Image inventory for runtime CVE mapping