Support

Lesson 3 of 6 ·13 min read

Registries, Releases, and CI/CD

The bridge between building an image and running it in production.

Fast answer: A registry is GitHub for built images: you docker push an image to it, and your production server docker pulls that exact image back down. Add automated tests and versioned tags in front of the push and you have CI/CD without the mystique: the image that passed your tests is, byte for byte, the image that goes live.

The problem: your image is on your laptop

After Lesson 1 you can build myapp locally. Your production server has never heard of it. Emailing a 400 MB file around is obviously wrong, and rebuilding from source on the server reintroduces the very "different machine, different result" problem Docker exists to kill.

The registry is the missing piece:

Developer
   │  docker build
   ▼
Image
   │  docker push
   ▼
Registry            (Docker Hub, GitHub Container Registry, AWS ECR)
   │  docker pull
   ▼
Production server
   │
   ▼
Running container

Docker Hub is the default public registry; GitHub Container Registry and Amazon ECR are the ones you are most likely to meet at work. They all do the same job: store versioned images where deployment environments can fetch them.

Tags: naming a release

Every push carries a tag:

Terminal
docker build -t myorg/myapp:1.4 .
docker push myorg/myapp:1.4

Tags turn images into versioned deployment artifacts: 1.4 today, 1.5 next week, and rollback means starting the previous tag rather than rebuilding anything.

One tag deserves suspicion: latest. It is a floating pointer, and it can point at different bytes tomorrow than it does today. "What is actually running in production?" should never have the answer "whatever latest meant at the time". Serious pipelines tag with a version number or the Git commit SHA, so every running container traces back to an exact commit.

Multi-stage builds: ship less

Building an application often needs tools that running it does not: compilers, dev dependencies, the source tree. A naive Dockerfile ships all of it to production, making the image larger and its attack surface wider. A multi-stage build says: use one temporary stage to build, then copy only the results into a clean final stage.

Dockerfile
# Stage 1: build
FROM python:3.12 AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt

# Stage 2: production
FROM python:3.12-slim

WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .

CMD ["python", "app.py"]

The magic line is COPY --from=builder: it lifts the installed packages out of the first stage, and everything else about that stage is thrown away. The final image starts from python:3.12-slim and contains only what production needs. Smaller image, faster pulls, fewer tools for an attacker to find, which is where Lesson 4 picks up.

CI/CD without the mystique

Wire the registry into automation and you get the pipeline everyone talks about:

commit
  ↓
tests run                 (CI: is this change trustworthy?)
  ↓
image built + tagged
  ↓
pushed to registry
  ↓
deployment pulls that exact image
  ↓
health checks pass → traffic moves    (CD: release it repeatably)
  ↓
or: roll back to the previous tag

Strip the vendor logos and CI/CD is four properties: the same steps run on every change, every release has an identity, failures stop the rollout, and the previous release stays one command away. GitHub Actions, GitLab CI, and the rest are just places to write that sequence down.

The payoff of the whole chain is a sentence worth memorising: the artifact you tested is the artifact you deployed. Not "the same code, rebuilt later, hopefully identically". The same bytes.

Try it: design a release you can identify

Imagine commit a84f2c1 passes its tests. Write down the image name you would push, the image reference production should deploy, and the one-line rollback action.

A sound answer: push myorg/myapp:a84f2c1, deploy that exact tag (or its immutable digest), and roll back by selecting the previously successful tag. No rebuilding is required during the incident.

If CI/CD still sounds like machinery

Treat a release like a sealed, labelled parcel. CI checks the contents and seals it. The registry stores it. CD delivers that same parcel. A tag tells you which parcel arrived; a rollback sends the previous sealed parcel instead.

Check your understanding

Answer before opening each explanation.

Why does myapp:latest make a production incident harder to diagnose?

Hint: Does that name always identify the same bytes?

Answer: No. latest is a movable tag, so the configuration does not uniquely identify a build. A version, commit SHA, or digest lets the team trace the running image to its code and test run.

Why does the first stage of the multi-stage build not reach production?

Hint: Look at the second FROM.

Answer: The second FROM begins a clean final image. Only the installed output explicitly copied with COPY --from=builder crosses the boundary; the builder and its extra tools are discarded.

Which action breaks the tested-artifact guarantee?

Hint: Which path creates a second artifact?

Answer: Rebuilding on the production server. Even from the same source, dependency or environment changes can produce different bytes. Pushing and later pulling the already-tested image preserves the guarantee.

What does a commit-SHA tag add to rollback?

Hint: Think speed and identity during an incident.

Answer: It preserves a known, already-built release with an exact identity. Rollback becomes selecting that artifact, rather than changing source and hoping a fresh build recreates the previous result.