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:

  1. First you understood CI (module 1) — workflows, jobs, steps, triggers
  2. Then you automated testing (module 2) — pytest, matrix, caching, reports
  3. You added AI-specific checks (module 3) — prompt regression, cost estimation
  4. You handled secrets safely (module 4) — API keys, OIDC, environments
  5. You automated Docker (module 5) — build, tag, scan, push
  6. Now you automate deployment (this module) — staging → approval → production
  7. You add monitoring (module 7) — notifications, scheduled workflows
  8. 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

AspectCI (Modules 1-5)CD (This module)
The question"Is the code valid?""Does the code reach users?"
OutputAn image in the registryAn application running in production
Feedback"The tests passed/failed""The deploy succeeded/failed"
RiskLow (it only validates)High (it modifies real environments)
RollbackNot applicable (there's no deploy)Critical (you need to go back)
ApprovalNot necessaryNecessary 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

#CapsuleWhat you'll learnType
01Introduction (this one)Why automate deployment, CI vs CD, the visionIntro
02CD Concepts and StrategiesDelivery vs Deployment, when to use each, the deploy patternTechnical
03GitHub Environments and Protection RulesStaging/production environments, branch restrictionsTechnical
04Deployment StrategiesRolling update (implement), blue-green, canary (conceptual)Technical
05Approval GatesRequired reviewers, human-in-the-loop, the full flowTechnical
06Rollback StrategiesReverting a commit, redeploying a previous tag, automatic rollbackTechnical
07Deploy NotificationsStatus checks, deployment status, basic notificationsTechnical
08Project: Staging Deploy PipelineThe complete pipeline: build → deploy staging → approve → productionProject

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:

  1. Build — The Docker image built and pushed (Module 5)
  2. Deploy to staging — The image deployed to staging automatically
  3. Smoke tests — Verification that staging works
  4. Approval — A reviewer approves the deploy to production
  5. Deploy to production — The image deployed to production
  6. 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 missingRecommended resource
Docker + docker-composeDocker Essentials Guide (#15, NIEVA)
GitHub Actions basicsModules 1-2 of this guide
Secrets and environmentsModule 4 of this guide
Docker in CIModule 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:

  1. What is a GitHub Environment and what protection rules does it have? (Module 4)
  2. What tags does your Docker pipeline generate? (Module 5)
  3. How do you log in to GHCR from a workflow? (Module 5)
  4. What does needs: do between jobs? (Module 1)
  5. 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

  1. GitHub Actions — Environments — Official documentation on environments for deployment
  2. GitHub Actions — Deployment workflows — The complete guide to deployment with Actions
  3. Continuous Delivery vs Continuous Deployment — The differences and when to use each one
  4. Docker Compose in production — Best practices for compose in production
  5. GitHub Environments Protection Rules — Configuring protection rules
  6. Docker Essentials Guide (#15, NIEVA) — Prerequisite: Docker and docker-compose fundamentals