GitHub Actions Pipeline Architecture
GitHub Actions Pipeline Architecture
Trigger Configuration
GitHub Actions workflows are defined as YAML files in .github/workflows/. The on key controls when the pipeline runs. Push events trigger on branch commits, while pull_request events run on PR creation and updates. The schedule trigger uses cron syntax for periodic runs like nightly builds or dependency updates.
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Weekly Monday at 2am UTC
workflow_dispatch: # Manual trigger
inputs:
skip_tests:
description: 'Skip test suite'
required: false
default: 'false'
Job Dependency Graph
Jobs run in parallel by default. Use needs to create dependency chains. The outputs keyword passes data between jobs, and if conditions control conditional execution based on previous job results or context variables.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run test:unit
- run: npm run test:integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
- uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 14
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Push to registry
run: |
echo ${{ secrets.REGISTRY_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin
docker push myapp:${{ github.sha }}
docker push myapp:latest
Matrix Builds
Matrix strategy runs the same job across multiple OS or language versions in parallel. This catches compatibility issues before merge. Combine with exclude and include to fine-tune the matrix.
test-matrix:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
exclude:
- os: windows-latest
node-version: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
Automated Quality Gates
Automated Quality Gates
Linting and Type Checking
Quality gates enforce code standards before merge. Run Prettier for formatting, ESLint for code quality, and TypeScript compiler for type safety. The --max-warnings 0 flag treats warnings as failures.
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- name: Prettier check
run: npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css}"
- name: ESLint
run: npx eslint . --max-warnings 0 --format stylish
- name: TypeScript
run: npx tsc --noEmit
- name: Test coverage threshold
run: npm run test:coverage -- --coverageThresholds=80
if: github.event_name == 'pull_request'
PR Coverage Comment
Post coverage summaries as PR comments so reviewers see impact without leaving the pull request. The sticky-pull-request-comment action recreates the comment on each push.
- name: Generate coverage report
run: npm run test:coverage
if: github.event_name == 'pull_request'
- name: Comment coverage on PR
uses: marocchino/sticky-pull-request-comment@v2
with:
recreate: true
path: coverage/coverage-summary.json
header: 'coverage-report'
Security Scanning
Scan container images for known vulnerabilities using Trivy. Upload SARIF results to GitHub Security tab for centralized vulnerability tracking across the repository.
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image for scanning
run: docker build -t myapp:scan .
- name: Scan with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:scan'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Dependency audit
run: npm audit --audit-level=high
continue-on-error: true
Branch Protection Rules
Configure branch protection in repository settings or via the GitHub API. Required status checks must pass before merge. Require at least one approval and restrict force pushes to the main branch.
# Branch protection for main:
# Required status checks:
# - quality
# - test
# - security
# - build
# Require pull request reviews: 1 approval minimum
# Require branches to be up to date before merging
# Restrict who can push to matching branches
# Require signed commits
# Require linear history (no merge commits)
Deployment Strategies and Rollbacks
Deployment Strategies and Rollbacks
Blue-Green Deployment
Blue-green deployment maintains two identical environments. The blue environment serves production traffic while green is updated. After smoke testing green, traffic is switched over. If issues arise, traffic routes back to blue instantly.
# .github/workflows/deploy-production.yml
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- name: Deploy to green environment
run: |
docker-compose -f docker-compose.green.yml up -d --build
docker-compose -f docker-compose.green.yml exec -T app node scripts/migrate.js
- name: Health check green
run: |
for i in {1..15}; do
STATUS=$(curl -s -o /dev/null -w '%{http_code}' http://green.myapp.com/health)
if [ "$STATUS" = "200" ]; then
echo "Green environment healthy after $i attempts"
exit 0
fi
echo "Attempt $i: status $STATUS"
sleep 10
done
echo 'Green environment failed health checks'
exit 1
- name: Run smoke tests against green
run: |
npm run test:smoke -- --baseUrl=http://green.myapp.com
- name: Switch traffic to green
run: |
# Update load balancer or DNS to point to green
aws elbv2 modify-rule --rule-arn $RULE_ARN \
--actions Type=forward,TargetGroupArn=$GREEN_TG_ARN
echo 'Traffic switched to green'
- name: Verify production
run: |
sleep 30
STATUS=$(curl -s -o /dev/null -w '%{http_code}' https://myapp.com/health)
if [ "$STATUS" != "200" ]; then
echo 'Rolling back to blue...'
aws elbv2 modify-rule --rule-arn $RULE_ARN \
--actions Type=forward,TargetGroupArn=$BLUE_TG_ARN
exit 1
fi
echo 'Deployment verified successfully'
- name: Cleanup old environment
if: success()
run: docker-compose -f docker-compose.blue.yml down
Canary Deployment
Canary deployments gradually shift traffic to the new version. Start with 5% of traffic, monitor error rates and latency, then incrementally increase. Automated rollback triggers if metrics exceed thresholds.
canary-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy canary (5% traffic)
run: |
kubectl apply -f k8s/canary-deployment.yaml
kubectl set image deployment/app-canary app=myapp:${{ github.sha }}
kubectl scale deployment/app-canary --replicas=1
- name: Monitor canary metrics
run: |
sleep 120
ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..',version='canary'}[5m])/rate(http_requests_total{version='canary'}[5m])*100" | jq -r '.data.result[0].value[1]')
echo "Canary error rate: ${ERROR_RATE}%"
if (( $(echo "$ERROR_RATE > 1.0" | bc -l) )); then
echo 'Error rate exceeded threshold, rolling back'
kubectl delete deployment app-canary
exit 1
fi
- name: Promote canary to full deployment
run: |
kubectl set image deployment/app-production app=myapp:${{ github.sha }}
kubectl rollout status deployment/app-production --timeout=300s
kubectl delete deployment app-canary
Automated Rollback on Failure
deploy-with-rollback:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Capture current version
id: current
run: |
CURRENT=$(kubectl get deployment app -o jsonpath='{.spec.template.spec.containers[0].image}')
echo "version=$CURRENT" >> $GITHUB_OUTPUT
- name: Deploy new version
id: deploy
continue-on-error: true
run: |
kubectl set image deployment/app app=myapp:${{ github.sha }}
kubectl rollout status deployment/app --timeout=180s
- name: Rollback on failure
if: steps.deploy.outcome == 'failure'
run: |
echo "Deploy failed, rolling back to ${{ steps.current.outputs.version }}"
kubectl rollout undo deployment/app
kubectl rollout status deployment/app --timeout=180s
Database Migrations in CI/CD
Run database migrations as a separate step before application deployment. Use lock files or advisory locks to prevent concurrent migration runs across replicas.
migrate-and-deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Run migrations
run: |
npx prisma migrate deploy
echo 'Migrations applied successfully'
env:
DATABASE_URL: ${{ secrets.DATABASE_URL_PRODUCTION }}
- name: Verify schema
run: npx prisma db pull --print | head -20
- name: Deploy application
run: |
kubectl set image deployment/app app=myapp:${{ github.sha }}
kubectl rollout status deployment/app --timeout=300s
Secrets Management
GitHub Actions secrets are encrypted and masked in logs. Environment-specific secrets are scoped to deployment environments. Use GITHUB_TOKEN for repository operations without storing additional credentials.
deploy-with-secrets:
runs-on: ubuntu-latest
environment: production
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: us-east-1
- name: Deploy to ECS
run: |
aws ecs update-service --cluster production --service myapp \
--force-new-deployment
- name: Notify Slack
if: success()
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "Deployed ${{ github.sha }} to production"}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Quiz
1. What does the `needs` keyword do in a GitHub Actions workflow?
2. In a blue-green deployment, what is the primary advantage over a simple rolling update?
3. Why should you use `npm ci` instead of `npm install` in CI/CD pipelines?
Flashcards
Question
What is the difference between `on: push` and `on: pull_request` triggers in GitHub Actions?
Click to reveal answer
Answer
`on: push` triggers when code is pushed to a branch (merges, direct pushes). `on: pull_request` triggers when a PR is opened, synchronized, or reopened. Use push for main branch deployments and pull_request for CI validation before merge.
Question
How does a canary deployment differ from a blue-green deployment?
Click to reveal answer
Answer
Blue-green maintains two full environments and switches all traffic at once. Canary gradually shifts a small percentage of traffic (e.g., 5%) to the new version, monitors metrics, then increases. Canary reduces risk by catching issues with minimal blast radius before full rollout.
Question
What are GitHub Actions environments and why use them?
Click to reveal answer
Answer
Environments are deployment targets (staging, production) with their own secrets, variables, and protection rules. They enable environment-specific secrets, require manual approvals before deployment, provide deployment history, and allow conditional step execution based on the target environment.
Revision Notes
Key Takeaways
- 1. GitHub Actions workflows trigger on push, pull_request, schedule, and workflow_dispatch events with configurable branch filters
- 2. Use `needs` to create job dependency chains and pass data between jobs via `outputs`
- 3. Matrix strategy runs jobs across multiple OS/language versions to catch compatibility issues
- 4. Quality gates should enforce linting, type checking, test coverage thresholds, and security scans before merge
- 5. Blue-green deployment maintains two environments for instant rollback; canary gradually shifts traffic with metric monitoring
- 6. Use `npm ci` in CI for deterministic builds from package-lock.json instead of `npm install`
Interview Tips
- • Explain how you would structure a CI/CD pipeline for a microservices architecture with independent deployments
- • Describe the tradeoffs between blue-green, canary, and rolling deployment strategies
- • Discuss how to handle database migrations safely in a CI/CD pipeline with zero downtime
- • Explain the purpose of GitHub Actions environments and how they differ from repository secrets
- • Describe strategies for reducing CI pipeline execution time (caching, parallel jobs, matrix builds)
- • Discuss how to implement automated rollback when deployment health checks fail
Cheat Sheet
GitHub Actions: on: push/PR/schedule/workflow_dispatch triggers. needs: creates job dependencies. services: starts Docker containers for tests. matrix: parallel builds across OS/versions. Deploy: blue-green switches all traffic instantly, canary shifts percentage gradually. Secrets: environment-scoped, masked in logs. npm ci: deterministic installs from lockfile. Health checks: curl loop with retry logic. Rollback: kubectl rollout undo or traffic switch back to previous environment.