Docker in CI/CD
🐳 Docker in CI/CD
Why Containerize in CI
A Docker image packages the app together with its exact runtime environment — eliminating "works on my machine" at the deployment level, not just development. The same image that passed CI's tests is the exact image that gets deployed — no rebuild-and-hope-it's-identical step in between.
Building an Image as a Pipeline Step
Tagging with the git commit SHA — not just latest — means always being able to identify exactly which commit produced a given running container, critical for debugging and for the rollback techniques Chapter 7 builds on.
Multi-Stage Builds, Revisited
A "builder" stage with full dev dependencies compiles the app; a final slim stage copies only the compiled output, discarding the builder stage's bulk — the same pattern from Express/Node's deployment chapters, now reused because CI is often where these images actually get built for real.
📤 Pushing to a Container Registry
Docker Hub or GitHub Container Registry (ghcr.io) — authentication uses a registry token, Chapter 5's secrets material applied directly: never hardcode registry credentials. The registry becomes the source of truth for which built images actually exist and are available to deploy.
Tagging Strategy
SHA tags for traceability, semantic version tags for human-readable releases — both have a place; latest alone has serious limits.
Job Dependency Enforces Order
Chapter 2's dependency graph applies directly: the image-build job depends on the test job passing — a broken build never gets containerized or pushed at all.
Build-Test-Then-Build-Push, With Job Dependency
Rebuild at Deploy Time vs Build Once in CI
Rebuilding Separately
Real risk of drift — a subtly different image built at deploy time than the one CI actually tested.
Build Once, Deploy That Exact Image
The image tested in CI is the literal artifact deployed everywhere after — no rebuild, no drift.
💻 Coding Challenges
Challenge 1: Add a Semantic Version Tag Alongside SHA
Extend the docker build/push steps from this chapter's example to also tag and push the image as v1.4.0 (in addition to the SHA tag), assuming the version is known ahead of time.
Goal: Practice tagging an image with more than one identifier at once.
Challenge 2: Make the Image Job Depend on Tests
Given two separate jobs, test and build-image, with no dependency configured between them, add the correct YAML so build-image never runs if test fails.
Goal: Practice applying Chapter 2's job-dependency mechanism to this chapter's specific use case.
Challenge 3: Diagnose a Rollback Failure
A production incident requires rolling back to "the version from yesterday," but every image was tagged only as latest, and today's build already overwrote it. Explain why the team can't identify or pull back yesterday's exact image.
Goal: Practice connecting this chapter's tagging guidance to a real operational failure.
latestTagging every build only as latest makes it impossible to know which specific commit or version is actually running in production at any given moment — latest gets overwritten every single build. If a bad deploy needs rolling back, there's no reliable way to identify or pull a specific prior version, since "latest from 10 minutes ago" and "latest right now" are indistinguishable once overwritten. Always tag with something immutable and traceable — a commit SHA, a semantic version — in addition to, or instead of, latest.
🎯 What's Next
With a traceable, registry-hosted image in hand, the next chapter covers how it actually reaches production: Deployment Strategies — rolling, blue-green, and canary deployment, and automatic rollback on health-check failure.