Every time you push code, tests should run. Every time you merge to main, a new version should deploy. GitHub Actions makes this automation native to your repository. No more external CI tools—everything lives alongside your code. In this lesson, you'll build production-ready pipelines that test, build, and deploy automatically.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Create GitHub Actions workflow files
- Automate testing on pull requests
- Build and push Docker images
- Deploy to cloud platforms (AWS, Azure)
- Use matrix builds for multiple versions
- Cache dependencies for faster workflows
2. Why GitHub Actions?
Real-world scenario: Every pull request needs to run tests. Every merge to main needs to build and deploy. Doing this manually is impossible. GitHub Actions automates everything, integrated directly into your repository.
Key benefits:
- ✅ No external CI/CD services needed
- ✅ Free for public repositories (2,000 minutes/month for private)
- ✅ Native GitHub integration
- ✅ Matrix builds for testing multiple versions
- ✅ Self-hosted runners option
3. Core Concepts
Workflow Structure
# .github/workflows/ci.yml
name: CI/CD Pipeline
# When to run
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch: # Manual trigger
# Environment variables
env:
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
# Jobs run in parallel
jobs:
test:
name: Test Application
runs-on: ubuntu-latest
# Strategy for matrix builds
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest]
fail-fast: false # Don't cancel all if one fails
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
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 pytest pytest-cov flake8 black
- name: Lint
run: flake8 . --count --max-complexity=10
- name: Format check
run: black --check .
- name: Test with pytest
run: pytest tests/ --cov=myapp --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
build:
name: Build Docker Image
runs-on: ubuntu-latest
needs: test # Wait for test to pass
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
environment:
name: production
url: https://example.com
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
run: |
echo "Deploying..."
# Add your deployment commands here
Complete CI/CD workflow
Common Actions and Patterns
# .github/workflows/actions-patterns.yml
# Cache npm dependencies
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
# Cache pip dependencies
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
# Upload artifact
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
# Download artifact
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
# Slack notification
- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "✅ Deployment successful!",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}*\nEnvironment: production\nStatus: ✅ Success"
}
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# Run on schedule
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
# Manual trigger with inputs
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
type: choice
options:
- staging
- production
version:
description: 'Version to deploy'
required: true
# Conditional steps
- name: Deploy to production
if: github.event.inputs.environment == 'production'
run: ./deploy-prod.sh
# Matrix builds
strategy:
matrix:
node-version: [18.x, 20.x]
os: [ubuntu-latest, windows-latest]
exclude:
- os: windows-latest
node-version: 18.x
Common GitHub Actions patterns
4. Complete Project: Production CI/CD Pipeline
# .github/workflows/production.yml
name: Production CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
AWS_REGION: us-east-1
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install
run: |
pip install -r requirements.txt
pip install pytest pytest-cov flake8 black mypy
- name: Lint
run: flake8 . --count --max-complexity=10 --statistics
- name: Type check
run: mypy src/
- name: Test
run: pytest tests/ -v --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
build:
runs-on: ubuntu-latest
needs: [test, security]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,format=short
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
runs-on: ubuntu-latest
needs: build
environment: staging
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to ECS
run: |
aws ecs update-service --cluster staging --service myapp --force-new-deployment
integration-tests:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- name: Run integration tests
run: |
echo "Waiting for deployment..."
sleep 60
curl -f https://staging.example.com/health || exit 1
pytest tests/integration/ --url=https://staging.example.com
deploy-production:
runs-on: ubuntu-latest
needs: integration-tests
environment:
name: production
url: https://example.com
needs: deploy-staging
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to production
run: |
aws ecs update-service --cluster production --service myapp --force-new-deployment
- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "🚀 Production deployment completed!",
"blocks": [{"type": "section", "text": {"type": "mrkdwn", "text": "*Deployment successful!*\nVersion: ${{ github.sha }}\nEnvironment: production"}}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Production-ready CI/CD pipeline
5. Self-Hosted Runners
# Install and configure self-hosted runner
# Download runner
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.308.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.308.0/actions-runner-linux-x64-2.308.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.308.0.tar.gz
# Configure
./config.sh --url https://github.com/owner/repo --token YOUR_TOKEN
# Run
./run.sh
# Install as service
sudo ./svc.sh install
sudo ./svc.sh start
# Use self-hosted runner in workflow
runs-on: self-hosted
# Or use labels
runs-on: [self-hosted, linux, x64]
Setting up self-hosted GitHub Actions runners
6. Common Errors & Solutions
7. Summary Checklist
8. Next Steps
Next category: Python & Automation - Programming Fundamentals
You'll learn to:
- Write Python scripts for automation
- Work with files, APIs, and system commands
- Build CLI tools for DevOps tasks
- Create reusable Python modules
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment