GitHub Actions is the most popular CI/CD platform with over 90 million workflow runs daily. This comprehensive guide covers workflow syntax, advanced deployment patterns, caching strategies, matrix builds, and enterprise DevOps automation with production-ready examples.
π Table of Contents
π Getting Started with GitHub Actions
GitHub Actions is GitHub's built-in CI/CD and automation platform. It allows you to define workflows as code in YAML files stored in your repository, trigger them on events (push, pull request, release, schedule), and execute jobs on GitHub-hosted or self-hosted runners.
Core Concepts
.github/workflows/
Your First Workflow
Create .github/workflows/ci.yml in your repository:
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Workflow Syntax Deep Dive
Understanding the full workflow syntax enables complex automation scenarios:
Trigger Events
on:
# Multiple events
push:
branches: [main]
tags: ['v*']
pull_request:
paths:
- 'src/**'
- 'tests/**'
schedule:
- cron: '0 2 * * 1-5' # 2 AM weekdays
workflow_dispatch: # Manual trigger
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'staging'
Environment Variables
env:
NODE_ENV: production
API_URL: https://api.example.com
jobs:
build:
env:
NODE_ENV: development
steps:
- name: Debug env
run: echo "Node env is $NODE_ENV"
Conditional Execution
steps:
- name: Deploy to production
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: ./deploy.sh production
- name: Notify on failure
if: failure()
run: ./notify.sh
π CI Pipeline Patterns
Multi-Stage Testing Pipeline
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
type-check:
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 type-check
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpass
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 test
env:
DATABASE_URL: postgres://postgres:testpass@localhost:5432/test
build:
runs-on: ubuntu-latest
needs: [lint, type-check, test] # Depends on all previous jobs
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
Docker Build & Push
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to 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
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
π CD & Deployment Strategies
Environment-Based Deployment
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
steps:
- uses: actions/checkout@v4
- name: Deploy
run: |
echo "Deploying to ${{ github.ref }}"
./deploy.sh ${{ github.ref_name }}
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Blue-Green Deployment
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to blue environment
run: |
# Deploy to blue (idle) environment
./deploy.sh blue
# Run smoke tests
./smoke-tests.sh blue
# Switch traffic
./switch-traffic.sh blue green
# Keep old version running briefly for rollback
sleep 300
# Decommission old (green) version
./decommission.sh green
Canary Deployment
jobs:
canary-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy canary (10% traffic)
run: ./deploy.sh --weight=10 --version=${{ github.sha }}
- name: Monitor canary metrics
run: |
echo "Monitoring for 10 minutes..."
sleep 600
# Check error rates
ERROR_RATE=$(./check-metrics.sh --metric=error_rate)
if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
echo "Error rate too high, rolling back"
./rollback.sh
exit 1
fi
# Promote to 100%
./deploy.sh --weight=100 --version=${{ github.sha }}
πΎ Caching Strategies
Caching dramatically speeds up workflows. GitHub Actions provides a cache action for this purpose.
npm Cache
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Built-in npm caching
- run: npm ci
pip Cache (Python)
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txt
Build Cache with Docker Layer Caching
steps:
- uses: actions/checkout@v4
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Restore Cache Manually
steps:
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
id: cache-dependencies
with:
path: |
node_modules
.venv
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-deps-
- name: Install dependencies
if: steps.cache-dependencies.outputs.cache-hit != 'true'
run: npm ci && npm run build
π Matrix Builds
Matrix builds let you test across multiple configurations in parallel:
Multi-Version Testing
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
operating-system: [ubuntu-latest, windows-latest]
exclude:
- node-version: 22
operating-system: windows-latest # Node 22 doesn't support Windows
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }} on ${{ matrix.operating-system }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install and test
run: |
npm ci
npm test
Multi-Database Testing
jobs:
integration-tests:
runs-on: ubuntu-latest
strategy:
matrix:
db: [postgres:14, postgres:15, mysql:8, mariadb:10.11]
services:
database:
image: ${{ matrix.db }}
env:
MYSQL_ROOT_PASSWORD: testpass
POSTGRES_PASSWORD: testpass
options: >-
--health-cmd pg_isready || mysqladmin ping
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
- 3306:3306
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm run integration-tests
env:
DATABASE_URL: ${{ startsWith(matrix.db, 'postgres') && 'postgres://postgres:testpass@localhost:5432/test' || 'mysql://root:testpass@localhost:3306/test' }}
π Security Best Practices
Using Secrets
steps:
- name: Deploy with secrets
env:
API_KEY: ${{ secrets.API_KEY }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: ./deploy.sh production
Encrypting Sensitive Data
# Using GPG to decrypt secrets at runtime
steps:
- name: Decrypt secrets
env:
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
echo "$GPG_KEY" | gpg --decrypt --quiet > secrets.json
# Use secrets.json in subsequent steps
- name: Deploy
env:
SECRETS_FILE: secrets.json
run: ./deploy.sh production
Limiting Permissions
permissions:
contents: read
packages: write
id-token: write # Required for OIDC authentication
jobs:
deploy:
permissions:
contents: read
id-token: write # Only what's needed for this job
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Deploy via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions-role
aws-region: us-east-1
Preventing Secret Leaks
# Add to workflow
- name: Prevent secret leaks
run: |
# Fail workflow if secrets are printed
echo "::add-matcher::.github/secret-leak-matcher.json"
# .github/secret-leak-matcher.json
{
"problemMatcher": [
{
"pattern": [
{
"regexp": "(?i)(secrets?|token|key|password|passwd|api.?key)",
"filePath": "test.txt"
}
],
"severity": "error"
}
]
}
π’ Enterprise Patterns
Reusable Workflows
Create reusable deployment workflows in a central repository:
# .github/workflows/deploy.yml (in centralized repo)
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
DEPLOY_TOKEN:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Deploy
run: ./deploy.sh ${{ inputs.environment }}
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Call it from other repos:
# In consuming repository
jobs:
deploy-staging:
uses: acme-corp/deployment-workflows/.github/workflows/deploy.yml@main
with:
environment: staging
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
OpenID Connect (OIDC) for Cloud Auth
# Configure AWS OIDC
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/GitHubActionsRole
aws-region: us-east-1
# No long-lived secrets neededβthe token is temporary
π‘ Our Verdict
GitHub Actions: The CI/CD Standard
Best for: Teams already using GitHub, open source projects, startups needing free CI/CD
Key strengths: Native GitHub integration, generous free tier, massive marketplace
Consider alternatives: For very complex enterprise needs, Jenkins or CircleCI may offer more advanced features
GitHub Actions has become the de facto standard for CI/CD on GitHub repositories. The combination of native integration, generous free tier (2,000 minutes/month for private repos, unlimited for public), and extensive marketplace makes it the default choice for most teams.
π Automate Your Deployments Today
GitHub Actions is free for open source and has generous limits for teams
Explore GitHub ActionsDisclosure: This guide contains affiliate links. We earn commissions from qualifying purchases at no additional cost to you.