Automation with CI/CD

Advanced · 17 min read · ▶ live playground · ✦ checkpoint

Your branch can look perfect on your laptop and still fail on someone else's machine. CI means continuous integration: automatically checking each proposed change in a clean environment so broken code gets caught before it lands in main.

CD means continuous delivery or continuous deployment: automatically preparing or releasing software after the checks pass. Today you focus on CI, because tests and linters are the gate every Python project should learn first.

What GitHub Actions Does

GitHub Actions is GitHub's automation system for running commands when repository events happen. An event is something GitHub notices in the repo, such as a push, a pull request, a new tag, or a manual button press.

The core idea is plain: GitHub rents you a clean machine, checks out your code, runs the commands you listed, then reports pass or fail on the commit or pull request. Think of CI as a workshop inspector that rebuilds the project from a fresh bench, not from the comfortable mess on your laptop.

The vocabulary:

  • A workflow is one automation file that tells GitHub Actions what to run.
  • YAML is a whitespace-sensitive configuration format made of keys, values, lists, and indentation.
  • A runner is the machine that executes the workflow.
  • A job is a group of steps that runs on one runner.
  • A step is one action or shell command inside a job.

GitHub Actions workflow files live in .github/workflows/ and usually end in .yml or .yaml. You are not creating that folder here; this is the shape you would add to a real project.

A Basic Python CI Workflow

Here is a small workflow for a Python project that has tests and a linter configured:

name: Python CI
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
 
    steps:
      - name: Check out code
        uses: actions/checkout@v4
 
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.14"
 
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install -r requirements.txt
 
      - name: Run tests
        run: python -m pytest
 
      - name: Run linter
        run: python -m ruff check .

The on section is the trigger, which is the event that starts the workflow. This one runs when commits are pushed to main and when a pull request targets main.

The runs-on line chooses the runner image. ubuntu-latest is a common Linux runner for Python projects. The two uses: steps call published actions: one checks out your repo, and one installs the requested Python version.

The run: steps are ordinary terminal commands. If the command exits successfully, the step passes. If a command exits with failure, the step fails and the job stops unless you configure otherwise.

Install or Sync Dependencies

The dependency step must match your project. A tiny learning repo might use requirements.txt:

python -m pip install -r requirements.txt

A project managed with uv might use a sync command instead:

uv sync --locked

The exact manager is less important than the principle: CI must rebuild the environment from files committed to the repo. If a test only passes because your laptop has an unrecorded package installed, CI is doing its job by failing.

For a library, you may test several Python versions with a matrix, which is a workflow setting that runs the same job with several values:

strategy:
  matrix:
    python-version: ["3.12", "3.13", "3.14"]

You do not need a matrix for every beginner project. Start with one Python version, get the workflow reliable, then expand when compatibility matters.

Run Tests and Linters Automatically

Tests answer "does the behavior still work?" Linters answer "does the code match the project's quality rules?" In a Python CI workflow, both should run before a pull request merges.

Common command shapes:

python -m pytest
python -m ruff check .
python -m ruff format --check .
python -m mypy src

Use the tools your project actually has configured. pytest came earlier as the test runner; Ruff and mypy came earlier as quality tools. CI is not a new kind of testing. It is your existing checks running automatically on a clean runner.

Put slower checks later and fastest checks earlier when you can. A linter can fail in seconds; a full integration test suite might take minutes. That ordering gives faster feedback on obvious problems.

Read a Failed Run

CI failures are feedback, not a verdict on you. Read them like tracebacks:

  1. Find the failed job.
  2. Open the failed step.
  3. Read the command that ran.
  4. Read the first useful error line, not just the final red summary.
  5. Fix locally, commit, and push again.

Example failure shape:

Run python -m pytest
============================= test session starts =============================
FAILED tests/test_menu.py::test_weekly_special
E   AssertionError: assert 'Soup' == 'Soup of the day'
Error: Process completed with exit code 1.

That log says the workflow worked: it found a behavior mismatch before merge. Your next move is not to edit the workflow. Your next move is to run the same command locally, fix either the code or the test, and push the next commit.

Where CI Fits in the Git Workflow

CI is the safety rail around the workflow you already learned:

  • Create a feature branch.
  • Commit focused changes.
  • Push the branch to GitHub.
  • Open a pull request.
  • Let CI run tests and linters.
  • Review the diff.
  • Merge only when the branch is green and reviewed.

Green means the required automated checks passed. Red means at least one check failed. Do not treat green as proof the code is perfect; tests can miss things. Treat red as a specific problem to inspect before merging.

CD comes after this foundation. A project might build a package, publish documentation, deploy a web app, or upload a release only after CI passes. The key habit is the same: automate repeatable checks so humans spend attention on design, risk, and review.

Checkpoint

Answer all three to mark this lesson complete

You now have the professional loop: Git tracks change, GitHub coordinates review, and CI checks every branch before it merges. Next, Section 20 turns to using AI as a developer without surrendering your judgment.

+50 XP on completion