Skip to content

ECR and container images

2 min read · main part only; folded sections are optional

After this you can answer: Where do the images come from, and how does a task know which one to run?

flowchart TB
CD["CD publish job"] -->|"push, tag = git SHA"| ECR[("ECR: api, web, worker")]
SSM["SSM /minitickets/dev/image-tag"] --> TD["task definition<br/>image = repo:SHA"]
ECR -->|"pull at task start"| TASK["ECS task"]
TD --> TASK
ECR -->|"pull"| LAMBDA["worker and tickets Lambdas"]
  1. CD builds once, for ARM64, and pushes minitickets-dev-api:<sha>, -web:<sha> and -worker:<sha>.
  2. Tags are immutable. Pushing a different image under an existing tag fails. A SHA always means the same bytes.
  3. The deploy writes the tag to the SSM parameter /minitickets/dev/image-tag. Pulumi reads it and puts it into each task definition.
  4. ECS pulls the image with the task execution role when a task starts. Lambda pulls the worker image when the function is created or updated.

The three repositories come from one list, imageServices, in infra/src/names.ts → imageServices, and are made in infra/src/registry.ts → createRegistry.

  • scanOnPush runs a basic vulnerability scan on every image.
  • A lifecycle policy keeps the newest 10 images tagged deployed-*, the newest 30 other tagged images, and deletes untagged ones after 1 day.
  • The worker repository has a repository policy that lets the Lambda service pull it, only for functions named minitickets-dev-*.

The images must be ARM64, because the tasks run on ARM (Graviton), which is cheaper.

From → toWhat happensWhat allows it
API ECS service → ECR repositoriesonly when computeEnabled = trueECS pulls the api image when a task starts
  • iam api execution role: AmazonECSTaskExecutionRolePolicy (ECR pull, CloudWatch Logs write)createIamRoles
  • route public route table: 0.0.0.0/0 → minitickets-dev-igw. The task has a public IP; there is no NAT gateway and no VPC endpoint.createNetwork
  • sg-rule api-sg outbound rule minitickets-dev-api-out-all: all traffic to 0.0.0.0/0createSecurityGroups
Web ECS service → ECR repositoriesonly when entryEnabled = trueECS pulls the web image when a task starts
  • iam web execution role: AmazonECSTaskExecutionRolePolicy (ECR pull, CloudWatch Logs write)createWebIamRole
  • route public route table: 0.0.0.0/0 → minitickets-dev-igw. The task has a public IP; there is no NAT gateway and no VPC endpoint.createNetwork
  • sg-rule web-sg outbound rule minitickets-dev-web-out-all: all traffic to 0.0.0.0/0createSecurityGroups
Worker Lambda → ECR repositoriesonly when computeEnabled = truethe Lambda service pulls the worker image when the function is created or updated
  • resource-policy repository policy LambdaPullWorkerImage on minitickets-dev-worker: lambda.amazonaws.com may ecr:BatchGetImage and ecr:GetDownloadUrlForLayer when aws:SourceArn is function:minitickets-dev-*allowLambdaPull
Tickets Lambda → ECR repositoriesonly when computeEnabled = truethe Lambda service pulls the same worker image for the tickets function
  • resource-policy the same repository policy: function:minitickets-dev-* matches minitickets-dev-ticketsallowLambdaPull
CD publish job → ECR repositoriesdocker push api, web and worker :<git sha>
  • iam publish role push-images: ecr:GetAuthorizationToken (*), and push/pull actions on the 3 repositories onlypublishPolicyDocument
CD deploy job → ECR repositorieschecks the tag exists and adds the tag deployed-<sha>
  • iam deploy role OurEcrRepos: ecr:* on the 3 repository ARNscreateCicd
See the repositories on the map →

ECR → Private registry → Repositories → minitickets-dev-api → Images: tags, pushed at, scan status.

SymptomCauseWhere to look
Deploy fails at “Check the image exists in ECR”The publish job did not push that tagPublish job log; ECR images
Tasks stop at once: exec format errorImage built for x86_64, task runs ARM64Image manifest; runtimePlatform
CannotPullContainerError, no log streamExecution role lacks the managed policyIAM → execution role
Why immutable tags instead of latest?

With latest, two tasks started a minute apart could run different code, and a rollback would not know what “before” was. A SHA tag makes every running task traceable to one commit.

What is the deployed- tag for?

After a successful deploy, CD adds a deployed-<sha> tag. The lifecycle policy keeps the newest 10 of those, so a rollback target is never deleted by the cleanup of other images.

What if the scan finds something?

ECR’s basic scan reports findings; it does not block a push. The blocking scan runs earlier, in CI, with Trivy: it fails the build on fixable critical findings.

Advanced: The worker repository policy

Lambda pulls container images with the Lambda service principal, so the worker repository needs a resource policy, LambdaPullWorkerImage, with an aws:SourceArn condition naming our function ARNs. Without it, creating or updating the worker Lambda fails with an access error on the image.

Answer these without scrolling up. If one feels hard, reread that section.

  1. What is an image tag in MiniTickets, and why can it never change?
  2. Where does the running tag come from?
  3. Which role pulls the image for an ECS task?
  4. Why must the images be ARM64?
  5. What does the lifecycle policy keep, and why?

Redraw it from memory: CD pushing to ECR, the SSM tag feeding the task definition, ECS and Lambda pulling

Try this: on the map, select ECR repositories and find who may pull from them in the Permissions tab

Read this book online: https://book.theversehub.in/