Monitoring and Debugging GitHub Actions
When workflows fail in production, you need to diagnose problems quickly. GitHub Actions provides several layers of observability — from basic run logs to interactive SSH debugging sessions. Mastering these tools separates teams that fix issues in minutes from those that spend hours staring at cryptic error messages.
Workflow Run Logs
Every workflow run produces structured logs organized by job and step. Each step's output is collapsible, timestamped, and searchable. Key navigation techniques:
- Log grouping — Use
::group::Titleand::endgroup::to organize verbose output into collapsible sections - Annotations —
::warning file=app.js,line=1::Missing semicoloncreates clickable file annotations - Error masking —
::add-mask::SECRET_VALUEredacts sensitive data from logs - Log download — Full logs are available as downloadable archives for 90 days
# Adding structured log output to your workflow steps
name: Build with Grouped Logs
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
echo "::group::Installing npm packages"
npm ci
echo "::endgroup::"
echo "::group::Checking installed versions"
node --version
npm --version
echo "::endgroup::"
- name: Run tests
run: |
npm test 2>&1 | while IFS= read -r line; do
if echo "$line" | grep -q "FAIL"; then
echo "::error::$line"
else
echo "$line"
fi
done
Debug Logging and SSH Debugging
When standard logs aren't enough, GitHub Actions offers two escalation paths: verbose debug logging and interactive SSH sessions.
Debug Logging — Set the repository secret ACTIONS_STEP_DEBUG to true to enable step-level debug output. This reveals internal action execution details, environment variable resolution, and path operations that are normally hidden:
# Enable debug logging per-run via workflow_dispatch
name: Debug Build
on:
workflow_dispatch:
inputs:
debug_enabled:
description: 'Enable debug logging'
required: false
default: 'false'
type: boolean
jobs:
build:
runs-on: ubuntu-latest
env:
ACTIONS_STEP_DEBUG: ${{ inputs.debug_enabled }}
steps:
- uses: actions/checkout@v4
- name: Build
run: |
echo "::debug::Starting build at $(date)"
make build
echo "::debug::Build completed with exit code $?"
SSH Debugging with tmate — For truly puzzling failures, you can SSH into the runner mid-workflow. The mxschmitt/action-tmate action pauses execution and provides an SSH connection string:
# Interactive SSH debugging session
name: Debug via SSH
on: workflow_dispatch
jobs:
debug:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup environment
run: npm ci
- name: Start SSH session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' }}
with:
limit-access-to-actor: true # Only workflow triggerer can connect
detached: true # Continue workflow after timeout
limit-access-to-actor: true on tmate sessions and never use SSH debugging on workflows triggered by pull requests from forks. The runner has access to your repository secrets during the session.
Workflow Status Badges and GitHub CLI
Status Badges — Embed workflow status in your README to provide instant visibility:
# Badge URL format
# https://github.com/OWNER/REPO/actions/workflows/WORKFLOW_FILE/badge.svg
# Markdown for README.md


GitHub CLI for Run Inspection — The gh CLI provides powerful commands for workflow management:
# List recent workflow runs
gh run list --limit 10
# View a specific run's details
gh run view 12345678
# Watch a run in real-time
gh run watch 12345678
# Download run logs
gh run download 12345678
# Re-run failed jobs only
gh run rerun 12345678 --failed
# List workflows and their status
gh workflow list
# Trigger a workflow manually
gh workflow run deploy.yml -f environment=staging
# View run usage (billable time)
gh api repos/OWNER/REPO/actions/runs/12345678/timing
Workflow Best Practices and Performance Optimization
Slow workflows waste developer time and consume billable minutes. The difference between a 2-minute and 15-minute CI pipeline compounds across hundreds of daily pushes. Here are the highest-impact optimization techniques.
Minimizing Checkout Depth and Job Parallelism
Shallow clones — Most workflows don't need full git history. Use fetch-depth: 1 (or a small number) to dramatically reduce clone time on large repositories:
# Optimized checkout for CI builds
name: Fast CI
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1 # Shallow clone — fastest
# If you need tags for versioning:
- name: Fetch tags only
run: git fetch --tags --no-recurse-submodules
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history — only when needed (e.g., changelog generation)
Job Parallelism — Split independent tasks into parallel jobs. Each job gets its own runner, so they execute simultaneously:
# Parallel job execution — all 3 jobs start simultaneously
name: Parallel CI
on: push
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --shard=1/2
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:integration
# Deploy only after all parallel jobs pass
deploy:
needs: [lint, unit-tests, integration-tests]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: echo "All checks passed — deploying"
Reusable Workflows and Composite Actions
Reusable Workflows — Extract common workflow patterns into callable templates. Teams call them with uses: just like actions:
# .github/workflows/reusable-node-ci.yml (the reusable workflow)
name: Reusable Node.js CI
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
working-directory:
required: false
type: string
default: '.'
secrets:
NPM_TOKEN:
required: false
jobs:
ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json
- run: npm ci
- run: npm test
- run: npm run build
# Calling the reusable workflow from another repo
name: My App CI
on: push
jobs:
ci:
uses: my-org/.github/.github/workflows/reusable-node-ci.yml@main
with:
node-version: '22'
working-directory: './frontend'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Composite Actions — Bundle multiple steps into a single reusable action for shared setup patterns:
# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Install dependencies and configure environment'
inputs:
node-version:
description: 'Node.js version'
default: '20'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- run: npm ci
shell: bash
- run: npx playwright install --with-deps
shell: bash
Self-Hosted Runners for Heavy Workloads
For builds requiring GPU access, large memory, specific hardware, or reduced network latency, self-hosted runners provide dedicated infrastructure:
- No minute billing — you pay only for the infrastructure itself
- Persistent caches — tools and dependencies stay installed between runs
- Custom hardware — GPUs, ARM processors, high-memory machines
- Network access — reach internal services without VPN tunnels
fetch-depth: 1 unless you need history. (2) Cache dependencies aggressively (actions/cache). (3) Run independent jobs in parallel. (4) Use matrix strategies to test multiple versions simultaneously. (5) Skip unnecessary steps with path filters. (6) Use larger runners (4-core) for CPU-bound builds.
Cost Management and Usage Limits
GitHub Actions is free for public repositories with generous limits, but private repository usage can become expensive quickly — especially with macOS runners or large teams. Understanding the pricing model is essential for budget planning.
Free Tier and Pricing Breakdown
| Plan | Included Minutes/Month | Storage | Concurrent Jobs |
|---|---|---|---|
| Free | 2,000 (Linux) | 500 MB | 20 |
| Team | 3,000 (Linux) | 2 GB | 40 |
| Enterprise | 50,000 (Linux) | 50 GB | 500 |
| Public repos | Unlimited (free) | Unlimited | 20 |
Minute multipliers by OS — GitHub charges different rates depending on the runner OS:
| Runner OS | Minute Multiplier | Cost per Minute (overage) | Effective Rate |
|---|---|---|---|
| Linux | 1× | $0.008 | $0.48/hour |
| Windows | 2× | $0.016 | $0.96/hour |
| macOS | 10× | $0.08 | $4.80/hour |
| Linux (4-core) | 2× | $0.016 | $0.96/hour |
| Linux (8-core) | 4× | $0.032 | $1.92/hour |
Strategies to Reduce Spend
- Path filters — Only trigger workflows when relevant files change (
paths:filter) - Concurrency groups — Cancel redundant runs when new commits push (
concurrency: cancel-in-progress: true) - Timeout limits — Set
timeout-minuteson jobs to prevent runaway builds - macOS avoidance — Run iOS builds only on tagged releases, not every push
- Cache everything — Dependencies, Docker layers, build outputs
- Self-hosted for bulk — Move high-volume builds to self-hosted runners
- Scheduled cleanup — Delete old workflow run logs and artifacts to reduce storage
# Cost-optimized workflow with concurrency and timeouts
name: Efficient CI
on:
push:
branches: [main, develop]
paths:
- 'src/**'
- 'tests/**'
- 'package.json'
- 'package-lock.json'
pull_request:
paths:
- 'src/**'
- 'tests/**'
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true # Cancel previous runs on same branch
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15 # Kill if stuck longer than 15 minutes
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Built-in dependency caching
- run: npm ci
- run: npm test
# macOS builds only on release tags
ios-build:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- run: xcodebuild -scheme MyApp -sdk iphonesimulator build
Integrating with Popular Tools
GitHub Actions shines when connected to the broader DevOps ecosystem. Here are production-ready integration patterns for the most commonly requested tools.
Slack Notifications
Send deployment notifications, build failures, and PR updates directly to Slack channels using the official slackapi/slack-github-action:
# Slack notification on deployment success/failure
name: Deploy and Notify
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
id: deploy
run: ./deploy.sh
- name: Notify Slack - Success
if: success()
uses: slackapi/slack-github-action@v2.0.0
with:
webhook-type: incoming-webhook
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":white_check_mark: *Deploy Succeeded*\n*Repo:* ${{ github.repository }}\n*Branch:* `${{ github.ref_name }}`\n*Commit:* `${{ github.sha }}` by ${{ github.actor }}"
}
}
]
}
- name: Notify Slack - Failure
if: failure()
uses: slackapi/slack-github-action@v2.0.0
with:
webhook-type: incoming-webhook
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":x: *Deploy FAILED*\n*Repo:* ${{ github.repository }}\n*Branch:* `${{ github.ref_name }}`\n*Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Logs>"
}
}
]
}
Docker Hub Build and Push
# Build and push Docker image to Docker Hub
name: Docker Build & Push
on:
push:
tags: ['v*']
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: myorg/myapp
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
AWS and Terraform
# AWS deployment with OIDC authentication (no long-lived credentials)
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials (OIDC)
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 --delete
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
--paths "/*"
# Terraform plan and apply workflow
name: Terraform
on:
push:
branches: [main]
paths: ['infra/**']
pull_request:
paths: ['infra/**']
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./infra
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/TerraformRole
aws-region: us-east-1
- name: Terraform Init
run: terraform init
- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan
continue-on-error: true
- name: Comment Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const plan = `${{ steps.plan.outputs.stdout }}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `#### Terraform Plan 📖\n\`\`\`\n${plan.substring(0, 65000)}\n\`\`\``
});
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
SonarCloud, Datadog, and New Relic
# SonarCloud code quality analysis
name: SonarCloud Analysis
on:
push:
branches: [main]
pull_request:
jobs:
sonarcloud:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for blame data
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@v3
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.organization=my-org
-Dsonar.projectKey=my-project
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
# Datadog CI visibility — send test results and pipeline metrics
name: CI with Datadog
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- name: Run tests with Datadog tracing
env:
DD_API_KEY: ${{ secrets.DD_API_KEY }}
DD_ENV: ci
DD_SERVICE: my-app
NODE_OPTIONS: '-r dd-trace/ci/init'
run: npm test
- name: Upload test results to Datadog
if: always()
uses: datadog/junit-upload-github-action@v2
with:
api-key: ${{ secrets.DD_API_KEY }}
service: my-app
files: junit-results.xml
Migrating from Other CI/CD Platforms
Moving to GitHub Actions from an established CI/CD platform requires understanding how concepts map between systems. Every platform has workflows, jobs, and steps — but they use different terminology, file formats, and capabilities.
flowchart LR
subgraph Jenkins
J1[Jenkinsfile]
J2[Pipeline/Stage]
J3[Step/sh]
J4[Agent/Node]
J5[Shared Library]
end
subgraph GitLab CI
G1[.gitlab-ci.yml]
G2[Stage/Job]
G3[Script]
G4[Runner/Tags]
G5[Include/Extend]
end
subgraph CircleCI
C1[.circleci/config.yml]
C2[Workflow/Job]
C3[Step/run]
C4[Executor]
C5[Orb]
end
subgraph GitHub Actions
A1[.github/workflows/*.yml]
A2[Job/Step]
A3[run/uses]
A4[Runner]
A5[Reusable Workflow/Action]
end
J1 --> A1
G1 --> A1
C1 --> A1
Platform Concept Comparison Table
| Concept | Jenkins | GitLab CI | CircleCI | GitHub Actions |
|---|---|---|---|---|
| Config file | Jenkinsfile | .gitlab-ci.yml | .circleci/config.yml | .github/workflows/*.yml |
| Pipeline | Pipeline | Pipeline | Workflow | Workflow |
| Stage/Group | Stage | Stage | Job | Job |
| Execution unit | Step | Script block | Step | Step |
| Compute | Agent/Node | Runner (tagged) | Executor | Runner (labeled) |
| Reusability | Shared Library | include/extends | Orbs | Actions/Reusable workflows |
| Secrets | Credentials plugin | CI/CD Variables | Contexts/Env vars | Secrets (repo/org/env) |
| Caching | Plugin-based | Built-in (cache:) | Built-in (save/restore) | actions/cache |
| Artifacts | archiveArtifacts | artifacts: | persist_to_workspace | actions/upload-artifact |
| Manual approval | input step | when: manual | requires approval | Environments + reviewers |
- Source code host — If you're already on GitHub, Actions eliminates integration complexity
- Plugin ecosystem — Jenkins has 1800+ plugins; map critical ones to Actions equivalents before migrating
- Runner requirements — Self-hosted Jenkins agents translate directly to self-hosted GitHub runners
- Compliance needs — Audit trails, RBAC, and environment protections differ between platforms
- Migration approach — Run both systems in parallel during transition; migrate team by team
Migrating from Jenkins
Jenkins pipelines (Declarative or Scripted) map to GitHub Actions workflows with some structural differences. Here's a side-by-side comparison:
Before — Jenkinsfile (Declarative Pipeline):
# Jenkinsfile
pipeline {
agent any
environment {
NODE_VERSION = '20'
DEPLOY_ENV = 'production'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'npm run test:unit'
}
}
stage('Integration Tests') {
steps {
sh 'npm run test:integration'
}
}
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Deploy') {
when {
branch 'main'
}
input {
message 'Deploy to production?'
}
steps {
sh './deploy.sh'
}
}
}
post {
failure {
slackSend channel: '#builds', message: "FAILED: ${env.JOB_NAME}"
}
}
}
After — GitHub Actions Workflow:
# .github/workflows/ci-cd.yml (equivalent to the Jenkinsfile above)
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
DEPLOY_ENV: production
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run test:unit
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run test:integration
build:
needs: [unit-tests, integration-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production # Requires manual approval via environment protection rules
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- run: ./deploy.sh
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v2.0.0
with:
webhook-type: incoming-webhook
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{"text": "FAILED: ${{ github.workflow }} - ${{ github.ref_name }}"}
Migrating from GitLab CI
GitLab CI uses a single .gitlab-ci.yml with stages that run sequentially. Key differences:
| GitLab CI Feature | GitHub Actions Equivalent |
|---|---|
stages: |
jobs: with needs: for ordering |
image: node:20 |
container: image: node:20 or setup-node action |
cache: paths: |
actions/cache@v4 or built-in setup action caching |
artifacts: paths: |
actions/upload-artifact@v4 |
include: remote: |
Reusable workflows (uses: org/repo/.github/workflows/x.yml) |
extends: .template |
Composite actions or reusable workflows |
rules: - if: |
Job-level if: conditions |
when: manual |
workflow_dispatch or environment protection rules |
# GitLab CI (.gitlab-ci.yml) — BEFORE
stages:
- test
- build
- deploy
variables:
NODE_VERSION: "20"
test:
stage: test
image: node:20
cache:
paths:
- node_modules/
script:
- npm ci
- npm test
only:
- merge_requests
- main
# GitHub Actions equivalent — AFTER
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
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 test
Migrating from CircleCI
CircleCI's config structure is closest to GitHub Actions. The main differences are in naming and how reusable components work (Orbs vs Actions):
# CircleCI (.circleci/config.yml) — BEFORE
version: 2.1
orbs:
node: circleci/node@5.2
workflows:
build-and-test:
jobs:
- node/test:
version: '20'
- build:
requires:
- node/test
- deploy:
requires:
- build
filters:
branches:
only: main
jobs:
build:
docker:
- image: cimg/node:20.0
steps:
- checkout
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm ci
- save_cache:
paths:
- node_modules
key: v1-deps-{{ checksum "package-lock.json" }}
- run: npm run build
- persist_to_workspace:
root: .
paths:
- dist/
deploy:
docker:
- image: cimg/base:stable
steps:
- attach_workspace:
at: .
- run: ./deploy.sh
# GitHub Actions equivalent — AFTER
name: Build and Test
on:
push:
branches: [main]
pull_request:
jobs:
test:
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 test
build:
needs: test
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 build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: ./deploy.sh
Exercises
Build a Self-Diagnosing Workflow
Create a workflow that: (1) runs your test suite, (2) on failure, automatically enables debug logging and re-runs, (3) posts the debug logs to a Slack channel, and (4) opens a tmate SSH session if the second run also fails. Use workflow_dispatch inputs so developers can manually trigger with debug mode enabled.
Optimize a Slow Monorepo Pipeline
Given a monorepo with frontend/, backend/, and infra/ directories: (1) Create path-filtered workflows that only build affected services. (2) Implement aggressive caching (npm, Docker layers, Terraform providers). (3) Add concurrency groups to cancel stale runs. (4) Add timing annotations to identify the slowest steps. Target: reduce a 20-minute pipeline to under 5 minutes for single-service changes.
Full DevOps Integration Pipeline
Build a production-grade workflow that: (1) Runs SonarCloud analysis on every PR. (2) Builds and pushes a Docker image to Docker Hub on merge to main. (3) Deploys infrastructure with Terraform (plan on PR, apply on main). (4) Configures AWS credentials via OIDC. (5) Sends Slack notifications with deployment status and links. All secrets must use environment-scoped secrets with protection rules.
Migrate a Jenkins Pipeline to GitHub Actions
Take a real or sample Jenkinsfile with: (1) Multiple stages (build, test, scan, deploy). (2) Parallel test execution. (3) Manual approval gates. (4) Post-build notifications. (5) Shared library calls. Convert it completely to GitHub Actions. Document every Jenkins concept and its Actions equivalent. Run both pipelines in parallel for one sprint to validate parity.
Bootcamp Complete — All 11 Modules
Here's a summary of everything covered across the bootcamp:
| Module | Topic | Key Skills Gained |
|---|---|---|
| 1 | Introduction to GitHub Actions | YAML syntax, first workflow, runners, components |
| 2 | Workflow Triggers and Events | 35+ event types, filtering, repository dispatch |
| 3 | Jobs, Runners, and Execution | Job dependencies, matrix builds, containers |
| 4 | Actions and the Marketplace | Using, creating, and publishing custom actions |
| 5 | Secrets, Variables, and Security | Secret management, OIDC, security hardening |
| 6 | Artifacts, Caching, and Outputs | Build artifacts, dependency caching, job outputs |
| 7 | CI Workflows | Testing, linting, code quality, multi-language CI |
| 8 | CD and Deployment Workflows | Environments, deployment strategies, rollbacks |
| 9 | Advanced Workflow Patterns | Reusable workflows, dynamic matrices, fan-out/fan-in |
| 10 | Real-World Project | End-to-end pipeline for a production application |
| 11 | Monitoring, Optimization & Migration | Debugging, cost control, integrations, platform migration |
Recommended Next Steps
- Get certified — Pursue the GitHub Actions certification to validate your skills
- Contribute to Actions — Build and publish your own action to the Marketplace
- Explore GitHub Advanced Security — CodeQL, secret scanning, and Dependabot integrate deeply with Actions
- Implement GitOps — Use Actions to drive infrastructure-as-code deployments with ArgoCD or Flux
- Scale with GitHub Enterprise — Larger organizations benefit from runner groups, audit logs, and GHES