14 · Container registries & ECR
Your CD pipeline built an image — now it needs a home the cloud can pull from. A container registry (Docker Hub, GHCR, AWS ECR) is that home. Pushing to AWS ECR is a fixed three-step dance: log in, tag the image with the registry's address, then push.
A container registry stores and serves your Docker images so any machine — a teammate's laptop, a CI runner, a cloud server — can `docker pull` the exact image you built. The big options are Docker Hub (public default), GHCR (GitHub-native), and AWS ECR (in your AWS account, next to where you deploy). Pushing to ECR is a fixed three-step flow: `aws ecr get-login-password | docker login` (authenticate), `docker tag` (name the image with the registry's full address), then `docker push`. Tag with the immutable git SHA (or a content digest) so a deployed version is exactly traceable.
Without this:
Without a registry, an image lives only on the machine that built it — the cloud server has no way to get the exact bytes you tested, so you'd be rebuilding (and risking a different result) on every target instead of pulling one verified artifact everywhere.
Lesson 13's CD pipeline ended with docker push — but push to where? The answer is a container registry: a server whose whole job is to store Docker images and hand them out when something runs docker pull. It's to images what GitHub is to git repos — the shared place a built artifact lives so any machine can fetch the exact bytes, not a fresh rebuild.
Three registries cover almost every team:
- Docker Hub — the default public registry (
docker pushwith no host goes here). Great for open base images (python:3.11-slimlives here); free public repos, paid private ones. - GHCR (GitHub Container Registry) —
ghcr.io. Lives next to your code on GitHub, authenticates with the same token your Actions workflow already has. The natural choice when CI/CD is GitHub Actions. - AWS ECR (Elastic Container Registry) —
<account>.dkr.ecr.<region>.amazonaws.com. Lives inside your AWS account, right next to ECS/Beanstalk/SageMaker where you'll deploy. Private by default, IAM-controlled. The default when you deploy on AWS.
The ECR push flow is a fixed three-step dance. ECR is private, so before anything you must authenticate. ECR doesn't take a static password — you fetch a short-lived token and pipe it into docker login:
- Log in.
aws ecr get-login-password --region <r> | docker login --username AWS --password-stdin <registry-url>. TheawsCLI mints a temporary token;--password-stdinfeeds it to Docker without it ever touching your shell history. - Tag. A local image is named like
churn-api:abc123. ECR needs the image named with its full registry address:docker tag churn-api:abc123 <registry-url>/churn-api:abc123. The tag IS the destination address —docker pushreads it to know where to send the bytes. - Push.
docker push <registry-url>/churn-api:abc123uploads the layers. Layers already in the registry are skipped, so repeat pushes are fast.
The order is not interchangeable: you can't push to a registry you haven't logged into, and you can't push an image that isn't tagged with the registry's address. Login → tag → push.
Immutable tags vs digests. A tag like :latest is a movable label — repush and it points somewhere new, so "latest" is a moving target you can't pin a deploy to. Tag with the git SHA instead (:abc123) and you get an immutable, traceable name — that tag always means this build. Even stronger is the digest (@sha256:…), a hash of the image's actual content: pull by digest and you are guaranteed the byte-for-byte identical image, no matter what any tag was later moved to point at. Production deploys reference the SHA tag or the digest, never bare :latest.
The ECR push flow as an ASCII diagram + commands: (1) `aws ecr get-login-password | docker login`, (2) `docker tag` the local image with the registry's full address, (3) `docker push` — tagged with the immutable git SHA, never bare `:latest`.
What is the correct order of steps to push a Docker image to AWS ECR?
- A container registry (Docker Hub, GHCR, AWS ECR) stores built images so any machine can `docker pull` the exact bytes — the handoff point between 'built' and 'running'.
- The ECR push flow is a fixed order: log in (`aws ecr get-login-password | docker login`), `docker tag` with the registry's full address, then `docker push` — login → tag → push.
- Tag with the immutable git SHA (or pin by `@sha256:` digest) for a traceable, reproducible deploy — never reference a movable `:latest` tag in production.
Every cloud-deployed model service pulls its container from a registry — typically ECR when on AWS — tagged with the git SHA, so the running endpoint is traceable to an exact commit and the cloud runs the same image CI verified.
If you remove it: The image would live only on the build machine, so the cloud would have to rebuild from source on every target — risking a different result than the one you tested.