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

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.mddocumenting:- 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-mergefor 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.