A good CI/CD pipeline catches bugs before they reach production, enforces code quality, and automates the boring stuff. But setting one up from scratch? That's an afternoon of YAML debugging you'll never get back.
This guide shows you how to build a complete GitHub Actions pipeline for Python in five minutes — using three purpose-built GitHub Actions from Kryptorious Tools: Python Guardian (code quality), PR Sentinel (pull request checks), and Release Forge (automated releases). Pair them with the companion CLI tools available in the $9 lifetime bundle on Gumroad.
What We're Building
Here's the pipeline that fires on every push and PR:
- Lint & Type Check — Python Guardian runs ruff, mypy, and bandit
- Test — pytest with coverage reporting
- Secret Scan — Catch leaked credentials before they hit the repo
- PR Validation — PR Sentinel enforces branch rules, commit hygiene, and required checks
- Auto-Release — Release Forge builds and publishes when you push a version tag
Step 1: The Foundation — Python Guardian
Python Guardian is a unified code-quality action. Instead of wiring up six separate actions for linting, formatting, type checking, and security scanning, you get one action that runs them all:
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Python Guardian — Lint & Security
uses: codegero/python-guardian@v1
with:
ruff: true
mypy: true
bandit: true
fail-on: error
Python Guardian handles dependency caching automatically and produces a single
unified report. Set fail-on: error and your PR gets blocked on
any lint violations or type errors. Set it to warning during
early-stage projects where you're still cleaning up legacy code.
Step 2: Test & Coverage
Add a test job to the same workflow:
test:
needs: quality
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -e ".[dev]"
- name: Run tests with coverage
run: |
pytest --cov=src --cov-report=xml --cov-report=term-missing
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
The strategy.matrix runs tests across Python 3.11, 3.12, and 3.13
in parallel. Combined with Python Guardian's lint step, any push to main is fully
validated in under 90 seconds (most projects).
Step 3: Secret Scanning — Don't Be a Headline
The Secret Scanner GitHub Action scans every commit and PR for leaked credentials. It catches the obvious (AWS keys, GitHub tokens) and the subtle (database URLs with embedded passwords, hardcoded JWT secrets):
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # scan full history
- name: Secret Scanner
uses: codegero/secret-scanner@v1
with:
scan-history: true
fail-on-detection: true
If it finds a secret, the PR is blocked. You can add custom patterns in
.secret-scanner.yaml at the repo root — useful for internal
API keys or project-specific credential formats.
Step 4: PR Sentinel — Enforce Branch Discipline
PR Sentinel adds a layer of automation on top of GitHub's branch protection rules. It enforces things GitHub's built-in rules can't easily do:
pr-check:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: PR Sentinel
uses: codegero/pr-sentinel@v1
with:
require-conventional-commits: true
max-commits: 15
require-pr-description: true
enforce-linear-history: true
label-on-conflict: true
Key features:
- Conventional commits — enforces
feat:,fix:,chore:prefixes - Max commits per PR — prevents 47-commit monster PRs; encourages squashing locally
- PR description required — no more PRs titled just "fix" with an empty body
- Linear history — blocks merge commits, enforces rebase or squash
- Conflict labels — automatically labels PRs that conflict with the base branch
Step 5: Release Forge — Ship on Tag
The final piece: Release Forge automates the entire release process. Push a version tag, and it:
- Builds the package (wheel + sdist)
- Generates a changelog from conventional commit history
- Creates a GitHub Release with assets attached
- Optionally publishes to PyPI
release:
needs: [quality, test, secrets]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Release Forge
uses: codegero/release-forge@v1
with:
generate-changelog: true
pypi-publish: true
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
Now your release workflow is: git tag v1.2.0 && git push --tags.
Everything else — build, changelog, GitHub Release, PyPI upload — happens automatically.
The Complete Workflow (All Together)
Here's the full .github/workflows/ci.yml with all five jobs wired together.
Copy, paste, adjust your Python versions, and you're done:
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- uses: codegero/python-guardian@v1
with: { ruff: true, mypy: true, bandit: true, fail-on: error }
test:
needs: quality
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '${{ matrix.python-version }}' }
- run: pip install -e ".[dev]"
- run: pytest --cov=src --cov-report=xml
- uses: codecov/codecov-action@v4
with: { file: ./coverage.xml }
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: codegero/secret-scanner@v1
with: { scan-history: true, fail-on-detection: true }
pr-check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: codegero/pr-sentinel@v1
with:
require-conventional-commits: true
max-commits: 15
require-pr-description: true
release:
needs: [quality, test, secrets]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- uses: codegero/release-forge@v1
with: { generate-changelog: true, pypi-publish: true }
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
Python Guardian, Release Forge, PR Sentinel, Secret Scanner, plus DevFlow, GitSweep, EnvGuard, and more — $9 lifetime on Gumroad. Drop them into your workflow today.
Explore all tools at codegero.github.io.
Why This Matters
CI/CD isn't optional in 2026 — it's table stakes. A pipeline that lints, tests, scans for secrets, validates PRs, and auto-releases isn't just about catching bugs. It's about velocity: every minute your team spends manually checking code style, writing changelogs, or building releases is a minute not spent shipping features.
The five jobs above run in under three minutes on free GitHub Actions runners for most Python projects. That's less time than it takes to get coffee — and it runs on every single push, catching problems when they're cheapest to fix.