Duration: 5 Hours
📚 Learning Objectives
By the end of this session, you will be able to:
- Understand CI/CD concepts and benefits
- Create GitHub Actions workflow files
- Set up automated testing on pull requests
- Use workflow triggers and conditions
- Implement basic automation pipelines
📖 Core Concepts (2 Hours)
What is CI/CD?
CI/CD (Continuous Integration/Continuous Deployment) is the practice of automating the building, testing, and deployment of code changes.
- Continuous Integration: Automatically build and test code on every push
- Continuous Delivery: Automatically prepare code for release
- Continuous Deployment: Automatically deploy to production
- Benefits: Faster feedback, fewer bugs, consistent releases
CI/CD Pipeline Overview
Developer → CI/CD Pipeline → Production
│ │ │
│ git push │ │
└────────────→ ┌────┴────┐ │
│ Build │ │
└────┬────┘ │
│ │
┌────┴────┐ │
│ Test │ │
└────┬────┘ │
│ │
┌────┴────┐ │
│ Review │ │
└────┬────┘ │
│ │
┌────┴────┐ Deploy │
│ Deploy │──────────────→
└─────────┘
Key Stages:
1. Build - Compile code, create artifacts
2. Test - Run automated tests
3. Review - Code review (PRs)
4. Deploy - Release to environment
What is GitHub Actions?
# GitHub Actions = CI/CD built into GitHub
Components:
┌─────────────────────────────────────────────┐
│ Workflow │
│ └── .github/workflows/ci.yml │
│ │ │
│ ├── Event (trigger) │
│ │ └── on: push, pull_request │
│ │ │
│ └── Jobs │
│ └── build: │
│ └── Steps │
│ ├── checkout code │
│ ├── setup environment │
│ ├── run tests │
│ └── deploy │
└─────────────────────────────────────────────┘
# Workflows are defined in YAML files
# Location: .github/workflows/*.yml
Basic Workflow Structure
# .github/workflows/ci.yml
name: CI Pipeline # Workflow name
on: # Triggers
push:
branches: [main]
pull_request:
branches: [main]
jobs: # Jobs to run
build: # Job name
runs-on: ubuntu-latest # Runner OS
steps: # Steps in job
- name: Checkout code
uses: actions/checkout@v4
- name: Run tests
run: echo "Running tests..."
- name: Build
run: echo "Building..."
Workflow Triggers (Events)
# Push triggers
on:
push:
branches: [main, develop]
paths:
- 'src/**' # Only when src changes
- '!docs/**' # Ignore docs changes
tags:
- 'v*' # When version tag pushed
# Pull request triggers
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Schedule (cron)
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
- cron: '0 */6 * * *' # Every 6 hours
# Manual trigger
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
# Multiple triggers
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
Jobs and Steps
jobs:
# Job 1: Test
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
echo "Running unit tests"
./run-tests.sh
# Job 2: Build (depends on test)
build:
needs: test # Run after 'test' job
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build application
run: ./build.sh
# Job 3: Deploy (depends on build)
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # Only on main
steps:
- name: Deploy
run: ./deploy.sh
# Jobs run in parallel by default
# Use 'needs' to create dependencies
Using Actions
# Actions are reusable workflow components
# Format: owner/repo@version
steps:
# Checkout repository code
- uses: actions/checkout@v4
# Setup Node.js
- uses: actions/setup-node@v4
with:
node-version: '20'
# Setup Python
- uses: actions/setup-python@v5
with:
python-version: '3.11'
# Cache dependencies
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# Upload artifact
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
# Download artifact
- uses: actions/download-artifact@v4
with:
name: build-output
# Find actions: github.com/marketplace?type=actions
Environment Variables and Secrets
# Environment variables
env: # Workflow level
NODE_ENV: production
jobs:
build:
env: # Job level
BUILD_TYPE: release
steps:
- name: Build
env: # Step level
DEBUG: 'true'
run: |
echo "Node env: $NODE_ENV"
echo "Build: $BUILD_TYPE"
echo "Debug: $DEBUG"
# Using GitHub context variables
steps:
- name: Print info
run: |
echo "Repository: ${{ github.repository }}"
echo "Branch: ${{ github.ref_name }}"
echo "Commit: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Event: ${{ github.event_name }}"
# Secrets (sensitive data)
# Set in: Settings → Secrets and variables → Actions
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
run: ./deploy.sh
# Never print secrets!
# run: echo "${{ secrets.API_KEY }}" # BAD!
Matrix Strategy
# Run tests on multiple configurations
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test
# This creates 9 jobs (3 node versions × 3 OS)
# Exclude combinations
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [16, 18, 20]
exclude:
- os: windows-latest
node: 16
# Include extra combinations
strategy:
matrix:
os: [ubuntu-latest]
node: [18, 20]
include:
- os: ubuntu-latest
node: 20
experimental: true
Conditionals
jobs:
deploy:
runs-on: ubuntu-latest
# Only run on main branch
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
# Only on push events
- name: Deploy to staging
if: github.event_name == 'push'
run: ./deploy-staging.sh
# Only if previous step succeeded
- name: Notify success
if: success()
run: echo "Deployment succeeded!"
# Only if previous step failed
- name: Notify failure
if: failure()
run: echo "Deployment failed!"
# Always run (even if cancelled)
- name: Cleanup
if: always()
run: ./cleanup.sh
# Common conditions:
# github.event_name == 'push'
# github.event_name == 'pull_request'
# github.ref == 'refs/heads/main'
# contains(github.event.head_commit.message, '[skip ci]')
# startsWith(github.ref, 'refs/tags/v')
Complete CI Example
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
run: |
sudo apt-get install -y shellcheck
shellcheck src/*.sh
test:
name: Run Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- name: Make scripts executable
run: chmod +x src/*.sh
- name: Run tests
run: |
echo "Testing hello.sh..."
./src/hello.sh
echo "Testing health-check.sh..."
./src/health-check.sh
build:
name: Build
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Create release archive
run: |
mkdir -p dist
tar -czvf dist/scripts-${{ github.sha }}.tar.gz src/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: scripts-bundle
path: dist/
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: scripts-bundle
- name: Deploy (simulation)
run: |
echo "Deploying to production..."
ls -la
echo "Deployment complete!"
PR Status Checks
# Workflow for PR checks
# .github/workflows/pr-check.yml
name: PR Checks
on:
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check file formatting
run: |
# Check for trailing whitespace
if grep -r '[[:space:]]$' src/; then
echo "Found trailing whitespace!"
exit 1
fi
- name: Validate scripts
run: |
for script in src/*.sh; do
if ! bash -n "$script"; then
echo "Syntax error in $script"
exit 1
fi
done
echo "All scripts valid!"
# Enable as required check:
# Settings → Branches → Branch protection rules
# → Require status checks → Select "check"
🔬 Hands-on Lab (2.5 Hours)
Lab 1: Create Your First Workflow
- Create workflow directory
- Write basic CI workflow
- Push and observe the run
# Lab 1: First workflow
cd ~/git-practice
# Create workflows directory
mkdir -p .github/workflows
# Create basic workflow
cat > .github/workflows/hello.yml << 'EOF'
name: Hello World
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: # Manual trigger
jobs:
greet:
runs-on: ubuntu-latest
steps:
- name: Say Hello
run: echo "Hello from GitHub Actions!"
- name: Print GitHub Context
run: |
echo "Repository: ${{ github.repository }}"
echo "Branch: ${{ github.ref_name }}"
echo "Actor: ${{ github.actor }}"
echo "Event: ${{ github.event_name }}"
- name: Print Date
run: date
EOF
# Commit and push
git add .github/workflows/hello.yml
git commit -m "Add first GitHub Actions workflow"
git push origin main
# Go to GitHub:
# 1. Click "Actions" tab
# 2. See your workflow running!
# 3. Click on it to view logs
Lab 2: CI Pipeline for Scripts
- Create testing workflow
- Run script tests
- Add linting
# Lab 2: CI for shell scripts
cd ~/git-practice
cat > .github/workflows/ci.yml << 'EOF'
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: Lint Shell Scripts
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install ShellCheck
run: sudo apt-get install -y shellcheck
- name: Run ShellCheck
run: |
echo "Checking shell scripts..."
find src -name "*.sh" -exec shellcheck {} \;
echo "Linting passed!"
test:
name: Test Scripts
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Make scripts executable
run: chmod +x src/*.sh
- name: Test hello.sh
run: |
echo "Testing hello.sh..."
output=$(./src/hello.sh)
if [[ "$output" == *"Hello"* ]]; then
echo "✓ hello.sh works!"
else
echo "✗ hello.sh failed"
exit 1
fi
- name: Test health-check.sh
run: |
echo "Testing health-check.sh..."
./src/health-check.sh
echo "✓ health-check.sh works!"
build:
name: Create Package
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create tarball
run: |
VERSION=$(date +%Y%m%d-%H%M%S)
tar -czvf scripts-${VERSION}.tar.gz src/ docs/ config.txt README.md
echo "Created scripts-${VERSION}.tar.gz"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: scripts-package
path: scripts-*.tar.gz
retention-days: 7
EOF
# Commit and push
git add .github/workflows/ci.yml
git commit -m "Add CI pipeline with lint, test, and build"
git push origin main
# Check Actions tab for the pipeline running!
Lab 3: PR Checks Workflow
- Create workflow for pull requests
- Test by creating a PR
- See status checks on PR
# Lab 3: PR validation
cd ~/git-practice
cat > .github/workflows/pr-checks.yml << 'EOF'
name: PR Checks
on:
pull_request:
branches: [main]
jobs:
validate:
name: Validate PR
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check PR title
run: |
TITLE="${{ github.event.pull_request.title }}"
if [[ ${#TITLE} -lt 10 ]]; then
echo "PR title too short (min 10 chars)"
exit 1
fi
echo "PR title OK: $TITLE"
- name: Check for TODO comments
run: |
if grep -r "TODO" src/; then
echo "Warning: Found TODO comments"
fi
- name: Validate shell scripts
run: |
for script in src/*.sh; do
echo "Validating $script..."
if ! bash -n "$script"; then
echo "Syntax error in $script"
exit 1
fi
done
echo "All scripts have valid syntax"
EOF
git add .github/workflows/pr-checks.yml
git commit -m "Add PR validation workflow"
git push origin main
# Now create a test PR
git checkout -b feature/test-pr-checks
echo '# Test file' > test-file.md
git add test-file.md
git commit -m "Add test file for PR checks"
git push -u origin feature/test-pr-checks
# Go to GitHub and create a PR
# Watch the checks run!
Lab 4: Scheduled Workflow
- Create a scheduled health check
- Understand cron syntax
- View scheduled run history
# Lab 4: Scheduled workflow
cd ~/git-practice
cat > .github/workflows/scheduled.yml << 'EOF'
name: Scheduled Health Check
on:
schedule:
# Run every day at 9 AM UTC
- cron: '0 9 * * *'
# Allow manual trigger for testing
workflow_dispatch:
jobs:
health:
name: Daily Health Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: System info
run: |
echo "=== Daily Health Check ==="
echo "Date: $(date)"
echo "Runner OS: ${{ runner.os }}"
echo ""
echo "Disk space:"
df -h
echo ""
echo "Memory:"
free -h
- name: Check repository
run: |
echo "Repository stats:"
echo "Files: $(find . -type f | wc -l)"
echo "Shell scripts: $(find . -name '*.sh' | wc -l)"
echo "Commits: $(git rev-list --count HEAD)"
- name: Run health script
run: |
chmod +x src/health-check.sh
./src/health-check.sh
EOF
git add .github/workflows/scheduled.yml
git commit -m "Add scheduled health check workflow"
git push origin main
# To test immediately:
# Go to Actions → Scheduled Health Check → Run workflow
Lab 5: Matrix Testing
- Create matrix workflow
- Test on multiple platforms
- Understand parallel execution
# Lab 5: Matrix builds
cd ~/git-practice
cat > .github/workflows/matrix.yml << 'EOF'
name: Matrix Test
on:
push:
branches: [main]
workflow_dispatch:
jobs:
test:
name: Test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, ubuntu-22.04, macos-latest]
fail-fast: false # Continue other jobs if one fails
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Show OS info
run: |
echo "Running on: ${{ matrix.os }}"
uname -a
- name: Run scripts (Linux/macOS)
run: |
chmod +x src/*.sh
./src/hello.sh
multi-version:
name: Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.9', '3.10', '3.11', '3.12']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Show Python version
run: |
python --version
echo "Testing with Python ${{ matrix.python }}"
EOF
git add .github/workflows/matrix.yml
git commit -m "Add matrix testing workflow"
git push origin main
# Watch multiple jobs run in parallel!
🎉 Week 3 Complete!
What you've learned this week:
- Git fundamentals: init, add, commit, status, log
- Branching and merging strategies
- Working with remote repositories
- GitHub workflows: PRs, Issues, Projects
- GitHub Actions for CI/CD automation
📝 Week 3 Project
Weekend Project: Complete Git Repository
Create a professional repository that includes:
- Well-organized directory structure
- Comprehensive README.md
- .gitignore file
- At least 3 useful shell scripts
- GitHub Actions CI pipeline that tests all scripts
- Branch protection rules on main
- Issue templates
- PR template
📝 Practice Exercises (30 Minutes)
- Create a workflow that runs on every push and prints repository info
- Add a job that uploads logs as an artifact
- Create a matrix workflow that tests on Ubuntu and macOS
- Add a manually triggered workflow with input parameters
- Enable required status checks on your main branch
📚 Resources
Additional Learning Materials
✅ Week 3 Final Checklist
- Confident with Git basics (init, add, commit, status, log)
- Can create and merge branches
- Can resolve merge conflicts
- Comfortable with remote operations (push, pull, fetch)
- Can create and manage Pull Requests
- Know how to use GitHub Issues and Projects
- Can write GitHub Actions workflows
- Understand CI/CD concepts
- Can set up automated testing on PRs