Running Python in CI
Write a GitHub Actions workflow that sets up Python, installs requirements with pip caching, and runs your pipeline script on every push and on a daily schedule.
- Write a workflow that uses actions/setup-python to install a specific Python version
- Add pip dependency caching to avoid reinstalling packages on every run
- Run a pipeline script and handle its exit code correctly
A Python workflow in GitHub Actions follows the same three-step pattern on almost every project: check out the code, set up Python, install dependencies, run the script. The only thing that changes is what the script does.
A complete workflow
# .github/workflows/run-pipeline.yml
name: Run pipeline
on:
push:
branches: ["main"]
schedule:
- cron: "0 2 * * *" # 02:00 UTC daily
workflow_dispatch: # manual trigger
jobs:
pipeline:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip" # cache ~/.cache/pip between runs
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run pipeline
run: python pipeline.py
env:
API_KEY: ${{ secrets.API_KEY }}
OUTPUT_DIR: outputDependency caching
cache: "pip" in actions/setup-python caches the pip download cache between
workflow runs. The cache key is computed from the hash of requirements.txt. When
dependencies have not changed, pip installs from the cache rather than downloading
packages — cutting a typical install from 30–90 seconds to under 5 seconds.
The cache is stored per branch and per OS. It is automatically invalidated when
requirements.txt changes (a new hash) and expires after 7 days of non-use.
cache: "pip" caches the pip download cache, not the installed packages
themselves. pip install still runs on every workflow execution; it just finds
the wheels already downloaded rather than fetching them from PyPI. If you need to
skip pip install entirely, cache the virtual environment itself using
actions/cache with the site-packages directory as the path.
Exit codes and failure detection
GitHub Actions marks a step as failed when its command exits with a non-zero code. Python scripts exit 0 by default even if they catch and discard exceptions. Ensure your pipeline propagates failures:
import sys
def main():
try:
run_pipeline()
except Exception as exc:
print(f"Pipeline failed: {exc}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()A failed step stops the job and marks it red in the Actions UI. Without sys.exit(1),
a pipeline that silently failed will show as a green check — the worst outcome.
Multi-Python version matrix
To verify your script works on multiple Python versions, use a matrix strategy:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- run: pip install -r requirements.txt
- run: pytest tests/This runs three parallel jobs — one per Python version — and fails the whole workflow if any version fails.
Uploading output files as artifacts
- name: Run pipeline
run: python pipeline.py
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: pipeline-report
path: output/
retention-days: 30Artifacts are downloadable from the Actions run page for retention-days days.
Useful for reports, logs, and any output you need to inspect after the run.
Where to go next
Next: secrets and environments — how GitHub Secrets differ from plain environment variables, and how deployment environments add approval gates for production runs.
GitHub Actions basics
A GitHub Actions workflow is a YAML file that runs jobs on triggers — understand workflows, triggers, jobs, and steps before writing your first automation.
Secrets and environments
GitHub Secrets are encrypted and masked in logs — understand how they differ from plain environment variables, and how deployment environments add approval gates for production runs.