Git & GitHub Mastery: Workflows & CI/CD
Back to Real Git Scenarios

Scenario 6 — Build a CI Workflow with GitHub Actions

Add CI to a repo: lint, test, deploy. The first step toward DevOps for any project.

39 minutesAssignment
🎯 Free Guest Mode: You are learning for free. Sign in to save your completion progress and quiz answers.

The brief

Add CI to a repo: lint + tests on every PR, with status checks blocking merge if they fail. Optional: deploy on main.

Step 1: Starter repo (5 min)

Use the repo from Scenario 5 (mandatory — the capstone chain shares one repo). If you don't have it, bootstrap a minimal Python project:

mkdir ci-demo && cd ci-demo
git init && git switch -c main
echo "[project]\nname = \"ci-demo\"\nversion = \"0.1.0\"" > pyproject.toml
echo "def add(a, b):\n    return a + b" > src.py
mkdir tests && echo "from src import add\n\ndef test_add():\n    assert add(1, 2) == 3" > tests/test_src.py
git add . && git commit -m "Starter: pyproject + src + tests"

Step 2: Create the CI workflow (15 min)

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
          cache: 'pip'
      - run: pip install ruff
      - run: ruff check .
      - run: ruff format --check .

  test:
    name: Test
    runs-on: ubuntu-latest
    needs: lint              # only run if lint passed
    strategy:
      matrix:
        python: ['3.11', '3.12']
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python }}
          cache: 'pip'
      - run: pip install -e ".[dev]"
      - run: pytest --cov=src --cov-report=term-missing

Commit and push:

git add .github/workflows/ci.yml
git commit -m "Add CI workflow: lint + test"
git push

Step 3: Verify it runs (10 min)

Push a change OR open a PR. Visit the "Actions" tab on GitHub:

  • See your workflow running.
  • Click into it to see job logs.
  • Wait for it to finish; should be green if your code is clean.

If red:

  • Look at the failed step's logs.
  • Fix the issue locally (lint errors, test failures).
  • Push again; CI re-runs.

Step 4: Make CI required (10 min)

Chain: this branch protection is configured on the Scenario 5 repo, so the S5 PR merge is blocked until this CI is green — the capstone is self-justifying.

this branch protection is configured on the Scenario 5 repo, so the S5 PR merge is blocked until this CI is green — the capstone is self-justifying.

this branch protection is configured on the Scenario 5 repo, so the S5 PR merge is blocked until this CI is green — the capstone is self-justifying.

GitHub repo → Settings → Branches → Branch protection rule for main:

  • ✅ Require status checks to pass before merging.
  • Pick the checks: Lint, Test (3.12), Test (3.11).
  • ✅ Require branches to be up to date before merging.
  • ✅ Require pull request reviews before merging (1 approval).

Now PRs can't merge until CI is green.

Step 5: Add a typecheck step (10 min)

  typecheck:
    name: Typecheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: '3.12'
          cache: 'pip'
      - run: pip install -e ".[dev]"
      - run: pyright src tests

Commit, push. CI now has lint + typecheck + tests.

Step 6: Add a deploy step (15 min, optional)

For a real app, add a deploy job that runs only on main:

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - uses: actions/checkout@v6
      - name: Deploy to Fly.io (example)
        uses: superfly/flyctl-actions/setup-flyctl@v2
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Replace with your actual deploy command. Store credentials as GitHub secrets.

Step 7: Status badge (5 min)

Add to README:

# My Project

![CI](https://github.com/yourname/my-project/actions/workflows/ci.yml/badge.svg)

Now your README shows the build status.

Step 8: Add Dependabot (5 min)

.github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: pip
    directory: /
    schedule:
      interval: weekly
    open-pull-requests-limit: 5

GitHub will open PRs to update outdated dependencies weekly. Saves manual maintenance.

Step 9: Multi-OS testing (optional)

For libraries that should work cross-platform:

  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python: ['3.11', '3.12']
    runs-on: ${{ matrix.os }}
    steps: [...]

Runs 6 jobs in parallel. Catches Windows-specific bugs.

Step 10: Caching for speed (5 min)

- uses: actions/setup-python@v6
  with:
    python-version: '3.12'
    cache: 'pip'                    # auto-cache pip downloads

- uses: actions/cache@v5             # cache pre-commit, mypy, etc.
  with:
    path: ~/.cache/pre-commit
    key: ${{ runner.os }}-precommit-${{ hashFiles('.pre-commit-config.yaml') }}

Subsequent CI runs reuse caches; faster.

Deliverables

Submission contract: paste the repo URL + the green Actions run link into submission notes; upload CI.md (jobs + order, caching strategy, secrets management).

100-pt rubric: CI workflow runs green (25) / branch protection requires the checks (25) / typecheck step added (15) / badge in README (10) / CI.md documents jobs/caching/secrets (25).

paste the repo URL + the green Actions run link into submission notes; upload CI.md (jobs + order, caching strategy, secrets management).

CI workflow runs green (25) / branch protection requires the checks (25) / typecheck step added (15) / badge in README (10) / CI.md documents jobs/caching/secrets (25).

paste the repo URL + the green Actions run link into submission notes; upload CI.md (jobs + order, caching strategy, secrets management).

CI workflow runs green (25) / branch protection requires the checks (25) / typecheck step added (15) / badge in README (10) / CI.md documents jobs/caching/secrets (25).

  • A working CI workflow on a real repo.
  • Lint + typecheck + tests passing.
  • Branch protection requiring CI to pass.
  • A status badge in the README.
  • A CI.md documenting:
    • The jobs and their order.
    • Caching strategy.
    • How secrets are managed.

What you'll learn

  • GitHub Actions workflow syntax.
  • Jobs, steps, matrix strategies.
  • Caching for fast CI.
  • Branch protection for required checks.
  • Dependabot for automated dependency updates.
  • Status badges for visibility.

Stretch

  • Add auto-merge for Dependabot PRs that pass CI.
  • Add a release workflow that publishes to PyPI on tag.
  • Add a workflow that runs nightly (on a schedule) to catch regressions.
  • Add a workflow that comments on PRs with coverage diff.

Track your learning progressFree

Sign in or enroll free to mark this assignment as complete, save your progress, and earn certificates.