02 Code Quality in Practice: Automation and CI/CD - Building Quality into Every Commit
October 2025 (9201 Words, 52 Minutes)
1. Introduction: From Manual Checks to Automated Workflows
In Chapter 02 (Code Quality in Practice), you learned about code quality tools like Ruff and Pyright. In Chapter 02 (Feature Branch Development), you learned about feature branch workflows and pull requests. Now you can:
# Create a feature branch
uv run ruff check .
uv run ruff format .
uv run pyright
# Create a pull request
But here’s the problem: What happens when:
- A team member forgets to run the checks before creating a PR?
- Someone pushes code without running the quality tools?
- A contributor doesn’t know about your quality standards?
- You want to ensure every single pull request meets your quality bar before it can be merged?
The answer: Automation. Specifically, Continuous Integration and Continuous Deployment (CI/CD).
This lecture is about moving from “please remember to run checks before creating a PR” to “checks run automatically on every PR, and code that doesn’t pass quality checks cannot be merged.”
We’ll build on the feature branch workflow you learned in Part 1 and add automated quality gates that ensure every change meets your standards before it reaches the main branch.
2. Learning Objectives
By the end of this lecture, you will:
- Understand CI/CD concepts and why they’re fundamental to modern software development
- Learn GitHub Actions - GitHub’s built-in automation platform
- Build automated quality checks that run on every push and pull request
- Create development workflows that enforce quality standards automatically
- Implement pre-commit hooks for local validation before code reaches GitHub
- Design a complete quality pipeline from local development to production
3. Part 1: What is CI/CD?
3.1 The Problem CI/CD Solves
Traditional Software Development (Pre-CI/CD):
Developer A writes code → Works on their machine ✅
Developer B writes code → Works on their machine ✅
Merge both → Integration Hell 🔥
↓
- Conflicting dependencies
- Different Python versions
- Untested integration points
- Manual testing takes days
- Deployment is a weekend event
Real Example:
# Developer A's machine (Python 3.12)
def process_data(items: list[str]) -> list[str]:
return [item.removeprefix("data_") for item in items] # Python 3.9+ feature
# Developer B's machine (Python 3.8)
# This code crashes with AttributeError: 'str' object has no attribute 'removeprefix'
Both developers think their code works. Only when they integrate does the problem appear.
3.2 What is Continuous Integration (CI)?
Continuous Integration (CI) is the practice of automatically integrating code changes from multiple contributors several times a day into a shared repository.
Key Principles:
- Frequent Integration - Merge code changes daily (or more often)
- Automated Build - Every integration triggers an automated build
- Automated Testing - Build includes running tests
- Fast Feedback - Developers know within minutes if integration breaks something
- Shared Responsibility - Everyone commits to fixing broken builds immediately
The CI Workflow:
Developer writes code
↓
Push to GitHub
↓
Automated checks run (CI)
- Install dependencies
- Run linters
- Run tests
- Check formatting
- Type checking
↓
Pass ✅ → Allow merge
Fail ❌ → Block merge, notify developer
3.3 What is Continuous Deployment (CD)?
Continuous Deployment (CD) extends CI by automatically deploying every change that passes all tests to production.
Two Flavors:
Continuous Delivery:
- Automatically prepare code for deployment
- Manual approval before production release
- Common in enterprise environments
Continuous Deployment:
- Fully automated deployment to production
- No manual intervention
- Every passed build goes live immediately
The CD Workflow:
CI Pipeline Passes ✅
↓
Automated Deployment
- Build production artifacts
- Run integration tests
- Deploy to staging
- Run smoke tests
- Deploy to production
↓
Success ✅ → Code is live
Fail ❌ → Automatic rollback
3.4 Benefits of CI/CD
1. Catch Issues Early
- Problems detected minutes after commit, not days or weeks
- Cheaper to fix (remember the 1x/10x/100x/1000x cost escalation?)
2. Faster Development
- No “integration phase” - code is always integrated
- Automated tests replace manual testing
- Faster feedback loops
3. Higher Quality
- Every commit is tested
- Consistent quality bar across the team
- No “works on my machine” problems
4. Reduced Risk
- Small, frequent changes are less risky than big releases
- Automated rollbacks minimize downtime
- Easy to identify which commit caused an issue
5. Better Collaboration
- Clear quality standards enforced automatically
- No style debates - tools enforce consistency
- Code reviews focus on logic, not formatting
Real-World Impact:
| Metric | Without CI/CD | With CI/CD |
|---|---|---|
| Time to detect bugs | Days to weeks | Minutes |
| Deployment frequency | Weekly/monthly | Multiple times daily |
| Failed deployments | 20-30% | <5% |
| Time to fix production issues | Hours to days | Minutes |
| Developer satisfaction | Lower (manual work) | Higher (automation) |
4. Part 2: Introduction to GitHub Actions
4.1 What is GitHub Actions?
GitHub Actions is GitHub’s built-in automation platform that lets you define workflows triggered by events in your repository.
Key Concepts:
Workflow:
- A configurable automated process
- Defined in YAML files in
.github/workflows/ - Can run one or more jobs
Event:
- Something that triggers a workflow
- Examples:
push,pull_request,schedule,release
Job:
- A set of steps that execute on the same runner
- Jobs run in parallel by default
Step:
- An individual task within a job
- Can run commands or use actions
Action:
- A reusable piece of functionality
- Like a function you can call in your workflow
Runner:
- A server that runs your workflows
- GitHub provides free runners (Ubuntu, Windows, macOS)
4.2 Workflow Syntax
Here’s a minimal GitHub Actions workflow:
name: My First Workflow # Workflow name (appears in GitHub UI)
on: [push, pull_request] # Events that trigger this workflow
jobs: # Define jobs
my-job: # Job ID
runs-on: ubuntu-latest # Runner to use
steps: # Steps in this job
- name: Checkout code # Step 1: Get the code
uses: actions/checkout@v4 # Use a predefined action
- name: Run a command # Step 2: Run a shell command
run: echo "Hello, World!" # The command to run
Breaking it down:
name: My First Workflow
- The display name shown in the GitHub Actions tab
on: [push, pull_request]
- Trigger this workflow when:
- Someone pushes code (
push) - Someone opens/updates a pull request (
pull_request)
- Someone pushes code (
jobs:
my-job:
runs-on: ubuntu-latest
- Define a job called
my-job - Run it on an Ubuntu Linux runner (latest version)
steps:
- name: Checkout code
uses: actions/checkout@v4
- Use a predefined action (
actions/checkout) to clone your repository - The
@v4specifies the version of this action
- name: Run a command
run: echo "Hello, World!"
- Execute a shell command
runlets you execute any command you’d run in a terminal
4.3 Finding and Choosing the Right Actions
One of the most powerful features of GitHub Actions is the GitHub Actions Marketplace - a vast ecosystem of reusable actions created by GitHub and the community. But how do you find the right action and know which version to use?
4.3.1 The GitHub Actions Marketplace
Where to find actions:
🔗 GitHub Marketplace - The official marketplace for GitHub Actions
How to search:
- Go to the Marketplace: Visit github.com/marketplace
- Filter by “Actions”: Use the left sidebar to show only Actions
- Search by purpose: Keywords like “Python”, “deploy”, “test”, “Docker”, etc.
- Sort by popularity: Choose “Most installed” to see battle-tested actions
Example search flow:
Search: "Python setup" → Find: actions/setup-python
Search: "Docker build" → Find: docker/build-push-action
Search: "AWS deploy" → Find: aws-actions/configure-aws-credentials
4.3.2 Evaluating Action Quality
Not all actions are created equal. Here’s how to evaluate if an action is trustworthy:
✅ Quality Indicators:
1. Official Actions (Highest Trust)
- Published by
actions/*(GitHub official) - Example:
actions/checkout,actions/setup-python - Always use these when available
2. Verified Creators
- Blue checkmark ✓ next to the publisher name
- Published by well-known organizations (Docker, AWS, Google, etc.)
- Example:
docker/build-push-action,aws-actions/configure-aws-credentials
3. Community Metrics
- Stars: 1,000+ stars indicates wide adoption
- Used by: Check how many public repositories use it
- Last updated: Updated within the last 6 months
- Issues/PRs: Active maintenance (issues are addressed, PRs are reviewed)
4. Documentation Quality
- Clear README with examples
- Documented inputs and outputs
- Security policy and changelog
❌ Red Flags:
- No updates in over a year
- Few stars (<100) and low usage
- No documentation or examples
- Many open, unaddressed issues
- No security policy
4.3.3 Understanding Action Versions
Actions use Git tags for versioning. You have several options:
Version Formats:
# ✅ RECOMMENDED: Major version (tracks latest compatible)
uses: actions/checkout@v4
# ⚠️ SPECIFIC: Exact version (safest, but misses updates)
uses: actions/checkout@v4.1.1
# ⚠️ BRANCH: Bleeding edge (can break without warning)
uses: actions/checkout@main
# ❌ NEVER: SHA only (hard to maintain)
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
Best Practice Recommendations:
| Version Type | Syntax | Updates | Stability | When to Use |
|---|---|---|---|---|
| Major version | @v4 |
Auto-updates to v4.x.x | ✅ High | Default choice - Gets patches & features, avoids breaking changes |
| Exact version | @v4.1.1 |
Never changes | ✅ Highest | Critical workflows, compliance requirements, reproducibility |
| Branch name | @main |
Every commit | ❌ Low | Testing unreleased features (not production!) |
| Commit SHA | @8ade135 |
Never changes | ✅ Highest | Security-critical workflows (but hard to maintain) |
Why use major versions (@v4)?
- ✅ Automatic security patches: Get bug fixes and security updates automatically
- ✅ New features: Benefit from non-breaking improvements
- ✅ No breaking changes: Major version won’t introduce incompatible changes
- ✅ Easy maintenance: Don’t need to manually update patch versions
Example of version evolution:
# You write this in 2025:
uses: actions/checkout@v4
# GitHub automatically uses latest v4.x.x:
# Jan 2025: v4.1.0 ✓
# Mar 2025: v4.1.5 ✓ (security patch - automatically applied!)
# Jun 2025: v4.2.0 ✓ (new feature - automatically available!)
# Jan 2026: v5.0.0 ✗ (breaking change - you need to update manually)
4.3.4 How to Find the Latest Version
Method 1: Marketplace Page
- Go to the action’s Marketplace page
- Look for “Latest version” badge in the sidebar
- Example: actions/checkout
Method 2: GitHub Releases
- Visit the action’s GitHub repository
- Click “Releases” in the right sidebar
- The top release is the latest version
- Example: github.com/actions/checkout/releases
Method 3: Repository Tags
- Go to the action’s repository
- Click the branch/tag dropdown
- Select “Tags” tab
- See all available versions sorted by date
Pro Tip: Check the README Most well-maintained actions show usage examples with the latest version in their README!
4.3.5 Common Actions You’ll Use
Here are the most popular actions you’ll encounter in this course and beyond:
Essential Actions for Python Projects:
# Clone your repository
- uses: actions/checkout@v4
# Set up uv (Python package manager)
- uses: astral-sh/setup-uv@v4
# Cache dependencies for speed
- uses: actions/cache@v4
# Upload artifacts (build outputs, logs, etc.)
- uses: actions/upload-artifact@v4
# Download artifacts from previous jobs
- uses: actions/download-artifact@v4
Why astral-sh/setup-uv instead of actions/setup-python?
In this course, we use uv as our Python package manager. The astral-sh/setup-uv@v4 action:
- ✅ Installs uv on the runner
- ✅ Automatically manages Python versions (uv handles this!)
- ✅ Provides built-in caching for dependencies
- ✅ Faster installation and dependency resolution
- ✅ Consistent with our local development workflow
For other languages:
# Set up Node.js
- uses: actions/setup-node@v4
# Set up Go
- uses: actions/setup-go@v5
# Set up Java
- uses: actions/setup-java@v4
Where to find these:
- Official actions: github.com/actions
- Astral (uv creators): github.com/astral-sh
- All maintained by their respective teams
- Always check the marketplace first
4.3.6 Security Best Practices
Always review actions before using them!
# ❌ DON'T: Use random actions without checking
- uses: unknown-user/sketchy-action@v1
# ✅ DO: Use official or verified actions
- uses: actions/checkout@v4 # GitHub official
- uses: docker/build-push-action@v5 # Docker official (verified)
Security checklist:
- ✅ Check the source code (it’s all open source!)
- ✅ Review what permissions the action needs
- ✅ Read issues/PRs for security concerns
- ✅ Use Dependabot to keep actions updated
- ✅ Pin to specific SHA for critical workflows
Enable Dependabot for GitHub Actions:
Create .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
This will automatically create PRs when action versions are outdated!
4.3.7 Practical Example: Choosing an Action
Scenario: You need to set up uv (Python package manager) in your workflow.
Step 1: Search
- Go to GitHub Marketplace
- Search: “uv python”
Step 2: Evaluate Results
astral-sh/setup-uv✓ Verified creator (Astral - the creators of uv)- ⭐ 500+ stars
- 📦 Used by thousands of repositories
- 📅 Recently updated
- ✅ Winner!
Step 3: Check Latest Version
- Visit: github.com/astral-sh/setup-uv/releases
- Latest: v4.x.x
- Major version to use:
@v4
Step 4: Read Documentation
- Check the README for usage examples
- Review inputs you can customize
- Look for common use cases
Step 5: Use in Your Workflow
steps:
- uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v4 # Use major version
- name: Install dependencies
run: uv sync --dev
- name: Run checks
run: uv run ruff check .
Why this works:
astral-sh/setup-uvinstalls uv on the runner- uv automatically manages Python versions (no need for
actions/setup-python!) - Faster dependency installation compared to pip
- Consistent with local development workflow
4.3.8 Quick Reference: Action Selection Guide
When you need to…
For Python Projects (This Course):
- Clone your repo →
actions/checkout@v4 - Set up uv (Python package manager) →
astral-sh/setup-uv@v4⭐ - Cache dependencies →
actions/cache@v4(or rely on uv’s built-in caching) - Upload files →
actions/upload-artifact@v4 - Run tests → Write your own
run: uv run pytestcommands (more control!)
For Other Languages:
- Set up Node.js →
actions/setup-node@v4 - Set up Go →
actions/setup-go@v5 - Set up Java →
actions/setup-java@v4
For Deployment:
- Build Docker images →
docker/build-push-action@v5(verified: Docker) - Deploy to AWS →
aws-actions/configure-aws-credentials@v4(verified: AWS) - Deploy to Azure →
azure/webapps-deploy@v3(verified: Azure)
Golden Rule: Start with official actions/* actions, then look for verified creators (like astral-sh for uv), then carefully evaluate community actions.
4.4 Creating Custom Actions
While we primarily use existing actions in this course, it’s worth knowing that you can create your own custom GitHub Actions for specialized workflows.
4.4.1 When to Create Custom Actions
Consider creating a custom action when:
- ✅ You have complex logic repeated across multiple workflows
- ✅ Your team needs organization-specific automation
- ✅ Existing actions don’t meet your specific requirements
- ✅ You want to share reusable workflow components
Don’t create a custom action when:
- ❌ A simple
run:command would suffice - ❌ An existing action already does what you need
- ❌ The logic is used in only one workflow
4.4.2 Types of Custom Actions
1. JavaScript Actions
- Run directly on the runner (fast)
- Best for: API calls, data processing, GitHub API interactions
- Written in Node.js
2. Docker Container Actions
- Run in an isolated container
- Best for: Complex dependencies, specific runtime requirements
- More flexible but slower to start
3. Composite Actions
- Combine multiple steps into a single reusable action
- Best for: Combining existing actions and shell commands
- Simplest to create
Example: Simple Composite Action
Create .github/actions/setup-project/action.yml:
name: 'Setup Project'
description: 'Install uv and project dependencies'
runs:
using: 'composite'
steps:
- name: Set up uv
uses: astral-sh/setup-uv@v4
shell: bash
- name: Install dependencies
run: uv sync --dev
shell: bash
- name: Show environment info
run: |
uv --version
uv run python --version
shell: bash
Use it in workflows:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project # Use local action
- run: uv run ruff check .
4.4.3 Publishing Custom Actions
For organizational use:
- Store in
.github/actions/directory in your repository - Reference with relative path:
uses: ./.github/actions/my-action
For public sharing:
- Create a dedicated repository for your action
- Add proper documentation (README, inputs/outputs)
- Publish to GitHub Marketplace
- Version with Git tags
4.4.4 Resources for Learning More
Creating custom actions is beyond the scope of this course, but if you’re interested:
- 📚 GitHub Actions - Creating Actions
- 📚 Metadata syntax for custom actions
- 📚 JavaScript action template
- 📚 Docker action template
For this course: Focus on using existing actions effectively. Custom actions are an advanced topic for when you’ve mastered workflow basics and have specific needs that existing actions don’t fulfill.
4.5 Common Workflow Triggers
# Trigger on push to specific branches
on:
push:
branches:
- main
- develop
# Trigger on pull requests targeting specific branches
on:
pull_request:
branches:
- main
# Trigger on multiple events
on: [push, pull_request, workflow_dispatch]
# Scheduled workflows (cron syntax)
on:
schedule:
- cron: '0 0 * * *' # Run daily at midnight UTC
# Manual trigger (workflow_dispatch)
on:
workflow_dispatch: # Adds "Run workflow" button in GitHub UI
inputs:
environment:
description: 'Environment to deploy to'
required: true
type: choice
options:
- staging
- production
4.6 GitHub Actions Pricing
For Public Repositories:
- ✅ Free unlimited minutes
- Perfect for open-source projects
- All runners available (Ubuntu, Windows, macOS)
For Private Repositories:
- Free tier: 2,000 minutes/month (Pro accounts: 3,000 minutes)
- Linux runners: 1x multiplier (1 minute = 1 minute)
- Windows runners: 2x multiplier (1 minute = 2 minutes)
- macOS runners: 10x multiplier (1 minute = 10 minutes)
For This Course:
- Use public repositories → Free unlimited CI/CD
- Your student projects should be public anyway (portfolio benefit!)
5. Part 3: Building Your First GitHub Actions Workflow
Let’s build a real workflow for the Road Profile Viewer project that runs quality checks.
5.1 Step 1: Create the Workflow File
Create .github/workflows/quality.yml in your repository:
name: Code Quality
on:
pull_request:
branches: [ main ]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
- name: Install dependencies
run: uv sync --dev
- name: Run Ruff linter
run: uv run ruff check .
- name: Check formatting
run: uv run ruff format --check .
- name: Run Pyright
run: uv run pyright
5.2 Understanding Each Step
Step 1: Checkout Repository
- name: Checkout repository
uses: actions/checkout@v4
- Clones your repository into the runner’s workspace
- Required for any workflow that needs access to your code
@v4is the latest stable version
Step 2: Set Up uv
- name: Set up uv
uses: astral-sh/setup-uv@v4
- Installs
uvon the runner - Uses the official action from Astral (uv’s creators)
- Caches uv installation for faster subsequent runs
Step 3: Cache Dependencies (Speed Optimization!)
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
This step is a game-changer for CI speed. Here’s what happens:
First time your workflow runs:
Install dependencies → Takes ~30-45 seconds → Cache saved ✅
Subsequent workflow runs (if uv.lock unchanged):
Restore from cache → Takes ~3-5 seconds → Continue! 🚀
Why this works:
Remember that uv.lock file that always gets created in your repository? It’s not just documentation—it’s a precise snapshot of every package and version your project needs. The cache uses this file as its “fingerprint”:
- Cache key includes
uv.lockhash → If dependencies change, the hash changes - Hash changes → Cache invalidates automatically → Fresh install with new dependencies
- Hash unchanged → Cache hits → Skip downloading packages again
The relief moment: “Wait, so uv.lock isn’t just clutter—it’s saving me 30 seconds on every CI run?” Exactly! This is why uv always creates it. The lock file ensures:
- Reproducible builds (everyone gets the same versions)
- Fast CI (cache knows exactly what to restore)
- Dependency tracking (see what changed between versions)
Pro tip: Never delete uv.lock or add it to .gitignore. It’s a critical part of your project’s infrastructure!
Step 4: Install Dependencies
- name: Install dependencies
run: uv sync --dev
- Creates virtual environment and installs project dependencies
--devincludes development dependencies (ruff, pyright, etc.)- With caching enabled, this step is 6-10x faster on subsequent runs
- Equivalent to what you’d run locally
Steps 5-7: Run Quality Checks
- name: Run Ruff linter
run: uv run ruff check .
- name: Check formatting
run: uv run ruff format --check .
- name: Run Pyright
run: uv run pyright
- Each check runs independently
- If any step fails, the workflow fails
- GitHub will mark the commit/PR with ❌
5.3 Committing and Pushing the Workflow
# Create the directory structure
mkdir -p .github/workflows
# Create the workflow file
# (paste the YAML content above)
# Commit and push
git add .github/workflows/quality.yml
git commit -m "Add GitHub Actions workflow for code quality"
git push origin main
What happens next:
- GitHub detects the new workflow file
- Workflow triggers on this push
- You can watch it run in the “Actions” tab
- Results appear next to your commit
5.4 Viewing Workflow Results
On GitHub:
- Go to your repository
- Click “Actions” tab
- See all workflow runs
- Click a run to see detailed logs
- Each step shows its output
Visual Indicators:
- ✅ Green checkmark = All checks passed
- ❌ Red X = At least one check failed
- 🟡 Yellow dot = Workflow running
- ⚪ Gray circle = Workflow pending/queued
5.5 When a Check Fails
Example: Ruff finds style issues
Run uv run ruff check .
src/main.py:16:1: E401 [*] Multiple imports on one line
src/main.py:29:5: E225 [*] Missing whitespace around operator
Found 2 errors.
Error: Process completed with exit code 1.
GitHub’s Response:
- ❌ Marks the commit/PR as failed
- Blocks merge if branch protection is enabled
- Notifies the developer
- Shows exactly what went wrong
Developer’s Response:
- Fix the issues locally
- Run checks locally to verify fix
- Push the fix
- Workflow runs automatically on the new commit
- If it passes: ✅ Ready to merge
6. Part 4: Advanced Workflow Patterns
6.1 Running Multiple Jobs in Parallel
Speed up your workflow by running independent checks in parallel:
name: Code Quality
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- run: uv run ruff check .
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- run: uv run ruff format --check .
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- run: uv run pyright
Benefits:
- Three jobs run simultaneously
- Faster total execution time
- See which specific check failed immediately
Drawback:
- More runner minutes consumed (but still free for public repos!)
- Slightly redundant setup steps
6.2 Matrix Testing (Multiple Python Versions)
Test your code against multiple Python versions using uv’s built-in Python management:
name: Test Multiple Python Versions
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- name: Set Python version
run: uv python pin ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --dev
- name: Run checks
run: |
uv run ruff check .
uv run pyright
What happens:
- GitHub creates 2 jobs (one per Python version)
uv python pintells uv which Python version to use- uv automatically downloads and manages the Python version
- Both jobs run in parallel
- All must pass for the workflow to succeed
- Great for ensuring compatibility
Why use uv for Python version management?
- ✅ No need for separate
actions/setup-pythonstep - ✅ Consistent with local development (
uv python install 3.12) - ✅ Faster installation (uv caches Python versions)
- ✅ Simpler workflow with fewer actions
6.3 Caching Dependencies for Speed
Speed up workflows by caching dependencies:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- name: Cache uv dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-
- run: uv sync --dev
- run: uv run ruff check .
How it works:
- First run: Downloads and caches dependencies
- Subsequent runs: Restore from cache (much faster)
- Cache invalidates when
uv.lockchanges - Typical speedup: 30 seconds → 5 seconds
6.4 Conditional Steps
Run steps only under certain conditions:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- name: Run linter
run: uv run ruff check .
- name: Auto-fix and commit (only on main branch)
if: github.ref == 'refs/heads/main'
run: |
uv run ruff check --fix .
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add -A
git diff --quiet && git diff --staged --quiet || git commit -m "Auto-fix: Apply Ruff suggestions"
git push
Use cases:
- Deploy only from
mainbranch - Run expensive checks only on PRs
- Different behavior for tags vs. branches
6.5 Using Secrets
Store sensitive data securely:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
echo "Deploying with API key: $API_KEY"
# Actual deployment commands
Managing Secrets:
- Go to repository Settings → Secrets and variables → Actions
- Click “New repository secret”
- Add name and value
- Reference in workflow with
${{ secrets.SECRET_NAME }} - Secret values are never exposed in logs
7. Part 5: Using GitHub Copilot to Create Workflows
7.1 Why Learn Both Manual and AI-Assisted Workflow Creation?
You’ve learned the fundamentals—now it’s time to leverage AI.
In Parts 3 and 4, you learned how to manually write GitHub Actions workflows. This foundational knowledge is critical because:
✅ Understanding enables verification - You can review and validate what Copilot generates
✅ Better prompts = better results - Knowing YAML syntax helps you write precise prompts
✅ Debugging requires knowledge - When workflows fail, you need to understand why
✅ AI is a tool, not magic - You guide the AI with your expertise
But we’re in the age of LLMs. Now that you understand workflows, let’s use GitHub Copilot to accelerate your work.
7.2 GitHub Copilot in VS Code
We use GitHub Copilot throughout this course as your AI pair programmer. For workflow creation, Copilot can:
- 🤖 Generate complete workflow files from natural language descriptions
- 🔄 Suggest improvements to existing workflows
- 🐛 Help debug workflow errors
- 📝 Add comments and documentation
- ⚡ Speed up repetitive workflow patterns
Two ways to use Copilot for workflows:
- Inline Suggestions - As you type YAML, Copilot suggests completions
- Copilot Chat - Describe what you want, get complete workflows
7.3 Practical Example: Creating a Workflow with Copilot Chat
Let’s create the same quality workflow from Part 3, but using Copilot Chat.
Step 1: Open Copilot Chat
In VS Code:
- Press
Ctrl+Alt+I(Windows/Linux) orCmd+Alt+I(Mac) - Or click the chat icon in the sidebar
- Or use Command Palette: “GitHub Copilot: Open Chat”
Step 2: Write a Clear Prompt
The better your prompt, the better the result. Use your workflow knowledge to write precise prompts.
❌ Vague prompt:
Create a GitHub Actions workflow for my Python project
✅ Specific prompt:
Create a GitHub Actions workflow for a Python project that:
- Triggers on push to main and pull requests
- Uses astral-sh/setup-uv@v4 to set up uv
- Installs dependencies with uv sync --dev
- Runs ruff check and ruff format --check
- Runs pyright type checker
- Uses Ubuntu runner
- Caches uv dependencies for faster runs
Save it as .github/workflows/quality.yml
Step 3: Review and Refine
Copilot generates:
name: Code Quality
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v4
- name: Cache uv dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-
- name: Install dependencies
run: uv sync --dev
- name: Run Ruff linter
run: uv run ruff check .
- name: Check formatting
run: uv run ruff format --check .
- name: Run Pyright
run: uv run pyright
✅ This is excellent! But always review:
- Does it match your requirements?
- Are the action versions correct?
- Is the syntax valid?
- Are there security concerns?
If something needs adjustment:
Can you also trigger this workflow on pushes to the develop branch?
Copilot updates the workflow:
on:
push:
branches: [ main, develop ] # ✅ Added develop
pull_request:
branches: [ main ]
Step 4: Create the File
Ask Copilot to create the file:
Create the file .github/workflows/quality.yml with this content
Copilot will:
- Create the directory structure if needed
- Generate the file
- Add the workflow content
Or create it manually:
- Create
.github/workflows/directory - Create
quality.ymlfile - Paste the generated content
7.4 Advanced Prompting Techniques
Leverage your workflow knowledge for better prompts.
Technique 1: Reference Specific Actions
Instead of:
Add Python setup to my workflow
Use your knowledge:
Add astral-sh/setup-uv@v4 to install uv, then use uv python pin to set Python 3.12
Technique 2: Specify Matrix Testing
Create a workflow that tests my Python project against Python 3.12 and 3.13
using a matrix strategy. Use uv python pin to switch versions.
Copilot generates:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- name: Set Python version
run: uv python pin $
- run: uv sync --dev
- run: uv run pytest
Technique 3: Request Conditional Logic
Add a deployment step that only runs on main branch after tests pass.
Use the "needs" keyword to create a dependency.
Technique 4: Ask for Explanations
Explain what this workflow does and why we use actions/cache@v4
Copilot explains the workflow, helping you learn while building.
7.5 Debugging Workflows with Copilot
When a workflow fails, use Copilot to debug.
Scenario: Workflow Error
You see this error in GitHub Actions:
Error: Unable to resolve action `astral-sh/setup-uv@v3`,
unable to find version `v3`
Ask Copilot:
My workflow is failing with "unable to find version v3" for astral-sh/setup-uv.
What's wrong and how do I fix it?
Copilot responds:
The error indicates that v3 doesn't exist for astral-sh/setup-uv.
The latest version is @v4. Update your workflow:
- uses: astral-sh/setup-uv@v4 # Changed from @v3
You can verify the latest version at:
https://github.com/astral-sh/setup-uv/releases
Ask for Optimization
My workflow takes 5 minutes to run. How can I make it faster?
Copilot suggests:
- Add caching for dependencies
- Run jobs in parallel
- Use matrix strategy efficiently
- Cache Python installations
7.6 Best Practices: Combining Manual Knowledge with AI
Do:
✅ Learn the fundamentals first (you did this in Parts 3-4!)
✅ Write specific prompts using correct terminology
✅ Review all generated code - you’re responsible for what runs
✅ Verify action versions in the marketplace
✅ Test workflows before merging to main
✅ Ask Copilot to explain unfamiliar patterns
✅ Iterate with Copilot - refine prompts based on results
Don’t:
❌ Blindly trust generated code without review
❌ Skip learning fundamentals and only use AI
❌ Use outdated actions Copilot suggests (verify versions!)
❌ Ignore security implications of generated workflows
❌ Accept the first suggestion - iterate for better results
7.7 Practical Exercise: Create Your Own Workflow
Now you try! Use Copilot to create a workflow for your Road Profile Viewer project.
Your task:
- Open Copilot Chat in VS Code
- Write a prompt for a workflow that:
- Runs on pull requests to main
- Sets up uv
- Installs dependencies
- Runs ruff, pyright, and pytest
- Uploads test coverage as an artifact
- Review the generated workflow
- Ask Copilot to improve it (add caching, better naming, etc.)
- Create the file and test it
Example prompt to get started:
Create a GitHub Actions workflow for my Python project that runs quality checks
and tests on pull requests. Use uv for dependency management, run ruff for
linting and formatting checks, pyright for type checking, and pytest for tests.
Upload test coverage results as an artifact. Add caching for faster runs.
7.8 The Modern Development Workflow
This is how modern software engineering works:
1. Understand fundamentals ✅ (Parts 3-4)
↓
2. Use AI to accelerate ⚡ (This part!)
↓
3. Review with expertise 🧠 (Your knowledge)
↓
4. Iterate and improve 🔄 (Refine prompts)
↓
5. Ship quality code 🚀
Key insight: AI amplifies your skills. The better you understand workflows, the better you can use Copilot to create them.
7.9 When to Use Manual vs. AI-Assisted Creation
Use manual creation when:
- 📚 Learning new concepts (like you did in Parts 3-4)
- 🔍 You need precise control over every detail
- 🐛 Debugging complex issues
- 📖 The pattern is simple and you know it well
Use AI-assisted creation when:
- ⚡ You need to iterate quickly
- 🔄 Creating variations of similar workflows
- 🆕 Exploring new actions or patterns
- 📝 Generating boilerplate quickly
- 🤝 Collaborating (Copilot explains your workflow to teammates)
Best approach: Combine both! Use your knowledge to guide AI, and use AI to speed up your work.
8. Part 6: Pre-commit Hooks - Local Quality Gates
8.1 Why Pre-commit Hooks?
Problem: Waiting for CI to fail is slow:
Write code → Commit → Push → CI runs (2 minutes) → CI fails ❌
↓
Fix issues → Commit → Push → CI runs (2 minutes) → CI passes ✅
Total time wasted: 4+ minutes
Solution: Catch issues before they reach GitHub using pre-commit hooks.
8.2 What are Pre-commit Hooks?
Pre-commit hooks are scripts that run automatically when you try to commit code.
Workflow:
git commit
↓
Pre-commit hook runs
- Checks code quality
- Runs linters
- Checks formatting
↓
Pass ✅ → Commit proceeds
Fail ❌ → Commit blocked, fix issues
Benefits:
- Instant feedback (seconds, not minutes)
- Prevents pushing broken code
- Saves CI minutes
- Consistent quality across team
8.3 Installing Pre-commit
# Install pre-commit as a tool (not project dependency)
uv tool install pre-commit
# Verify installation
pre-commit --version
8.4 Configuring Pre-commit
Create .pre-commit-config.yaml in your project root:
repos:
# Ruff linter and formatter
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
# Pyright type checker
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.388
hooks:
- id: pyright
# General checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
Install the hooks:
pre-commit install
This creates a .git/hooks/pre-commit script that runs automatically.
8.5 Using Pre-commit
Automatic on commit:
git add .
git commit -m "Add new feature"
# Pre-commit runs automatically:
# - Ruff linter checks code
# - Ruff formatter checks formatting
# - Pyright checks types
# - General checks run
#
# If all pass: Commit succeeds
# If any fail: Commit blocked
Manual run:
# Run on all files
pre-commit run --all-files
# Run specific hook
pre-commit run ruff --all-files
Skipping hooks (emergency only):
git commit --no-verify -m "Emergency hotfix"
8.6 Pre-commit Best Practices
DO:
- ✅ Keep hooks fast (<10 seconds total)
- ✅ Auto-fix when possible (Ruff with
--fix) - ✅ Run same checks in CI and pre-commit
- ✅ Document pre-commit setup in README
DON’T:
- ❌ Run slow tests in pre-commit (move to CI)
- ❌ Run external API calls (flaky, slow)
- ❌ Make pre-commit overly strict (frustrates developers)
9. Part 7: Building a Complete Quality Pipeline
Let’s combine everything into a comprehensive quality pipeline.
9.1 The Complete Setup
File: pyproject.toml
[project]
name = "road-profile-viewer"
version = "0.1.0"
description = "Interactive road profile visualization"
requires-python = ">=3.12"
dependencies = [
"numpy>=2.0.0",
"dash>=2.18.0",
"plotly>=5.24.0",
]
[build-system]
requires = ["uv>=0.4.0"]
build-backend = "uv"
[tool.uv.dev-dependencies]
ruff = ">=0.8.0"
pyright = ">=1.1.388"
pre-commit = ">=4.0.0"
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"N", # pep8-naming
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
[tool.pyright]
typeCheckingMode = "standard"
pythonVersion = "3.12"
File: .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.388
hooks:
- id: pyright
File: .github/workflows/quality.yml
name: Code Quality
on:
pull_request:
branches: [ main ]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
- name: Install dependencies
run: uv sync --dev
- name: Run Ruff linter
run: uv run ruff check .
- name: Check formatting
run: uv run ruff format --check .
- name: Run Pyright
run: uv run pyright
9.2 The Complete Developer Workflow
1. Initial Setup (once per developer):
# Clone repository
git clone https://github.com/your-username/road-profile-viewer.git
cd road-profile-viewer
# Set up environment
uv sync --dev
# Install pre-commit hooks
pre-commit install
2. Daily Development:
# Create feature branch
git checkout -b feature/new-visualization
# Write code with IDE feedback
# - VS Code + Pylance shows type errors in real-time
# - Ruff extension highlights style issues
# Run checks manually (optional, pre-commit will do this)
uv run ruff check --fix .
uv run ruff format .
uv run pyright
# Commit code
git add .
git commit -m "Add new visualization"
# Pre-commit hooks run automatically:
# ✅ Ruff linter - passed
# ✅ Ruff format - passed
# ✅ Pyright - passed
#
# Commit succeeds!
# Push to GitHub
git push origin feature/new-visualization
3. GitHub Responds:
GitHub Actions workflow triggered:
- Installing dependencies... ✅
- Running Ruff linter... ✅
- Checking formatting... ✅
- Running Pyright... ✅
All checks passed! ✅
4. Code Review:
Reviewer sees:
- ✅ All automated checks passed
- Can focus on:
- Business logic
- Algorithm correctness
- Architecture decisions
- NOT style/formatting (already validated)
5. Merge:
# Merge pull request on GitHub
# Or via command line:
git checkout main
git merge feature/new-visualization
git push origin main
# CI runs again on main branch
# Confirms everything still works after merge
9.3 Quality Gates at Every Level
| Level | Tool | When | Speed | Purpose |
|---|---|---|---|---|
| IDE | Pylance, Ruff extension | Real-time while typing | Instant | Catch errors as you write |
| Pre-commit | pre-commit hooks | Before each commit | <10s | Prevent bad commits |
| CI | GitHub Actions | After each push | 1-3 min | Verify in clean environment |
| Code Review | Human reviewers | Before merge | Hours | Check logic and design |
The Funnel Effect:
- Most issues caught in IDE (99%)
- Pre-commit catches what IDE missed (0.9%)
- CI catches environment-specific issues (0.09%)
- Code review catches logical issues (0.01%)
- Production: Nearly zero quality issues reach users
10. Part 8: Branch Protection and Merge Requirements
10.1 What is Branch Protection?
Branch protection rules prevent direct pushes to important branches and enforce quality requirements before merging.
Common protections:
- Require pull request reviews
- Require status checks to pass (CI)
- Require branches to be up to date
- Require signed commits
- Restrict who can push
10.2 Setting Up Branch Protection
On GitHub:
- Go to repository Settings
- Click “Branches” in sidebar
- Click “Add branch protection rule”
- Configure:
Branch name pattern: main
☑ Require a pull request before merging
☑ Require approvals: 1
☑ Dismiss stale pull request approvals when new commits are pushed
☑ Require status checks to pass before merging
☑ Require branches to be up to date before merging
Status checks required:
- quality (your GitHub Actions workflow name)
☑ Require conversation resolution before merging
☐ Require signed commits (optional, more advanced)
☐ Require linear history (optional, prevents merge commits)
☐ Include administrators (recommended: yes)
Click “Create” to activate.
10.3 What Happens Now?
Before Branch Protection:
# Anyone can push directly to main
git checkout main
git commit -m "Quick fix"
git push origin main # ✅ Works (but risky!)
After Branch Protection:
git push origin main # ❌ Blocked!
# Error: Protected branch 'main' cannot be pushed to
Required workflow:
# 1. Create feature branch
git checkout -b feature/fix
# 2. Make changes and commit
git commit -m "Fix bug"
# 3. Push feature branch
git push origin feature/fix
# 4. Open pull request on GitHub
# 5. Wait for CI to pass
# 6. Request code review
# 7. Get approval
# 8. Merge via GitHub UI (now allowed)
10.4 Benefits of Branch Protection
Quality Enforcement:
- No broken code can reach main
- Every change is reviewed
- CI must pass before merge
Collaboration:
- Forces discussion via pull requests
- Creates audit trail
- Improves code quality through review
Safety:
- Prevents accidental direct pushes
- Reduces “oops, broke production” incidents
- Easy to revert bad merges
11. Part 9: Advanced CI/CD Patterns
11.1 Deployment Pipeline
Extend your CI pipeline to include deployment:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- run: uv run ruff check .
- run: uv run pyright
- run: uv run pytest # Run tests
deploy:
needs: test # Only run if test job succeeds
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
echo "Deploying to production..."
# Your deployment commands here
11.2 Notifications
Get notified when workflows fail:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... your checks ...
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Quality checks failed!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
11.3 Workflow Badges
Show build status in your README:
# Road Profile Viewer

Interactive road profile visualization tool.
The badge updates automatically:
- ✅ Green: All checks passing
- ❌ Red: Checks failing
- 🟡 Yellow: Checks running
12. Summary: Key Takeaways
12.1 CI/CD is Not Optional
In modern software development, CI/CD is fundamental:
- Catches issues minutes after they’re introduced
- Enables fast, confident deployment
- Enforces quality standards automatically
- Essential for team collaboration
12.2 Layer Your Quality Gates
Build a quality funnel:
- IDE - Real-time feedback while writing
- Pre-commit - Prevent bad commits
- CI - Verify in clean environment
- Code Review - Human validation
- Production Monitoring - Catch what escaped
12.3 Automate Everything
If humans need to remember to do it, it won’t get done consistently:
- Automated tests > Manual testing
- Pre-commit hooks > “Please run linter”
- Branch protection > “Please don’t push to main”
- CI/CD > “Please check your code”
12.4 GitHub Actions is Powerful and Free
For public repositories (like student projects):
- Unlimited CI/CD minutes
- Easy integration with GitHub
- Rich ecosystem of actions
- Professional-grade automation
12.5 AI Amplifies Your Skills
In the age of LLMs:
- Learn fundamentals first - Understand workflows manually
- Use AI to accelerate - Let Copilot speed up your work
- Review with expertise - Your knowledge validates AI output
- Iterate and improve - Combine human insight with AI efficiency
Key insight: The better you understand CI/CD, the better you can use AI tools like GitHub Copilot to create sophisticated workflows.
13. Additional Resources
13.1 Official Documentation
- GitHub Actions: docs.github.com/actions
- pre-commit: pre-commit.com
- uv CI/CD guide: docs.astral.sh/uv/guides/integration/github
13.2 Learning Resources
- GitHub Actions Tutorial: GitHub Skills
- CI/CD Best Practices: Martin Fowler - Continuous Integration
- DevOps Handbook: Book by Gene Kim et al.
13.3 Tools and Extensions
- Act: github.com/nektos/act - Run GitHub Actions locally
- VS Code GitHub Actions Extension: Test workflows in VS Code
- pre-commit CI: pre-commit.ci - Run pre-commit in cloud
14. References
[1] GitHub Actions Documentation. https://docs.github.com/en/actions
[2] pre-commit Framework Documentation. https://pre-commit.com/
[3] Martin Fowler - Continuous Integration. https://martinfowler.com/articles/continuousIntegration.html
[4] Astral - uv CI/CD Integration. https://docs.astral.sh/uv/guides/integration/github/
[5] Kim, Gene et al. “The DevOps Handbook.” IT Revolution Press, 2016.
[6] Humble, Jez and David Farley. “Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation.” Addison-Wesley, 2010.
[7] GitHub Branch Protection Documentation. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches
[8] YAML Syntax Guide. https://yaml.org/spec/1.2/spec.html
Note: This lecture is based on GitHub Actions and tooling available as of October 2025. Platform features and specific actions may evolve over time.