ECR
Amazon Elastic Container Registry (ECR) stores container images so that Kubernetes and Lambda can pull them. The platform keeps every image in one artifacts account, tags them immutably, and lets the workload accounts pull but only the CI account push.
What it does
A repository in ECR holds the versions of one image. Each version is a manifest identified by a digest and referenced by one or more tags. With tag immutability on, a tag can be added to a manifest but never moved to a different one, so main-a1b2c3d always means the same build. Scan on push checks each new image for known vulnerabilities. A lifecycle policy expires old images automatically. A repository policy grants other accounts pull or push rights. Images are encrypted at rest.
How BuiltForProd uses it
The ecr unit (registry module terraform-aws-modules/ecr 3.2.1 wrapped in modules/ecr, listed by core/artifacts/us-west-2) creates the repositories in core-artifacts. It ships with enable_ecr = false and is switched on when the code repositories are deployed; until then no repository exists. Two repositories are defined:
| Repository | Image | Pulled by |
|---|---|---|
acme-aws-blueprint-webapp | The Flask API image built by the web application code repository | EKS nodes in every plat-* account |
acme-aws-blueprint-etl-lambda | The container image for the ETL trigger function | The Lambda service in every plat-* account |
Every repository has the same settings: IMMUTABLE tags, scan on push, KMS encryption, and a three-rule lifecycle policy. Untagged images are expired after 14 days. Release images, the ones carrying a v tag, are kept indefinitely. Only integration builds, tagged main-<sha>, age out, and the last 30 of those are kept. The order matters: ECR expires an image by at most the first rule that claims it, and a release image is a promoted main-<sha> manifest with the release tag added, so it matches both rules and the release rule wins. The repository policy grants read to the four plat-* accounts and to Lambda in those accounts, and read-write to core-auto, where the GitHub OIDC deployer roles for the code repositories carry an ecr_push policy scoped to their own repository.
Immutability is what makes the delivery model work. Each image is built once on main and pushed as main-<short sha>; promotion adds a release tag to the same manifest rather than rebuilding; nothing ever pushes latest. Production runs the exact bytes that were tested in staging, and the prod promotion workflow checks that the release tag exists in ECR before opening the rollout pull request. The ETL blueprint records the deployed tag in Parameter Store and seeds a bootstrap tag once, because its Lambda module cannot reference a moving tag.
Images are scanned twice before and once after they land: the code repositories' CI runs a Trivy scan that fails on fixable HIGH and CRITICAL vulnerabilities, ECR scans on push, and Inspector, when switched on, rescans stored images as new vulnerabilities are published (about $0.09 per image). Images run as a fixed non-root user, UID 10001, matching the Kubernetes chart's security context.
Terms you will see
| Term | Meaning |
|---|---|
| Repository | The ECR container for one image's versions. |
| Manifest and digest | The image content and its content hash; the thing a tag points at. |
| Tag immutability | A tag, once set, cannot be pointed at a different manifest. |
| Scan on push | Vulnerability scanning of each image as it arrives. |
| Lifecycle policy | Rules that expire old or untagged images; release tags are exempt. |
| Repository policy | The resource policy granting other accounts pull or push. |
main-<sha> and release tag | The build tag and the promotion tag on the same image. |
Where to read more
- Web App Blueprint overview and Data and ETL Blueprint overview for the images and their pipelines.
- Docker for how the images are built.
- GitHub OIDC for the roles allowed to push.