Module 5: Docker in CI/CD
1. Introduction: Docker in CI/CD
Overview
You know Docker. You build images, write efficient Dockerfiles, handle multi-stage builds, and bring up containers with docker-compose. You learned all of that in guide #15. But there's a problem: every time you make a change to your code, you open the terminal, run docker build, wait 3-5 minutes, tag manually, and push to the registry. If you work on a team, each developer has their own build process — and sometimes the images differ because the local environments are different.
This module eliminates that manual process. After this, every push to the repository triggers an automatic build: the image gets built, tagged with the commit's SHA (traceability), scanned for vulnerabilities (security), and pushed to the registry (availability) — all without human intervention.
The mindset shift: You go from "I build Docker when I remember to" to "Docker builds automatically on every push, with caching that makes it fast, tags that make it traceable, and security scanning that makes it safe."
Context: You come from Module 4, where you learned to handle secrets safely in CI. Now those secrets (like registry tokens) get used to authenticate the image push. And what you build here — images in a registry — is exactly what Module 6 needs in order to deploy automatically.
Where Are We in the Guide?
Context
This guide has 8 modules organized into 3 phases:
Phase 1: CI Fundamentals (Modules 1-3)
├── Module 1: Introduction to CI/CD and GitHub Actions ✅ Completed
├── Module 2: Automated Testing in CI ✅ Completed
└── Module 3: AI-Specific CI Checks ✅ Completed
Phase 2: CD & Deployment Pipelines (Modules 4-6)
├── Module 4: Secrets and Environment Management ✅ Completed
├── Module 5: Docker in CI/CD ← YOU ARE HERE
└── Module 6: Deployment Pipelines
Phase 3: Production Pipelines (Modules 7-8)
├── Module 7: Monitoring, Notifications and Advanced Patterns
└── Module 8: Capstone Project — Production AI Pipeline
Estimated duration of the module: 60-90 minutes.
Where are we headed?
This module is step 5 of 8. You automate the building and distribution of Docker images. The progression is deliberate:
- First you understood CI (module 1) — workflows, jobs, steps, triggers
- Then you automated testing (module 2) — pytest, matrix, caching, reports
- You added AI-specific checks (module 3) — prompt regression, cost estimation
- You handled secrets safely (module 4) — API keys, OIDC, environments
- Now you automate Docker (this module) — build, tag, scan, push
- Then you deploy (module 6) — staging → approval → production
- You add monitoring (module 7) — notifications, scheduled workflows
- You integrate everything (module 8) — a complete commit-to-production pipeline
The problem: manual Docker builds
What your Docker flow looks like today
Developer: "I pushed a fix to the embeddings endpoint"
Terminal:
$ docker build -t my-ai-app . # 3-5 minutes of waiting
$ docker tag my-ai-app:latest ghcr.io/user/my-ai-app:v1.2.3 # manual tag
$ docker push ghcr.io/user/my-ai-app:v1.2.3 # manual push
Another developer:
$ docker build -t my-ai-app . # Their build uses different cached layers
$ docker tag my-ai-app:latest ghcr.io/user/my-ai-app:v1.2.3 # Same tag, different image!
$ docker push ghcr.io/user/my-ai-app:v1.2.3 # It overwrites the previous one
Visible problems:
- 📋 A repetitive manual process — Build, tag, push, every time you change something
- 📋 Inconsistent tags — Who decided it's
v1.2.3? Which commit is that? - 📋 Non-reproducible builds — Your laptop has a cache; the CI runner doesn't
- 📋 No security verification — The image goes to the registry without a scan
- 📋 No quality guarantee — The image gets pushed even when the tests failed
What it looks like after this module
Developer: "I pushed a fix to the embeddings endpoint"
GitHub Actions (automatic):
1. Build with docker/build-push-action
2. Cache layers with the GitHub Cache backend
3. Tag: sha-abc1234 + v1.2.3 (if it's a release)
4. Scan with trivy → 0 CRITICAL vulnerabilities
5. Push to GHCR → ghcr.io/user/my-ai-app:sha-abc1234
✅ The image is available in 2 minutes. Traceable. Safe. Reproducible.
The difference: zero manual intervention, traceability by commit, security through scanning, and consistency through a centralized build.
What makes Docker in CI different from Docker locally
The key differences
| Aspect | Docker locally | Docker in CI |
|---|---|---|
| Cache | Layers on the local disk (persistent) | A clean runner on every run (no cache by default) |
| Tags | Whatever you decide | Automatic: SHA, semver, branch |
| Push | Manual (docker push) | Automatic after the build |
| Security | You decide whether to scan | Scanning is mandatory in the pipeline |
| Reproducibility | It depends on your local environment | Identical on every run (a standardized runner) |
| Trigger | When you remember | Every push/PR, automatically |
| Multi-platform | Only your architecture | amd64 + arm64 if you need it |
The most important difference: the cache
On your laptop, Docker caches layers on disk. If you only change the code (not the dependencies), the build takes seconds because the pip install layers are cached. In CI, every workflow run starts with a clean runner — there's no cache. Without explicit configuration, every build downloads the base image and reinstalls every dependency from scratch.
For an AI project with heavy dependencies (torch, transformers, langchain), this means 5-10 minute builds without a cache. With caching configured, 1-2 minutes. Configuring caching is the first priority after the basic build, not a "nice to have."
Goal of the module
By completing this module you will be able to:
- ✅ Configure
docker/build-push-actionto build images automatically in CI - ✅ Implement Docker layer caching in Actions: the GitHub Cache backend and the Registry cache backend
- ✅ Push images to GHCR with
GITHUB_TOKENand to Docker Hub with secrets - ✅ Design tagging strategies: SHA, semver, latest, branch-based
- ✅ Integrate image scanning with trivy to detect vulnerabilities in CI
- ✅ Configure multi-platform builds (amd64 + arm64) when necessary
- ✅ Build a complete Docker CI Pipeline as the module's project
The professional goal
When someone on your team pushes a change, the Docker image gets built, scanned, tagged with the commit's SHA, and pushed to the registry — without anyone opening a terminal or running a single command. If the image has critical vulnerabilities, the pipeline blocks it. That's professional Docker in CI/CD.
Module contents
The capsule map
| # | Capsule | What you'll learn | Type |
|---|---|---|---|
| 01 | Introduction (this one) | Why automate Docker, the manual problem, the vision | Intro |
| 02 | Building Docker Images in CI | docker/build-push-action, buildx, the workflow YAML | Technical |
| 03 | Docker Layer Caching in Actions | GitHub Cache vs Registry cache, configuration, impact | Technical |
| 04 | Container Registries | GHCR with GITHUB_TOKEN, Docker Hub with secrets, a comparison | Technical |
| 05 | Image Tagging Strategies | SHA, semver, latest, branch-based, rollbacks | Technical |
| 06 | Image Scanning in CI | trivy, severities, quality gates, interpretation | Technical |
| 07 | Multi-Platform Builds | amd64 + arm64, QEMU, when you need it | Technical |
| 08 | Project: Docker CI Pipeline | The complete pipeline: build → tag → scan → push | Project |
The learning flow
First you build a basic build in CI (capsule 02). Then you make it fast with caching (capsule 03). Then you configure where to push the image (capsule 04). You learn to tag for traceability and rollbacks (capsule 05). You integrate security scanning to detect vulnerabilities (capsule 06). Optionally, you add multi-platform for different architectures (capsule 07). Finally, you integrate everything into a complete pipeline (capsule 08).
The progression is: build → optimization → distribution → traceability → security → project.
Estimated duration of the module: 60-90 minutes.
What carries over from Module 4
Your current pipeline (after Module 4) handles secrets safely:
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
quality-gate:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -r requirements-dev.txt
- name: Lint
run: ruff check src/ tests/
- name: Type check
run: mypy src/ --ignore-missing-imports
- name: Tests
run: pytest tests/ -v --tb=short
- name: Prompt regression
run: python scripts/evaluate_prompts.py
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Cost estimation
run: python scripts/estimate_costs.py
Functional. Automated tests, AI-specific checks, safe secrets. But one crucial step is missing: the Docker image. Your code passes every check, but does it build correctly in Docker? Is the resulting image safe? Is it available in a registry for deployment?
What this module adds
# What you add AFTER the quality gate
docker:
runs-on: ubuntu-latest
needs: quality-gate
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
One new job: build, cache, tag, push. Automatic. Traceable. Fast.
Connection with the guide's project
This module's project: Docker CI Pipeline
The mini-project builds a complete pipeline:
- Build — It builds the image with
docker/build-push-action - Cache — The GitHub Cache backend for fast builds
- Tag — The commit's SHA + semver for releases
- Scan — trivy to detect vulnerabilities
- Push — To GHCR with authentication via GITHUB_TOKEN
Push / PR
↓
┌──────────────────────────────────────┐
│ Job: Docker CI Pipeline │
│ │
│ 1. Checkout + Setup Buildx │
│ 2. Login to GHCR │
│ 3. Build image (with cache) │
│ 4. Tag: sha-abc1234 + v1.2.3 │
│ 5. Scan with trivy │
│ 6. Push to GHCR │
│ │
│ If scan CRITICAL → ❌ Block push │
│ If scan clean → ✅ Push to registry │
└──────────────────────────────────────┘
Connection with later modules
Module 4: Secrets management (tokens for registries)
↓
Module 5: Docker CI Pipeline (build + push images) ← YOU ARE HERE
↓
Module 6: Deployment Pipelines (deploys the image to the server)
↓
Module 8: Capstone pipeline (lint → test → build → push → deploy)
The image this pipeline produces is the one Module 6 deploys to staging and production. Without an image in a registry, there's nothing to deploy.
Prerequisites
- ✅ Modules 1-4 completed: Workflows, testing, AI checks, secrets management
- ✅ Docker fundamentals (guide #15): Dockerfile, docker build, multi-stage builds, docker-compose
- ✅ A GitHub account: For GHCR (included for free)
- ✅ Intermediate Python: Functions, classes, REST APIs
If you don't have these prerequisites
| What you're missing | Recommended resource |
|---|---|
| Docker basics | Docker Essentials Guide (#15, NIEVA) |
| GitHub Actions | Modules 1-2 of this guide |
| Secrets in CI | Module 4 of this guide |
Technical setup
The module's file structure
your-ai-project/
├── .github/
│ └── workflows/
│ ├── ci.yml # Quality gate (Modules 1-4)
│ └── docker.yml # Docker CI Pipeline (this module)
├── Dockerfile # Your existing Dockerfile
├── .dockerignore # Excluding unnecessary files
├── src/
│ └── ai_app/
│ ├── __init__.py
│ ├── main.py # FastAPI app
│ └── chain.py # LangChain pipeline
├── tests/
│ └── test_chain.py
├── requirements.txt
└── requirements-dev.txt
Quick verification
# Verify that Docker works
docker --version
# Expected output: Docker version 24.x or higher
# Verify that your Dockerfile builds
docker build -t test:local .
# Expected output: a successful build
# Verify that you have a repo on GitHub
git remote -v
# Expected output: origin https://github.com/your-user/your-repo.git
If all three commands work, you're ready for the module.
What this module does NOT cover
- ❌ Docker basics: How to write a Dockerfile, multi-stage builds, docker-compose — that's guide #15
- ❌ Deployment: How to deploy the image to a server — that's Module 6
- ❌ Kubernetes: Container orchestration — that's guide #17
- ❌ Docker in local development: docker-compose for development — guide #15
- ❌ Custom registries: AWS ECR, Google Artifact Registry — mentioned but not configured
Analogy: The automated production line
Imagine a car factory. In an artisan workshop, each mechanic assembles the whole car by hand: installs the engine, paints the body, verifies everything works, and parks it in the lot. It's slow, inconsistent, and depends on who does it.
In a modern factory, the production line automates everything: the chassis enters, it gets welded automatically, painted by robots, passes through quality control, and comes out to the lot — every car the same as the previous one, traceable by serial number, inspected by sensors.
Your manual Docker build is the artisan workshop. Docker in CI/CD is the production line:
- The automatic build = the robotic welding (always consistent)
- The layer cache = the pre-fabricated parts (don't redo what didn't change)
- The SHA tags = the serial number (you know exactly what each unit is)
- Trivy scanning = quality control (it detects defects before delivery)
- The push to the registry = parking in the lot (available for the buyer)
The mindset shift
Before this module
Developer: "I finished the embeddings feature"
Developer: *opens the terminal, runs docker build*
Developer: *waits 4 minutes*
Developer: *tags it manually as v1.3.0*
Developer: *runs docker push*
Developer: *tells the team on Slack*
Ops: *downloads the image, verifies that it works*
Ops: *deploys manually*
Total time: 15-30 minutes
Confidence: "it should work"
Traceability: "I think it's today's version"
After this module
Developer: "I finished the embeddings feature"
Developer: *git push*
GitHub Actions (automatic, 2 minutes):
✅ Tests pass
✅ Image built (cache → 45 seconds)
✅ Tag: sha-abc1234 + main + latest
✅ Scan: 0 CRITICAL vulnerabilities
✅ Push to GHCR
✅ Summary in the workflow
Ops: *sees the image in Packages, knows exactly which commit it is*
Total time: 2 minutes (automatic)
Confidence: "the tests passed, the scan is clean"
Traceability: "commit abc1234, March 8th, 2:34 PM"
That's the difference between manual Docker and Docker in CI/CD.
Quick self-assessment
Before starting the capsules, verify that you have the necessary context:
- What does
docker build -t myapp .do? (guide #15) - What is a multi-stage build in Docker? (guide #15)
- How do secrets work in GitHub Actions? (Module 4)
- What is the
GITHUB_TOKEN? (Module 4) - What is an artifact in GitHub Actions? (Module 2)
If any question sounds completely new, review the corresponding module or guide before continuing.
Evidence of success
By the end of this module, you should be able to:
- Configure a workflow that builds Docker images automatically on every push
- Implement caching that reduces build time from 5+ minutes to < 2 minutes
- Push images to GHCR using GITHUB_TOKEN (without configuring extra secrets)
- Tag images with the commit's SHA for full traceability
- Integrate trivy to scan for vulnerabilities before the push
- Explain when multi-platform builds are necessary and when they aren't
- Build the project's complete Docker CI Pipeline
If you tick every check → you're ready for Module 6.
Summary
- Manual Docker builds are the problem: Slow, inconsistent, no traceability, no security
- Docker in CI automates it: Build, tag, scan, push — on every push, with no intervention
- The cache is the first priority: Without a cache, 5-10 min builds; with a cache, 1-2 min
- Tags = traceability: The commit's SHA tells you exactly what code is running in production
- Security scanning = prevention: Detecting vulnerabilities before the push, not after the deploy
- GHCR is the natural default: Authentication with GITHUB_TOKEN, the same ecosystem as Actions
- The image you build here is the one Module 6 deploys to staging and production
Additional resources
- docker/build-push-action — The official action for building and pushing Docker images
- GitHub Container Registry Docs — GHCR's documentation
- Docker Layer Caching in CI — How Docker's cache works
- trivy — Container Scanner — A vulnerability scanner for images
- GitHub Actions — Publishing Docker Images — GitHub's official guide
- Docker Essentials Guide (#15, NIEVA) — Prerequisite: Docker fundamentals