|

GitHub Actions CI/CD (4/10): Your First CI Workflow — Run on Every PR

← Previous GitHub Actions CI/CD (4/10) Next →

Summary: Create a GitHub Actions workflow that automatically checks out your code, installs the package, runs lint, and runs tests on every push and pull request. This is the moment your local checks become enforced CI.

KeyValue
Package namehelloci
Python version3.12
Working directory~/projects/helloci
Workflow file.github/workflows/ci.yml
Trigger eventspush, pull_request
Runnerubuntu-latest

0. Prerequisites

  • The helloci package with tests and ruff from Parts 1–3
  • A GitHub account
  • git installed locally
  • ./check.sh passes locally (lint + format + tests)

1. Initialize a Git Repository

If you have not already initialized git in your project, do it now.

cd ~/projects/helloci
git initCode language: Shell Session (shell)

Create a .gitignore file to keep build artifacts and virtual environments out of the repository.

__pycache__/
*.egg-info/
dist/
build/
.venv/
*.pycCode language: Shell Session (shell)

Stage and commit everything.

git add .
git commit -m "Initial commit: helloci package with tests and lint"Code language: Shell Session (shell)

2. Push to GitHub

Create a new repository on GitHub called helloci. Do not add a README or .gitignore — your local project already has everything it needs.

Add the remote and push.

git remote add origin https://github.com/<YOUR_GITHUB_USERNAME>/helloci.git
git branch -M main
git push -u origin mainCode language: Shell Session (shell)

Replace <YOUR_GITHUB_USERNAME> with your actual GitHub username.


3. Create the Workflow Directory

GitHub Actions looks for workflow files in a specific directory.

mkdir -p .github/workflowsCode language: Shell Session (shell)

This path is not optional — .github/workflows/ is where GitHub discovers your workflow files.


4. Write the Workflow File

Create .github/workflows/ci.yml.

name: CI

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

jobs:
  check:
    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.12"

      - name: Install package and test dependencies
        run: pip install -e ".[test]"

      - name: Lint
        run: ruff check .

      - name: Check formatting
        run: ruff format . --check

      - name: Run tests
        run: pytest -vCode language: YAML (yaml)

This is the entire workflow. Save the file.


5. Understand the Workflow

Every line in that file does something specific. Here is what each section means.

Trigger block:

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]Code language: YAML (yaml)

The workflow runs when code is pushed to main or when a pull request targets main. These are called trigger events.

Job definition:

jobs:
  check:
    runs-on: ubuntu-latestCode language: YAML (yaml)

A job is a set of steps that run on a fresh virtual machine. ubuntu-latest tells GitHub to use the most recent Ubuntu runner. Each job starts from a clean state — no leftover files from previous runs.

Steps:

StepWhat it does
actions/checkout@v4Clones your repository onto the runner
actions/setup-python@v5Installs the specified Python version
pip install -e ".[test]"Installs your package and test dependencies
ruff check .Runs the linter
ruff format . --checkChecks code formatting
pytest -vRuns all unit tests

Steps run top to bottom. If any step fails (non-zero exit code), the entire job stops and is marked as failed. This is why the check.sh script from Part 3 used set -euo pipefail — same principle.

Note: actions/checkout@v4 and actions/setup-python@v5 are actions — reusable packages maintained by GitHub. The @v4 part is a version tag. Always pin to a major version.


6. Push and Watch It Run

Commit the workflow file and push.

git add .github/workflows/ci.yml
git commit -m "Add CI workflow: lint, format, tests"
git pushCode language: Shell Session (shell)

Open your repository on GitHub. Click the Actions tab. You should see your workflow running.

The workflow appears as a green checkmark (passed) or red X (failed) next to the commit. Click the workflow run to see the full log.


7. Read the Workflow Logs

Click into the workflow run, then click the check job. You see each step with a collapsible log.

The log output looks exactly like what you see locally when you run ruff check . and pytest -v. This is by design — CI runs the same commands you run on your machine.

If a step fails, the log shows the error message. For example, if a test fails, you see the same pytest output with the assertion error and the failing line.

Tip: If the workflow fails and you cannot figure out why, compare the CI log to your local output. The most common cause is a missing dependency — something installed locally but not listed in pyproject.toml.


8. Test the Workflow With a Pull Request

Create a branch, make a small change, and open a pull request.

git checkout -b test-ciCode language: Shell Session (shell)

Edit src/helloci/__init__.py — add a second function.

def farewell(name: str) -> str:
    """Return a farewell string."""
    return f"Goodbye, {name}!"Code language: Python (python)

Commit and push the branch.

git add src/helloci/__init__.py
git commit -m "Add farewell function"
git push -u origin test-ciCode language: Shell Session (shell)

Open a pull request on GitHub from test-ci into main. The CI workflow runs automatically. On the pull request page, you see the check status — either a green checkmark or a red X next to the workflow name.

Warning: This pull request is just for testing. You can merge it or close it — the point is to see CI run on a PR.


9. Verify Your Project Structure

Your project should now look like this.

~/projects/helloci/
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── check.sh
├── pyproject.toml
├── src/
│   └── helloci/
│       ├── __init__.py
│       └── cli.py
└── tests/
    ├── __init__.py
    ├── test_cli.py
    └── test_greet.pyCode language: Shell Session (shell)

Summary

You created a GitHub Actions workflow that runs lint, format checks, and tests on every push and pull request. The workflow file lives at .github/workflows/ci.yml and uses four key actions and commands.

  • Trigger: push to main and pull requests targeting main
  • Runner: ubuntu-latest — a fresh VM for every run
  • Steps: checkout, setup Python, install, lint, format check, test
  • Feedback: green checkmark or red X on every commit and PR

Your code now has an automated quality gate. Nobody (including you) can push broken code to main without seeing a red X. In Part 5 you will expand this workflow to test across multiple Python versions.

Similar Posts

Leave a Reply