11. CI/CD Pipeline
Chapter 11 of 18 · 15 min
CI/CD automates testing, building, and deployment. GitHub Actions provides the pipeline infrastructure. The pipeline runs on every push and pull request, catching issues before they reach production.
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r backend/requirements.txt
pip install -r requirements-dev.txt
- name: Run linting
run: |
flake8 backend/
mypy backend/
- name: Run unit tests
run: pytest backend/tests/unit -v
- name: Run integration tests
run: |
docker compose -f docker-compose.test.yml up -d
pytest backend/tests/integration -v
docker compose -f docker-compose.test.yml down
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push backend
uses: docker/build-push-action@v5
with:
context: ./backend
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}-backend
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push frontend
uses: docker/build-push-action@v5
with:
context: ./frontend
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}-frontend
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment: staging
steps:
- name: Deploy to staging
run: |
echo "${{ secrets.SSH_KEY }}" > deploy_key
chmod 600 deploy_key
ssh -o StrictHostKeyChecking=no -i deploy_key user@staging-server \
"docker compose -f /app/docker-compose.staging.yml pull && \
docker compose -f /app/docker-compose.staging.yml up -d"
Pipeline failures often stem from flaky tests. Implement test retries with exponential backoff for integration tests that interact with external services. Cache dependencies between runs to speed up the pipeline. Use matrix builds to test across Python versions or Node versions in parallel.
EXERCISE
Create a GitHub Actions workflow that runs tests, builds Docker images, and deploys to a staging environment on merge to main.