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

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen January 27, 2025

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment