Docker in CI/CD

Docker Intermediate/Advanced
Chapter 7 ยท Docker in CI/CD

๐Ÿš€ Docker in CI/CD

pipelines1-6 already built and pushed a Docker image as part of a CI pipeline. This chapter reviews that material, goes deeper on registry mechanics, and formalizes a real tagging strategy โ€” while folding in everything this course has covered since: multi-stage builds (Ch.1), optimized caching (Ch.2), and vulnerability scanning (Ch.6).

Recap: pipelines1-6's Docker-in-CI Chapter

pipelines1-6 established the core shape: build an image tagged with the commit SHA, push it to a registry, and gate that job on the test suite already passing โ€” "no image without passing tests." This chapter builds on exactly that foundation.

Container Registries

A registry is where built images actually live โ€” Docker Hub is the public default, but production systems commonly use a private registry (a cloud provider's own, or a self-hosted one like Harbor). docker login authenticates; docker push/docker pull move images in and out.

# CI step โ€” authenticating with registry credentials from CI secrets, never hardcoded echo "$REGISTRY_PASSWORD" | docker login registry.example.com -u "$REGISTRY_USER" --password-stdin

Registry credentials are themselves secrets โ€” the exact same discipline Chapter 6 established for never baking a credential into an image applies just as much to the credentials used to push that image: read from CI secrets at runtime, never hardcoded into a pipeline config file.

Tagging Strategy

pipelines1-6's commit-SHA tagging is immutable and traceable โ€” that exact tag always points to that exact commit, forever. A complete tagging strategy layers a few tag types together:

Tag TypePurposeMutable?
Commit SHA (e.g. a1b2c3d)Exact traceability to one commitNo โ€” immutable
Semantic version (e.g. v1.2.3)Marks a real releaseNo โ€” should never be reused
latestConvenience pointer to "the newest build"Yes โ€” and that's the problem
# tagging one build with multiple tags at once, on a release docker build -t myapp:a1b2c3d -t myapp:v1.2.3 -t myapp:latest . docker push myapp:a1b2c3d docker push myapp:v1.2.3 docker push myapp:latest

latest vs. an Immutable SHA Tag

latest

Mutable and ambiguous โ€” any subsequent push to latest, from any branch, silently changes what that tag points to.

Commit SHA

Permanently tied to one exact commit โ€” pulling it a year from now returns exactly the same image, every time.

Putting It Together: A Full CI Build/Push Step

# 1. Multi-stage build (Ch.1), using registry cache from the last build (Ch.2) docker build \ --cache-from myapp:latest \ -t myapp:$GIT_SHA \ -t myapp:latest \ . # 2. Vulnerability scan gate (Ch.6) โ€” fail the pipeline on critical findings trivy image --exit-code 1 --severity CRITICAL myapp:$GIT_SHA # 3. Push only after the scan passes docker push myapp:$GIT_SHA docker push myapp:latest

๐Ÿ’ป Coding Challenges

Challenge 1: Write a Multi-Tag Build Command

Write a docker build command that tags one build with a commit SHA (e5f6g7h) and a semantic version (v2.0.0), for a release build โ€” but deliberately without a latest tag.

Goal: Practice applying multiple tags to a single build in one command.

โ†’ Solution

Challenge 2: Secure the Registry Login Step

A CI config hardcodes docker login -u admin -p Sup3rSecret directly in its pipeline file. Rewrite it to follow this chapter's credential-handling guidance.

Goal: Practice applying Chapter 6's never-hardcode-credentials discipline to registry authentication specifically.

โ†’ Solution

Challenge 3: Diagnose a Production Incident

A production system is configured to always pull myapp:latest. After an unrelated build on a different branch pushed a broken image also tagged latest, production silently started running that broken build. Explain what went wrong and how this chapter's tagging strategy would have prevented it.

Goal: Practice connecting the latest-tag gotcha to a realistic, concrete incident.

โ†’ Solution

โš ๏ธ Gotcha: Never Deploy Production Based on the latest Tag

The latest tag is just a mutable pointer โ€” it doesn't mean "the newest stable release," it means "whatever was pushed to latest most recently, from wherever." A production system configured to always pull latest has no guarantee it's running anything specific at all โ€” a stray build from an unrelated branch, a hotfix that was never meant to go live, or a broken in-progress push can silently become what production is running, with no deploy event, no record, and no easy way to know exactly what's live without inspecting the image directly. Production deployments should always reference an immutable, specific tag โ€” a commit SHA or a semantic version โ€” precisely because that guarantees pulling it always returns the exact same image, verifiably, every time.

๐ŸŽฏ What's Next

The next chapter is Orchestration Beyond Compose โ€” why Compose doesn't scale to multi-host production, and a conceptual orientation to Kubernetes/Swarm as the next step.