Module 6: Deployment Pipelines
1. Introduction: Deployment Pipelines
Overview
Your pipeline works. Every push to the repository triggers automated tests, AI-specific checks, safe secret handling, and a Docker build+push that produces a traceable image in GHCR. Five modules of work. But there's a fundamental problem: after the pipeline finishes successfully and the image is in the registry... someone opens a terminal, ssh's into the server, pulls the image manually, and restarts the service. Everything you automated up to now ends in a manual step.
This module eliminates that last manual step. After this, the pipeline doesn't just validate and build — it also deploys. First to staging (automatically), where you verify that everything works in a real environment. Then a reviewer approves, and the pipeline deploys to production. If something goes wrong, the pipeline rolls back to the previous version — automatically.
The mindset shift: You go from "the pipeline builds and I deploy" to "the pipeline builds AND deploys, with staging as a safety net, approval as protection, and rollback as the emergency plan."
Context: You come from Module 5, where you automated the build, tag, scan, and push of Docker images. The image that pipeline produces — ghcr.io/user/ai-api:sha-abc1234 — is exactly what this module deploys. Without that image in a registry, there's nothing to deploy. And what you build here is the foundation of Module 8 (the complete integrating pipeline).
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 ✅ Completed
└── Module 6: Deployment Pipelines ← YOU ARE HERE
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 6 of 8. You automate your AI application's deployment. 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
- You automated Docker (module 5) — build, tag, scan, push
- Now you automate deployment (this module) — 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 deployment
What your deployment flow looks like today
The CI/CD pipeline (Modules 1-5):
✅ Lint and type checking passed
✅ Tests passed (pytest, prompt regression)
✅ Cost estimation OK
✅ Docker image built, scanned, pushed
✅ ghcr.io/user/ai-api:sha-abc1234 available in GHCR
Deployment (manual):
Developer: *ssh into the staging server*
Developer: docker pull ghcr.io/user/ai-api:sha-abc1234
Developer: docker-compose down && docker-compose up -d
Developer: *manually tests that it works*
Developer: *ssh into the production server*
Developer: docker pull ghcr.io/user/ai-api:sha-abc1234
Developer: docker-compose down && docker-compose up -d
Developer: "I think it works. Fingers crossed."
Visible problems:
- 📋 Manual deployment is slow — SSH, pull, restart, verify... 15-30 minutes per deploy
- 📋 No separate staging environment — Either it doesn't exist, or it gets skipped "because it already works locally"
- 📋 No formal approval — Whoever builds also deploys, with nobody reviewing
- 📋 No rollback plan — If something fails in production, which version do you go back to?
- 📋 No notifications — The team doesn't know there was a deploy until something breaks
What it looks like after this module
Developer: *git push*
GitHub Actions (automatic):
1. Tests + AI checks + Docker build (Modules 1-5)
2. Deploy to staging → docker-compose up with the new image
3. Smoke tests in staging → /health returns 200
4. ⏸️ Waiting for approval...
Reviewer: *reviews staging, verifies that it works*
Reviewer: *approves the deploy to production*
GitHub Actions (automatic):
5. Deploy to production → docker-compose up with the approved image
6. Smoke tests in production → /health returns 200
7. ✅ Deploy successful — a notification in the workflow summary
If it fails → automatic rollback to the previous image
The difference: zero manual SSH, staging as validation, human approval before production, and an automatic rollback if something goes wrong.
CI vs CD: the key distinction
What you already have (CI)
CI — Continuous Integration — answers the question: "Is this code valid?"
Push → Do the tests pass? → Is the lint clean? → Do the prompts work? → Does Docker build? → No vulnerabilities?
↓
An image in the registry ✅
CI ends with an artifact ready to deploy: a Docker image in GHCR. But that artifact is sitting in the registry waiting for someone to grab it and put it on a server.
What you're adding now (CD)
CD — Continuous Delivery/Deployment — answers the question: "How does this code reach users?"
An image in the registry
↓
Deploy to staging → Does it work? → Approval → Deploy to production → Does it work?
↓ NO ↓ NO
Fix + retry Rollback
CD takes the artifact CI produced and takes it to real environments where users consume it.
The dividing line
| Aspect | CI (Modules 1-5) | CD (This module) |
|---|---|---|
| The question | "Is the code valid?" | "Does the code reach users?" |
| Output | An image in the registry | An application running in production |
| Feedback | "The tests passed/failed" | "The deploy succeeded/failed" |
| Risk | Low (it only validates) | High (it modifies real environments) |
| Rollback | Not applicable (there's no deploy) | Critical (you need to go back) |
| Approval | Not necessary | Necessary before production |
What makes CD different for AI systems
The particularities of deploying AI
Deploying an AI application isn't the same as deploying a CRUD. AI applications have characteristics that complicate deployment:
1. Heavy dependencies:
- torch, transformers, langchain → 1-3 GB images
- Pulling the image takes longer than in traditional apps (10-30s vs 2-5s)
- Longer startup time: loading models, initializing embeddings
2. Costs per request:
- Every request to OpenAI/Anthropic costs money
- A broken deploy that goes into retry loops can generate significant costs
- Staging MUST use API keys with a limited budget (Module 4)
3. Non-deterministic behavior:
- The same request can give different responses
- Smoke tests need to be tolerant (status 200, not an exact output)
- Prompt regression testing is probabilistic, not absolute
4. Cold start:
- Models that get loaded into memory at startup
- The health check can take 30-60 seconds to turn positive
- The deployment needs wait time before verifying health
These particularities influence how you design the deployment pipeline: longer timeouts, health checks with retries, smoke tests that verify functionality without depending on exact outputs, and staging with separate API keys.
Goal of the module
By completing this module you will be able to:
- ✅ Explain the difference between Continuous Delivery and Continuous Deployment
- ✅ Configure GitHub Environments (staging, production) with protection rules
- ✅ Implement deployment strategies: rolling update with docker-compose
- ✅ Configure approval gates with required reviewers before production
- ✅ Design rollback strategies: redeploying a previous tag, reverting a commit
- ✅ Add deploy notifications with status checks and workflow summaries
- ✅ Build a complete Staging Deploy Pipeline as the module's project
The professional goal
When someone on your team pushes to main, the application gets deployed automatically to staging. A reviewer verifies that it works, approves, and the pipeline deploys to production — with an automatic rollback if something fails. Zero SSH. Zero manual deploys. Zero "fingers crossed."
Module contents
The capsule map
| # | Capsule | What you'll learn | Type |
|---|---|---|---|
| 01 | Introduction (this one) | Why automate deployment, CI vs CD, the vision | Intro |
| 02 | CD Concepts and Strategies | Delivery vs Deployment, when to use each, the deploy pattern | Technical |
| 03 | GitHub Environments and Protection Rules | Staging/production environments, branch restrictions | Technical |
| 04 | Deployment Strategies | Rolling update (implement), blue-green, canary (conceptual) | Technical |
| 05 | Approval Gates | Required reviewers, human-in-the-loop, the full flow | Technical |
| 06 | Rollback Strategies | Reverting a commit, redeploying a previous tag, automatic rollback | Technical |
| 07 | Deploy Notifications | Status checks, deployment status, basic notifications | Technical |
| 08 | Project: Staging Deploy Pipeline | The complete pipeline: build → deploy staging → approve → production | Project |
The learning flow
First you understand the CD concepts and when to use Delivery vs Deployment (capsule 02). Then you configure the environments on GitHub with protection rules (capsule 03). You learn deployment strategies and implement rolling update (capsule 04). You configure approval gates for the step into production (capsule 05). You design rollback strategies for when something fails (capsule 06). You add notifications for visibility (capsule 07). Finally, you integrate everything into a complete deployment pipeline (capsule 08).
The progression is: concepts → environments → strategies → approvals → rollbacks → notifications → project.
Estimated duration of the module: 60-90 minutes.
What carries over from Module 5
Your current pipeline (after Module 5) produces Docker images automatically:
name: Docker CI Pipeline
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -r requirements-dev.txt
- run: ruff check src/ tests/
- run: pytest tests/ -v --tb=short
docker:
runs-on: ubuntu-latest
needs: test
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/metadata-action@v5
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=sha-
type=semver,pattern=v{{version}}
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
The result: an image in GHCR (ghcr.io/user/ai-api:sha-abc1234) ready for deployment. But "ready" doesn't mean "deployed." The pipeline finishes and the image waits.
What this module adds
deploy-staging:
needs: docker
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
# Pull the new image and deploy with docker-compose
docker compose pull
docker compose up -d
- name: Smoke test staging
run: curl -sf https://staging.your-app.com/health
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://your-app.com
steps:
- name: Deploy to production
run: |
docker compose pull
docker compose up -d
- name: Smoke test production
run: curl -sf https://your-app.com/health
Two new jobs: the deploy to staging (automatic) and the deploy to production (with an approval gate). The image Module 5 produces, Module 6 deploys.
Connection with the guide's project
This module's project: Staging Deploy Pipeline
The mini-project builds a complete deployment pipeline:
- Build — The Docker image built and pushed (Module 5)
- Deploy to staging — The image deployed to staging automatically
- Smoke tests — Verification that staging works
- Approval — A reviewer approves the deploy to production
- Deploy to production — The image deployed to production
- Rollback — If production fails, redeploy the previous version
Push to main
↓
┌──────────────────────────────────────────────────────┐
│ Job: test + docker (Module 5) │
│ Tests → Build → Scan → Push to GHCR │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Job: deploy-staging (automatic) │
│ Pull image → docker-compose up → smoke tests │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ ⏸️ Approval gate (a manual reviewer) │
│ The reviewer verifies staging → approves │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Job: deploy-production (post-approval) │
│ Pull image → docker-compose up → smoke tests │
│ If it fails → rollback to the previous version │
└──────────────────────────────────────────────────────┘
Connection with later modules
Module 5: Docker CI Pipeline (build + push images)
↓
Module 6: Deployment Pipelines (deploy + rollback) ← YOU ARE HERE
↓
Module 7: Notifications and Advanced Patterns (visibility)
↓
Module 8: The capstone pipeline (everything together, production-grade)
The deployment pipeline you build here is the core of Module 8. This module gives you the mechanics of deployment; Module 7 adds visibility (who finds out when it fails?); Module 8 integrates everything into a complete flow.
Prerequisites
- ✅ Modules 1-5 completed: Workflows, testing, AI checks, secrets, Docker build+push
- ✅ Docker fundamentals (guide #15): docker-compose, Dockerfile, volumes
- ✅ GitHub Environments (Module 4, capsule 04): the basics of creating environments
- ✅ Intermediate Python: FastAPI, REST APIs, async
If you don't have these prerequisites
| What you're missing | Recommended resource |
|---|---|
| Docker + docker-compose | Docker Essentials Guide (#15, NIEVA) |
| GitHub Actions basics | Modules 1-2 of this guide |
| Secrets and environments | Module 4 of this guide |
| Docker in CI | Module 5 of this guide |
Technical setup
The module's file structure
your-ai-project/
├── .github/
│ └── workflows/
│ ├── ci.yml # Quality gate (Modules 1-3)
│ ├── docker.yml # Docker CI Pipeline (Module 5)
│ └── deploy.yml # Deployment Pipeline (this module)
├── docker-compose.yml # Compose for deployment
├── docker-compose.staging.yml # An override for staging
├── docker-compose.prod.yml # An override for production
├── Dockerfile
├── src/
│ └── ai_app/
│ ├── __init__.py
│ ├── main.py # FastAPI app
│ └── chain.py # LangChain pipeline
├── tests/
│ └── test_chain.py
├── scripts/
│ └── smoke-test.sh # The smoke test script
├── requirements.txt
└── requirements-dev.txt
Quick verification
# Verify that you have Docker and docker-compose
docker --version
docker compose version
# Verify that your image is in GHCR (Module 5)
docker pull ghcr.io/your-user/your-repo:latest
# Verify that you have GitHub Environments configured (Module 4)
# GitHub → Settings → Environments → staging, production
If all three checks work, you're ready for the module.
What this module does NOT cover
- ❌ Cloud infrastructure: AWS, GCP, Azure deployment — that's guide #17
- ❌ Kubernetes: Container orchestration, Helm charts — guide #17
- ❌ Complete Slack/email notifications: Detailed configuration — Module 7
- ❌ A complete blue-green/canary implementation: It requires infrastructure — guide #17
- ❌ Docker basics: Dockerfile, docker-compose syntax — guide #15
Analogy: The restaurant with a test kitchen
Imagine a fine-dining restaurant. The chef doesn't prepare a new dish and serve it directly to the diners. First they prepare it in the test kitchen (staging): they check the flavor, the texture, the presentation. Then the sous-chef tastes it and gives the go-ahead (approval). Only then does the dish get prepared for the diners (production). If a diner reports a problem, the restaurant has the previous dish on the menu, ready to serve (rollback).
Your deployment pipeline works the same way:
- The test kitchen = the staging environment (you verify that it works)
- The sous-chef tasting = the approval gate (a human verifies and approves)
- Serving the diner = the production deployment (real users use it)
- The previous dish on the menu = the rollback (the previous version is in GHCR, ready)
No professional chef serves a dish without tasting it first. No professional team deploys without staging.
The mindset shift
Before this module
Developer: "The pipeline passed, the image is in GHCR"
Developer: *ssh staging-server*
Developer: *docker pull ghcr.io/user/ai-api:sha-abc1234*
Developer: *docker-compose down && docker-compose up -d*
Developer: *curl staging.app.com/health — it works*
Developer: *ssh prod-server*
Developer: *docker pull ghcr.io/user/ai-api:sha-abc1234*
Developer: *docker-compose down && docker-compose up -d*
Developer: "Done... I think."
Time: 20 minutes
Confidence: "It should work"
Rollback: "Hmm... what was the previous tag?"
After this module
Developer: *git push*
GitHub Actions:
✅ Tests + Docker build (Modules 1-5)
✅ Deploy to staging — automatic
✅ Smoke tests in staging — /health OK
⏸️ Waiting for approval for production...
Reviewer: *verifies staging — approves*
GitHub Actions:
✅ Deploy to production — automatic
✅ Smoke tests in production — /health OK
✅ Summary: "v1.2.3 deployed to production"
Time: 5 minutes (automatic)
Confidence: "Staging verified, the reviewer approved"
Rollback: "If it fails → automatic to sha-prev1234"
That's the difference between manual deployment and a deployment pipeline.
Quick self-assessment
Before starting the capsules, verify that you have the necessary context:
- What is a GitHub Environment and what protection rules does it have? (Module 4)
- What tags does your Docker pipeline generate? (Module 5)
- How do you log in to GHCR from a workflow? (Module 5)
- What does
needs:do between jobs? (Module 1) - What is a smoke test? (Module 2)
If any question sounds completely new, review the corresponding module before continuing.
Evidence of success
By the end of this module, you should be able to:
- Explain when to use Continuous Delivery vs Continuous Deployment
- Configure GitHub Environments with protection rules
- Implement rolling update deployment with docker-compose
- Configure approval gates with required reviewers
- Implement an automatic rollback to a previous image
- Add deploy notifications to the pipeline
- Build the project's complete Staging Deploy Pipeline
If you tick every check → you're ready for Module 7.
Summary
- CI validates the code; CD takes it to production — This module covers the second half
- Manual deployment is the problem: Slow, risky, no traceability, no rollback
- The standard pattern: build → test → deploy staging → approval → deploy production
- Staging as a safety net: Where you discover problems before users see them
- Approval gates as protection: A human verifies before production
- Rollback as a first-class citizen: Designed from the start, not as an afterthought
- Module 5's image is the input; the deployment pipeline is the output
Additional resources
- GitHub Actions — Environments — Official documentation on environments for deployment
- GitHub Actions — Deployment workflows — The complete guide to deployment with Actions
- Continuous Delivery vs Continuous Deployment — The differences and when to use each one
- Docker Compose in production — Best practices for compose in production
- GitHub Environments Protection Rules — Configuring protection rules
- Docker Essentials Guide (#15, NIEVA) — Prerequisite: Docker and docker-compose fundamentals