DevLogTechnical writing
Tutorials

Setting up your first GitHub Actions CI pipeline for a Python project

September 30, 2026

Every Python project deserves a safety net. Continuous integration (CI) is that net: every time you push code, a machine checks it for you. It runs your linter, runs your tests, and fails loudly the moment something breaks — before a bad commit sneaks into your shared main branch. Today you'll set up your first pipeline, and it takes only a few minutes.

What you're building

A GitHub Actions workflow is a YAML file that lives in your repository at .github/workflows/ci.yml. GitHub reads it, watches for the events you name, and runs your jobs on a fresh virtual machine every time.

The pipeline you'll write does two things:

  1. Lint your code with Ruff, a fast Python linter written in Rust.
  2. Run your test suite with pytest.

If either step fails, the whole run turns red and your commit or pull request shows the failure right where it matters.

How the workflow fits together

Every workflow has the same skeleton: a name, an on block that says when to run, and a jobs section listing what to do. Each job declares a runs-on — the kind of machine it uses, here ubuntu-latest — and a list of steps. A step either uses a prebuilt action or runs a shell command.

Two official actions do the heavy lifting: actions/checkout@v4 copies your repository onto the runner, and actions/setup-python@v5 installs the Python version you ask for.

The workflow

Drop this into .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  lint-and-test:
    runs-on: ubuntu-latest

    steps:
      - name: Check out the code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff pytest
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

      - name: Lint with Ruff
        run: ruff check --output-format=github .

      - name: Test with pytest
        run: pytest

Here's what each piece does:

Try it

Push the file to your repository, open the Actions tab, and watch the run. When it's green, you're set: every future push and pull request gets the same check automatically.

From here, natural next steps are testing on multiple Python versions with a strategy matrix, running ruff format --check to enforce consistent formatting, or publishing test reports as artifacts. But start simple — a pipeline that runs on every change and catches the obvious mistakes is worth more than an elaborate one you never finish.

← More Tutorials