Prerequisites
- Basic understanding of programming concepts π
- Python installation (3.8+) π
- VS Code or preferred IDE π»
What you'll learn
- Understand the concept fundamentals π―
- Apply the concept in real projects ποΈ
- Debug common issues π
- Write clean, Pythonic code β¨
π― Introduction
Welcome to the exciting world of CI/CD with GitHub Actions! π In this guide, weβll explore how to automate your Python development workflow like a pro.
Youβll discover how GitHub Actions can transform your development experience by automatically testing your code, checking for style issues, and even deploying your applications! Whether youβre building web applications π, data science projects π, or Python libraries π, understanding CI/CD is essential for professional development.
By the end of this tutorial, youβll have your own automated pipeline that makes you look like a DevOps wizard! Letβs dive in! πββοΈ
π Understanding CI/CD with GitHub Actions
π€ What is CI/CD?
CI/CD is like having a super-efficient robot assistant π€ that checks your homework, runs your tests, and even publishes your work - all automatically! Think of it as your personal quality control team that never sleeps.
In Python terms, CI/CD means:
- β¨ Continuous Integration (CI): Automatically test your code whenever you push changes
- π Continuous Deployment (CD): Automatically deploy your app when tests pass
- π‘οΈ Quality Gates: Enforce coding standards before merging
π‘ Why Use GitHub Actions?
Hereβs why developers love GitHub Actions:
- Free for Public Repos π°: No cost for open-source projects
- Native GitHub Integration π§: Works seamlessly with your repository
- Matrix Testing π―: Test across multiple Python versions simultaneously
- Marketplace π¦: Thousands of pre-built actions to use
Real-world example: Imagine youβre building a Python package. With GitHub Actions, every time you push code, it automatically runs tests on Python 3.8, 3.9, 3.10, and 3.11, checks your code style, and even publishes to PyPI when you create a release! π
π§ Basic Syntax and Usage
π Your First GitHub Action
Letβs start with a simple workflow:
# π Hello, GitHub Actions!
# File: .github/workflows/python-app.yml
name: Python application # π·οΈ Give your workflow a name
on: # π― When to run this workflow
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test: # π§ͺ Our test job
runs-on: ubuntu-latest # π§ Use Ubuntu
steps:
- uses: actions/checkout@v3 # π₯ Get your code
- name: Set up Python # π Install Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies # π¦ Install packages
run: |
python -m pip install --upgrade pip
pip install pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Run tests # π§ͺ Run your tests!
run: |
pytest
π‘ Explanation: This workflow runs every time you push to main or create a pull request. It sets up Python, installs dependencies, and runs your tests!
π― Common Workflow Patterns
Here are patterns youβll use daily:
# ποΈ Pattern 1: Matrix testing across Python versions
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11'] # π― Test all versions!
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} # π Dynamic version
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# π¨ Pattern 2: Code quality checks
- name: Lint with flake8 # π§Ή Check code style
run: |
pip install flake8
flake8 . --count --max-line-length=88
# π Pattern 3: Cache dependencies for speed
- name: Cache pip packages # β‘ Speed up builds
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
π‘ Practical Examples
π Example 1: Complete Python Testing Pipeline
Letβs build a comprehensive testing workflow:
# π― Complete testing pipeline
name: Python CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # π
Weekly Sunday run
jobs:
test:
name: Test Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest] # π₯οΈ All platforms!
python-version: ['3.8', '3.9', '3.10', '3.11']
exclude:
- os: macos-latest
python-version: '3.8' # π« Skip this combo
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # π Get full history for coverage
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir # ποΈ Find cache location
id: pip-cache
run: |
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies # β‘ Speed boost!
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies # π¦ Get all packages
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Lint with multiple tools # π§Ή Quality checks
run: |
# π¨ Check code formatting
black --check .
# π Check code style
flake8 . --count --statistics
# π‘οΈ Check type hints
mypy src/
- name: Test with pytest # π§ͺ Run all tests
run: |
pytest tests/ \
--cov=src \
--cov-report=xml \
--cov-report=html \
--cov-report=term-missing \
-v
- name: Upload coverage # π Track coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
π― Try it yourself: Add security scanning with bandit
or dependency checking with safety
!
π¦ Example 2: Auto-Deploy to PyPI
Letβs automate package publishing:
# π Auto-publish to PyPI
name: Publish Python Package
on:
release:
types: [published] # π·οΈ When you create a release
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install build tools # π οΈ Get packaging tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package # π¦ Create distribution
run: python -m build
- name: Check package # β
Verify it's valid
run: twine check dist/*
- name: Publish to Test PyPI # π§ͺ Test first!
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: |
twine upload --repository testpypi dist/*
- name: Test installation # π― Verify it works
run: |
pip install --index-url https://test.pypi.org/simple/ your-package
python -c "import your_package; print('β
Import successful!')"
- name: Publish to PyPI # π Go live!
if: success()
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
twine upload dist/*
echo "π Package published successfully!"
π Example 3: Deploy Web App
Deploy a Flask/Django app automatically:
# π Deploy web application
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
# ... (testing steps from above)
deploy:
needs: test # π― Only deploy if tests pass
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to Heroku # π Deploy to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: "your-app-name"
heroku_email: "[email protected]"
- name: Run smoke tests # π§ͺ Verify deployment
run: |
sleep 30 # β±οΈ Wait for deployment
response=$(curl -s -o /dev/null -w "%{http_code}" https://your-app.herokuapp.com/health)
if [ $response -eq 200 ]; then
echo "β
Deployment successful!"
else
echo "β Deployment failed!"
exit 1
fi
π Advanced Concepts
π§ββοΈ Advanced Workflows
When youβre ready to level up, try these advanced patterns:
# π― Conditional deployments
jobs:
deploy-staging:
if: github.event_name == 'pull_request'
# ... deploy to staging
deploy-production:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# ... deploy to production
# π Environment protection
deploy:
environment:
name: production
url: https://myapp.com
# Requires approval before deploying!
# π¨ Custom actions
- name: My Custom Action
uses: ./.github/actions/my-action
with:
magic: "β¨"
ποΈ Reusable Workflows
Create workflows you can share across projects:
# π Reusable workflow (.github/workflows/python-test.yml)
name: Reusable Python Test
on:
workflow_call:
inputs:
python-version:
required: true
type: string
secrets:
codecov-token:
required: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests for Python ${{ inputs.python-version }}
# ... test steps
# π― Using the reusable workflow
name: CI
on: [push]
jobs:
test-python:
uses: ./.github/workflows/python-test.yml
with:
python-version: '3.9'
secrets:
codecov-token: ${{ secrets.CODECOV_TOKEN }}
β οΈ Common Pitfalls and Solutions
π± Pitfall 1: Secrets in Logs
# β Wrong way - secrets might leak!
- name: Deploy
run: |
echo "Deploying with token: ${{ secrets.API_TOKEN }}" # π± Don't log secrets!
# β
Correct way - keep secrets secret!
- name: Deploy
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: |
echo "π Deploying application..."
# Use $API_TOKEN in your script without logging it
π€― Pitfall 2: Not Caching Dependencies
# β Slow way - downloads packages every time
- name: Install dependencies
run: pip install -r requirements.txt
# β
Fast way - cache those packages!
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
- name: Install dependencies
run: pip install -r requirements.txt # β‘ Much faster!
π₯ Pitfall 3: Forgetting Cross-Platform Testing
# β Limited - only tests on Linux
runs-on: ubuntu-latest
# β
Comprehensive - test everywhere!
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
π οΈ Best Practices
- π― Start Simple: Begin with basic CI, then add features
- π Use Secrets: Never hardcode credentials - use GitHub Secrets
- π‘οΈ Pin Versions: Use specific action versions (
@v3
not@latest
) - β‘ Cache Everything: Dependencies, build artifacts, test results
- β¨ Keep DRY: Use reusable workflows and composite actions
- π Monitor Usage: Check your Actions minutes and optimize
- π Badge It: Add status badges to your README
π§ͺ Hands-On Exercise
π― Challenge: Build a Complete Python CI/CD Pipeline
Create a GitHub Actions workflow that:
π Requirements:
- β Tests on Python 3.8, 3.9, 3.10, and 3.11
- π§Ή Runs linting with
flake8
and formatting check withblack
- π‘οΈ Checks security with
bandit
- π Generates test coverage report
- π¦ Builds documentation with
sphinx
- π Deploys to PyPI on release
- π¨ Each step should have meaningful names with emojis!
π Bonus Points:
- Add type checking with
mypy
- Create a badge for your README
- Set up dependency updates with Dependabot
- Add performance benchmarking
π‘ Solution
π Click to see solution
# π― Complete CI/CD Pipeline Solution
name: π Python CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
release:
types: [created]
schedule:
- cron: '0 0 * * 0' # π
Weekly check
env:
PYTHON_DEFAULT: '3.9'
jobs:
# π§ͺ Testing job
test:
name: π§ͺ Test Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
steps:
- name: π₯ Checkout code
uses: actions/checkout@v3
- name: π Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: πΎ Cache dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
- name: π¦ Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: π§Ή Lint with flake8
run: |
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --statistics
- name: π¨ Check formatting with black
run: black --check .
- name: π‘οΈ Security check with bandit
run: bandit -r src/ -f json -o bandit-report.json
- name: π Type check with mypy
run: mypy src/ --ignore-missing-imports
- name: π§ͺ Run tests with pytest
run: |
pytest tests/ \
--cov=src \
--cov-report=xml \
--cov-report=html \
--cov-report=term-missing \
--junitxml=junit.xml \
-v
- name: π Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: true
- name: π Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results-${{ matrix.python-version }}
path: |
junit.xml
htmlcov/
# π Documentation job
docs:
name: π Build Documentation
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v3
- name: π Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_DEFAULT }}
- name: π¦ Install docs dependencies
run: |
pip install sphinx sphinx-rtd-theme
pip install -r requirements.txt
- name: π Build documentation
run: |
cd docs
make html
echo "β
Documentation built successfully!"
- name: π€ Upload documentation
uses: actions/upload-artifact@v3
with:
name: documentation
path: docs/_build/html/
# π Deploy job
deploy:
name: π Deploy to PyPI
needs: [test, docs]
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'created'
steps:
- uses: actions/checkout@v3
- name: π Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_DEFAULT }}
- name: π¦ Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: ποΈ Build package
run: python -m build
- name: β
Check package
run: |
twine check dist/*
echo "π¦ Package validation passed!"
- name: π Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
twine upload dist/*
echo "π Successfully published to PyPI!"
- name: π·οΈ Create GitHub Release Assets
uses: softprops/action-gh-release@v1
with:
files: dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# π Weekly report
weekly-report:
name: π Weekly Health Check
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v3
- name: π Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_DEFAULT }}
- name: π Check dependencies
run: |
pip install pip-audit
pip-audit --desc
echo "β
Dependency audit complete!"
π― Bonus: Add this badge to your README.md:

π Key Takeaways
Youβve learned so much! Hereβs what you can now do:
- β Create GitHub Actions workflows with confidence πͺ
- β Automate testing across multiple Python versions π
- β Set up continuous deployment to PyPI or web hosts π
- β Implement code quality checks automatically π‘οΈ
- β Debug workflow issues like a pro π
Remember: CI/CD isnβt about perfection on day one - itβs about continuous improvement! Start simple and add features as you need them. π€
π€ Next Steps
Congratulations! π Youβve mastered CI/CD with GitHub Actions for Python!
Hereβs what to do next:
- π» Set up your first workflow using the examples above
- ποΈ Add status badges to your README
- π Explore the GitHub Actions Marketplace for more actions
- π Share your automated workflows with the community!
Remember: Every DevOps expert started with a simple βHello Worldβ workflow. Keep automating, keep improving, and most importantly, have fun watching your robots do the work! π€β¨
Happy automating! ππβ¨