GitHub Actions Cheatsheet
Table of Contents
- GitHub Actions Basics
- Workflow Syntax
- Events & Triggers
- Jobs & Steps
- Actions (Marketplace)
- Secrets & Variables
- Matrix Builds
- Reusable Workflows
- Composite Actions
- Runners
- Security & Best Practices
- Advanced Patterns
- Migration from Jenkins
- Interview Scenarios
GitHub Actions Basics
1. Quick Start - First Workflow
# .github/workflows/hello-world.yml
name: Hello World
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run a one-line script
run: echo "Hello, World!"
- name: Run a multi-line script
run: |
echo "Add other steps here"
echo "Each line runs in the same shell"2. Basic CI/CD Workflow
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up 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
- name: Build application
run: npm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/Workflow Syntax
3. Complete Workflow Structure
name: Complete Example
# Workflow triggers
on:
push:
branches: [ main ]
paths:
- 'src/**'
- '!src/docs/**'
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
type: choice
options:
- staging
- production
# Global environment variables
env:
NODE_VERSION: '20'
APP_NAME: 'myapp'
# Default settings
defaults:
run:
shell: bash
working-directory: ./src
# Permissions
permissions:
contents: read
packages: write
issues: write
jobs:
build:
name: Build Application
runs-on: ubuntu-latest
# Job-level environment variables
env:
BUILD_ENV: production
# Job outputs
outputs:
version: ${{ steps.version.outputs.version }}
# Timeout
timeout-minutes: 30
# Run only if condition met
if: github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version
id: version
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Build
run: npm run build
env:
# Step-level environment variable
API_URL: https://api.example.com4. Workflow Commands
steps:
- name: Set outputs
run: |
echo "version=1.0.0" >> $GITHUB_OUTPUT
echo "status=success" >> $GITHUB_OUTPUT
- name: Set environment variable
run: echo "BUILD_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
- name: Add to PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Create summary
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- Version: 1.0.0" >> $GITHUB_STEP_SUMMARY
echo "- Status: ✅ Success" >> $GITHUB_STEP_SUMMARY
- name: Group logs
run: |
echo "::group::My Group"
echo "Inside the group"
echo "::endgroup::"
- name: Warning and error
run: |
echo "::warning::This is a warning"
echo "::error::This is an error"
- name: Debug message
run: echo "::debug::Debug information"
- name: Mask value
run: echo "::add-mask::$SECRET_VALUE"Events & Triggers
5. Common Event Triggers
# Push to specific branches
on:
push:
branches:
- main
- 'releases/**'
branches-ignore:
- 'feature/**'
tags:
- 'v*'
paths:
- 'src/**'
- '**.js'
paths-ignore:
- 'docs/**'
- '**.md'
# Pull requests
on:
pull_request:
types: [opened, synchronize, reopened]
branches: [ main ]
# Issues
on:
issues:
types: [opened, labeled]
# Schedule (cron)
on:
schedule:
- cron: '30 5 * * 1-5' # Weekdays at 5:30 AM
# Manual trigger
on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
type: choice
options:
- info
- warning
- debug
environment:
description: 'Environment'
required: false
type: string
# Workflow call (reusable)
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
token:
required: true
# Multiple events
on: [push, pull_request, workflow_dispatch]6. Activity Types
on:
pull_request:
types:
- opened
- synchronize
- reopened
- closed
- labeled
- unlabeled
on:
issue_comment:
types: [created, edited, deleted]
on:
release:
types: [published, created, edited]
on:
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main]Jobs & Steps
7. Job Dependencies
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Testing..."
deploy:
needs: [build, test]
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."8. Conditional Jobs
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Always runs"
deploy-staging:
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploy to staging"
deploy-production:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploy to production"9. Using Containers
jobs:
build:
runs-on: ubuntu-latest
# Job container
container:
image: node:20
env:
NODE_ENV: production
volumes:
- my_volume:/volume_mount
options: --cpus 2 --memory 4g
# Service containers
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- run: npm test10. Strategy - Parallel Jobs
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 4
matrix:
node: [16, 18, 20]
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- node: 20
os: ubuntu-latest
experimental: true
exclude:
- node: 16
os: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm testActions (Marketplace)
11. Essential Actions
# Checkout code
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
token: ${{ secrets.GITHUB_TOKEN }}
# Setup languages
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: true
# Docker
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
# Artifacts
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: |
dist/
!dist/**/*.map
retention-days: 5
- uses: actions/download-artifact@v4
with:
name: my-artifact
path: ./artifacts
# Cache
- uses: actions/cache@v4
with:
path: |
~/.npm
~/.cache
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# GitHub CLI
- uses: github/gh-actions@v1
with:
command: pr list --state open12. Popular Third-Party Actions
# Code quality
- uses: github/super-linter@v5
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: sonarsource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# Security scanning
- uses: aquasecurity/trivy-action@master
with:
image-ref: myimage:latest
format: 'sarif'
output: 'trivy-results.sarif'
- uses: github/codeql-action/init@v3
with:
languages: javascript, python
# Deployment
- uses: azure/webapps-deploy@v2
with:
app-name: 'my-app'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/my-role
aws-region: us-east-1
# Notifications
- uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Build ${{ github.run_number }} completed"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}Secrets & Variables
13. Using Secrets
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Access secret
- name: Use secret
run: echo "Token is ${{ secrets.API_TOKEN }}"
env:
TOKEN: ${{ secrets.API_TOKEN }}
# Never do this (secrets will be masked)
- name: Don't print secrets
run: echo "${{ secrets.API_TOKEN }}" # Will show ***14. Environment Variables
# Repository variables (Settings > Secrets and variables > Actions > Variables)
# Environment variables
# Secrets
env:
# Global
GLOBAL_VAR: 'global'
jobs:
build:
runs-on: ubuntu-latest
env:
# Job level
JOB_VAR: 'job'
steps:
- name: Use variables
env:
# Step level
STEP_VAR: 'step'
run: |
echo "Global: $GLOBAL_VAR"
echo "Job: $JOB_VAR"
echo "Step: $STEP_VAR"
echo "Variable: ${{ vars.MY_VARIABLE }}"
echo "Secret: ${{ secrets.MY_SECRET }}"
# Precedence: Step > Job > Workflow > Repository Variable15. Environments
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://production.example.com
steps:
- name: Deploy
run: |
echo "Deploying to production"
echo "URL: ${{ secrets.PRODUCTION_URL }}"
env:
# Environment-specific secrets
API_KEY: ${{ secrets.API_KEY }}Matrix Builds
16. Simple Matrix
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test17. Advanced Matrix
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node: [16, 18, 20]
include:
# Add extra combinations
- os: ubuntu-latest
node: 20
experimental: true
coverage: true
- os: macos-latest
node: 20
exclude:
# Remove specific combinations
- os: windows-latest
node: 16
continue-on-error: ${{ matrix.experimental || false }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
- if: matrix.coverage
run: npm run coverageReusable Workflows
18. Reusable Workflow Definition
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
version:
required: false
type: string
default: 'latest'
secrets:
deploy-token:
required: true
outputs:
deployment-url:
description: "Deployment URL"
value: ${{ jobs.deploy.outputs.url }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
outputs:
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Deploy
id: deploy
run: |
echo "Deploying version ${{ inputs.version }} to ${{ inputs.environment }}"
echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUT
env:
DEPLOY_TOKEN: ${{ secrets.deploy-token }}19. Calling Reusable Workflow
# .github/workflows/main.yml
name: Main Workflow
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Build step"
deploy-staging:
needs: build
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
version: '1.0.0'
secrets:
deploy-token: ${{ secrets.STAGING_TOKEN }}
deploy-production:
needs: deploy-staging
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
version: '1.0.0'
secrets:
deploy-token: ${{ secrets.PRODUCTION_TOKEN }}Composite Actions
20. Composite Action
# .github/actions/setup-app/action.yml
name: 'Setup Application'
description: 'Setup Node.js and install dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
cache:
description: 'Enable caching'
required: false
default: 'true'
outputs:
cache-hit:
description: 'Whether cache was hit'
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Cache dependencies
if: inputs.cache == 'true'
id: cache
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: npm ci
- name: Display version
shell: bash
run: node --version21. Using Composite Action
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup application
uses: ./.github/actions/setup-app
with:
node-version: '20'
cache: 'true'
- run: npm run build22. Node.js Build Pipeline Composite Action
# .github/actions/nodejs-build/action.yml
name: 'Node.js Build Pipeline'
description: 'Complete Node.js CI/CD pipeline with build, test, lint, and security scanning'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
run-tests:
description: 'Run tests'
required: false
default: 'true'
run-lint:
description: 'Run linting'
required: false
default: 'true'
run-security-scan:
description: 'Run security scanning'
required: false
default: 'true'
registry-url:
description: 'npm registry URL for publishing'
required: false
default: 'https://registry.npmjs.org'
outputs:
build-status:
description: 'Build status (success/failure)'
value: ${{ steps.build.outcome }}
test-results:
description: 'Test results path'
value: ${{ steps.test.outputs.results-path }}
coverage-percentage:
description: 'Test coverage percentage'
value: ${{ steps.test.outputs.coverage }}
artifact-name:
description: 'Build artifact name'
value: 'build-${{ github.sha }}'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
registry-url: ${{ inputs.registry-url }}
- name: Install dependencies
shell: bash
run: npm ci
- name: Run linting
if: inputs.run-lint == 'true'
shell: bash
run: |
npm run lint || echo "::warning::Linting found issues"
- name: Build application
id: build
shell: bash
run: |
npm run build
echo "Build completed successfully"
- name: Run tests
if: inputs.run-tests == 'true'
id: test
shell: bash
run: |
npm test -- --coverage --ci --reporters=default --reporters=jest-junit
COVERAGE=$(node -e "const c=require('./coverage/coverage-summary.json');console.log(c.total.lines.pct)")
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "results-path=test-results/junit.xml" >> $GITHUB_OUTPUT
- name: Security audit
if: inputs.run-security-scan == 'true'
shell: bash
run: |
npm audit --audit-level=moderate || echo "::warning::Security vulnerabilities found"
npx audit-ci --moderate || echo "::error::Critical vulnerabilities detected"
- name: Upload test results
if: inputs.run-tests == 'true' && always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ github.sha }}
path: |
test-results/
coverage/
retention-days: 30
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ github.sha }}
path: |
dist/
build/
retention-days: 7
- name: Generate build summary
shell: bash
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- Node.js: ${{ inputs.node-version }}" >> $GITHUB_STEP_SUMMARY
echo "- Build: ✅ Success" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.run-tests }}" == "true" ]; then
echo "- Tests: ✅ Passed" >> $GITHUB_STEP_SUMMARY
echo "- Coverage: ${{ steps.test.outputs.coverage }}%" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.run-lint }}" == "true" ]; then
echo "- Lint: ✅ Checked" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.run-security-scan }}" == "true" ]; then
echo "- Security: ✅ Scanned" >> $GITHUB_STEP_SUMMARY
fi23. Docker Build/Push Composite Action
# .github/actions/docker-build-push/action.yml
name: 'Docker Build and Push'
description: 'Build, scan, and push Docker images with security scanning and SBOM generation'
inputs:
image-name:
description: 'Docker image name'
required: true
registry:
description: 'Container registry (ghcr.io, docker.io, etc.)'
required: false
default: 'ghcr.io'
tags:
description: 'Image tags (comma-separated)'
required: false
default: 'latest'
dockerfile-path:
description: 'Path to Dockerfile'
required: false
default: './Dockerfile'
context:
description: 'Build context path'
required: false
default: '.'
push:
description: 'Push image to registry'
required: false
default: 'true'
scan:
description: 'Run security scanning'
required: false
default: 'true'
platforms:
description: 'Target platforms (e.g., linux/amd64,linux/arm64)'
required: false
default: 'linux/amd64'
build-args:
description: 'Build arguments (key=value, one per line)'
required: false
default: ''
outputs:
image-tag:
description: 'Full image tag with registry'
value: ${{ steps.meta.outputs.tags }}
digest:
description: 'Image digest'
value: ${{ steps.build.outputs.digest }}
scan-results:
description: 'Security scan results file'
value: 'trivy-results.sarif'
runs:
using: 'composite'
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
driver-opts: |
network=host
- name: Log in to Container Registry
if: inputs.push == 'true'
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.registry }}/${{ inputs.image-name }}
tags: |
type=raw,value=${{ inputs.tags }}
type=sha,prefix={{branch}}-
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile-path }}
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: ${{ inputs.build-args }}
provenance: true
sbom: true
- name: Run Trivy vulnerability scanner
if: inputs.scan == 'true'
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.tags }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
exit-code: '0'
- name: Upload Trivy results to GitHub Security
if: inputs.scan == 'true'
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
category: 'container-scan'
- name: Generate SBOM
if: inputs.push == 'true'
shell: bash
run: |
docker buildx imagetools inspect ${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.tags }} --format '{{json .SBOM}}' > sbom.json
echo "✅ SBOM generated: sbom.json"
- name: Upload SBOM
if: inputs.push == 'true'
uses: actions/upload-artifact@v4
with:
name: sbom-${{ github.sha }}
path: sbom.json
retention-days: 90
- name: Generate Docker build summary
shell: bash
run: |
echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- Image: \`${{ inputs.registry }}/${{ inputs.image-name }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Tags: \`${{ inputs.tags }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Digest: \`${{ steps.build.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Platforms: \`${{ inputs.platforms }}\`" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.push }}" == "true" ]; then
echo "- Pushed: ✅ Yes" >> $GITHUB_STEP_SUMMARY
else
echo "- Pushed: ❌ No (dry run)" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.scan }}" == "true" ]; then
echo "- Security Scan: ✅ Completed" >> $GITHUB_STEP_SUMMARY
fi
echo "- SBOM: ✅ Generated" >> $GITHUB_STEP_SUMMARY24. Using Production Composite Actions Together
name: Complete CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Node.js build and test
build:
runs-on: ubuntu-latest
outputs:
coverage: ${{ steps.nodejs-build.outputs.coverage-percentage }}
artifact: ${{ steps.nodejs-build.outputs.artifact-name }}
steps:
- uses: actions/checkout@v4
- name: Build Node.js application
id: nodejs-build
uses: ./.github/actions/nodejs-build
with:
node-version: '20'
run-tests: 'true'
run-lint: 'true'
run-security-scan: 'true'
- name: Check coverage threshold
run: |
COVERAGE=${{ steps.nodejs-build.outputs.coverage-percentage }}
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "::error::Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
# Docker build and push
docker:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push'
permissions:
contents: read
packages: write
security-events: write
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ${{ needs.build.outputs.artifact }}
path: dist/
- name: Build and push Docker image
id: docker-build
uses: ./.github/actions/docker-build-push
with:
image-name: ${{ env.IMAGE_NAME }}
registry: ${{ env.REGISTRY }}
tags: |
${{ github.ref_name }}
${{ github.sha }}
dockerfile-path: './Dockerfile'
context: '.'
push: 'true'
scan: 'true'
platforms: 'linux/amd64,linux/arm64'
build-args: |
BUILD_DATE=${{ github.event.head_commit.timestamp }}
VCS_REF=${{ github.sha }}
VERSION=${{ github.ref_name }}
- name: Image deployed
run: |
echo "✅ Image pushed: ${{ steps.docker-build.outputs.image-tag }}"
echo "📦 Digest: ${{ steps.docker-build.outputs.digest }}"
# Deploy to staging (on develop branch)
deploy-staging:
needs: [build, docker]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment:
name: staging
url: https://staging.example.com
steps:
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/app \
app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop \
--namespace=staging
# Deploy to production (on main branch)
deploy-production:
needs: [build, docker]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://production.example.com
steps:
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/app \
app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--namespace=productionRunners
25. GitHub-Hosted Runners
jobs:
build:
# Linux
runs-on: ubuntu-latest # ubuntu-22.04
runs-on: ubuntu-20.04
# Windows
runs-on: windows-latest # windows-2022
runs-on: windows-2019
# macOS
runs-on: macos-latest # macos-12
runs-on: macos-13
runs-on: macos-14 # M1
# Larger runners (Team/Enterprise)
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest-16-cores26. Self-Hosted Runners
jobs:
build:
# Single label
runs-on: self-hosted
# Multiple labels (all must match)
runs-on: [self-hosted, linux, x64]
# Custom labels
runs-on: [self-hosted, gpu, production]27. Self-Hosted Runner Setup
# Download
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.311.0.tar.gz -L \
https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.311.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
sudo ./svc.sh status
# Kubernetes (via Actions Runner Controller)
kubectl apply -f https://github.com/actions-runner-controller/actions-runner-controller/releases/download/v0.27.4/actions-runner-controller.yamlSecurity & Best Practices
28. Security Best Practices
# Minimal permissions
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
# Job-specific permissions
permissions:
contents: read
pull-requests: write
steps:
# Pin actions to commit SHA
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1
# Use dependabot to keep actions updated
# Don't expose secrets in logs
- name: Use secret safely
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: |
curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com
# Validate inputs
- name: Validate input
if: github.event_name == 'workflow_dispatch'
run: |
if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid version format"
exit 1
fi29. OIDC Authentication (AWS)
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- name: Deploy to S3
run: |
aws s3 sync ./dist s3://my-bucket/30. Code Scanning
name: CodeQL
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 1' # Weekly
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
matrix:
language: [ 'javascript', 'python' ]
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3Advanced Patterns
31. Conditional Steps
steps:
# Run on specific branch
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: echo "Deploying to production"
# Run on success
- name: Success step
if: success()
run: echo "Previous steps succeeded"
# Run on failure
- name: Failure step
if: failure()
run: echo "A step failed"
# Always run
- name: Cleanup
if: always()
run: echo "Always runs"
# Run if cancelled
- name: Cancelled step
if: cancelled()
run: echo "Workflow was cancelled"
# Complex conditions
- name: Complex condition
if: |
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
!contains(github.event.head_commit.message, '[skip ci]')
run: echo "Complex condition met"29. Dynamic Matrix
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
echo 'matrix={"include":[{"project":"A","config":"Debug"},{"project":"B","config":"Release"}]}' >> $GITHUB_OUTPUT
build:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
steps:
- run: echo "Building ${{ matrix.project }} in ${{ matrix.config }}"33. Approval Gates
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: production
# Required reviewers configured in environment settings
steps:
- run: echo "Deploying to production"34. Workflow Artifacts Between Jobs
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Build artifact" > artifact.txt
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: artifact.txt
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: my-artifact
- run: cat artifact.txt35. Cancel Previous Runs
name: CI
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Only latest run continues"36. Job Summaries
steps:
- name: Create summary
run: |
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Test | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Unit | ✅ Pass |" >> $GITHUB_STEP_SUMMARY
echo "| Integration | ✅ Pass |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Coverage: 85%" >> $GITHUB_STEP_SUMMARYMigration from Jenkins
37. Jenkins vs GitHub Actions
# Jenkins Declarative Pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'npm install'
sh 'npm run build'
}
}
stage('Test') {
steps {
sh 'npm test'
}
}
}
}# GitHub Actions Equivalent
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run build
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test38. Key Differences
| Feature | Jenkins | GitHub Actions |
|---|---|---|
| Configuration | Jenkinsfile | .github/workflows/*.yml |
| Agents | nodes/agents | runners |
| Parallel | parallel {} | strategy.matrix or parallel jobs |
| Shared Libraries | @Library() | Reusable workflows / Composite actions |
| Credentials | credentials() | secrets / vars |
| Artifacts | archiveArtifacts | upload-artifact / download-artifact |
| Environment | environment | env / environments |
| Approval | input | environment protection |
| Triggers | triggers | on: |
39. Migration Checklist
✅ Pre-Migration:
- [ ] Inventory Jenkins pipelines
- [ ] Identify shared libraries
- [ ] Document credentials/secrets
- [ ] Map Jenkins plugins to Actions
- [ ] Identify self-hosted runner needs
✅ Migration:
- [ ] Create .github/workflows directory
- [ ] Convert Jenkinsfile to workflow YAML
- [ ] Migrate secrets to GitHub Secrets
- [ ] Convert shared libraries to reusable workflows
- [ ] Set up runners (if needed)
- [ ] Configure branch protection rules
- [ ] Set up environments and approvals
✅ Post-Migration:
- [ ] Test workflows thoroughly
- [ ] Monitor execution times
- [ ] Optimize runner selection
- [ ] Review security settings
- [ ] Train team on GitHub Actions
- [ ] Decommission Jenkins pipelines40. GitHub Actions Importer
# Install GitHub CLI
brew install gh
# Install Actions Importer extension
gh extension install github/gh-actions-importer
# Configure
gh actions-importer configure
# Select: Jenkins
# Provide: Jenkins URL, username, token, GitHub token
# Dry-run migration
gh actions-importer dry-run jenkins --source-url https://jenkins.example.com/job/MyJob
# Migrate pipeline
gh actions-importer migrate jenkins --source-url https://jenkins.example.com/job/MyJob \
--target-url https://github.com/owner/repo
# Audit (analyze all pipelines)
gh actions-importer audit jenkins --output-dir ./audit-resultsInterview Scenarios
Scenario 1: Multi-Environment Deployment
name: Deploy
on:
push:
branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to registry
run: |
echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }}
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- name: Deploy to staging
run: |
kubectl set image deployment/myapp \
myapp=ghcr.io/${{ github.repository }}:${{ github.sha }} \
-n staging
deploy-production:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: https://www.example.com
steps:
- name: Deploy to production
run: |
kubectl set image deployment/myapp \
myapp=ghcr.io/${{ github.repository }}:${{ github.sha }} \
-n production
kubectl rollout status deployment/myapp -n productionScenario 2: Monorepo with Path Filtering
name: Monorepo CI
on:
push:
paths:
- 'services/**'
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
api: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
frontend:
- 'services/frontend/**'
backend:
- 'services/backend/**'
api:
- 'services/api/**'
build-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd services/frontend && npm ci && npm run build
build-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd services/backend && npm ci && npm run build
build-api:
needs: detect-changes
if: needs.detect-changes.outputs.api == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd services/api && npm ci && npm run buildScenario 3: Release Automation
name: Release
on:
push:
tags:
- 'v*'
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: changelog
run: |
CHANGELOG=$(git log $(git describe --tags --abbrev=0 HEAD^)..HEAD --oneline)
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
body: |
## Changes
${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
build-and-publish:
needs: create-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build artifacts
run: npm run build
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./dist/app.zip
asset_name: app-${{ github.ref_name }}.zip
asset_content_type: application/zipGitHub Actions CLI (gh)
41. GitHub CLI Workflow Commands
# List workflows
gh workflow list
# View workflow
gh workflow view ci.yml
# Run workflow
gh workflow run ci.yml
# Run with inputs
gh workflow run deploy.yml -f environment=production -f version=1.0.0
# View workflow runs
gh run list --workflow=ci.yml
# View specific run
gh run view 12345
# Watch run
gh run watch
# Download artifacts
gh run download 12345
# Cancel run
gh run cancel 12345
# Re-run workflow
gh run rerun 12345
# View logs
gh run view 12345 --logPerformance Optimization
42. Caching Strategies
# npm
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Custom cache
- uses: actions/cache@v4
with:
path: |
~/.npm
~/.cache
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# Docker layer caching
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max43. Optimizing Workflow Execution
# Use concurrency to cancel outdated runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Use path filters to skip unnecessary runs
on:
push:
paths:
- 'src/**'
- '!src/docs/**'
# Use job outputs to skip dependent jobs
jobs:
check:
outputs:
should-deploy: ${{ steps.check.outputs.deploy }}
steps:
- id: check
run: echo "deploy=true" >> $GITHUB_OUTPUT
deploy:
needs: check
if: needs.check.outputs.should-deploy == 'true'
steps:
- run: echo "Deploying"
# Parallelize independent jobs (don't use needs unless required)Total Commands: 120+ GitHub Actions operations
Focus: Jenkins migration, enterprise patterns, security
Last updated on