GitHub Actions — Complete CI/CD Guide 2026
Advertisement
Introduction
Why This Matters
GitHub Actions is the most widely adopted CI/CD platform in 2026 — tightly integrated with GitHub repositories, free for public repos, and offering a massive marketplace of reusable actions. Unlike Jenkins, there is no server to maintain. Unlike GitLab CI, there is no separate platform to learn if you are already on GitHub.
Understanding GitHub Actions deeply lets you automate not just builds and tests, but deployments, code reviews, dependency updates, security scans, and release management — all from YAML files committed directly to your repository.
Core Concepts
| Concept | Description |
|---|---|
| Workflow | A YAML file in .github/workflows/ that defines automation |
| Event | A trigger that starts a workflow (push, pull_request, schedule) |
| Job | A group of steps that run on the same runner |
| Step | A single task — either a shell command or an action |
| Action | A reusable unit of automation from the Marketplace or your repo |
| Runner | The virtual machine (GitHub-hosted or self-hosted) that executes jobs |
Your First Workflow
Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
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 linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run buildTriggers and Events
on:
# Trigger on push to specific branches
push:
branches: [main]
paths:
- 'src/**'
- 'package*.json'
# Trigger on pull requests
pull_request:
types: [opened, synchronize, reopened]
# Scheduled trigger (cron syntax)
schedule:
- cron: '0 2 * * 1' # Every Monday at 2am UTC
# Manual trigger with inputs
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
deploy_version:
description: 'Version tag to deploy'
required: true
type: string
# Trigger from another workflow
workflow_call:
inputs:
environment:
required: true
type: stringMatrix Builds for Cross-Platform Testing
Matrix builds run jobs in parallel across multiple configurations:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: ['18', '20', '22']
exclude:
- os: windows-latest
node: '18'
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm testCaching Dependencies
steps:
- uses: actions/checkout@v4
- name: Cache node_modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ciFor Docker layer caching:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/myorg/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxComplete CI/CD Pipeline With Environments
name: Deploy Pipeline
on:
push:
branches: [main]
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 && npm test
build-image:
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,prefix=
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build-image
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: |
helm upgrade --install myapp ./charts/myapp \
--set image.tag=${{ github.sha }} \
--namespace staging \
--atomic
env:
KUBECONFIG: ${{ secrets.STAGING_KUBECONFIG }}
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.example.com
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
helm upgrade --install myapp ./charts/myapp \
--set image.tag=${{ github.sha }} \
--namespace production \
--atomic
env:
KUBECONFIG: ${{ secrets.PRODUCTION_KUBECONFIG }}Reusable Workflows
Extract common logic into reusable workflows:
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
image-tag:
required: true
type: string
secrets:
kubeconfig:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Deploy
run: |
helm upgrade --install myapp ./charts/myapp \
--set image.tag=${{ inputs.image-tag }} \
--namespace ${{ inputs.environment }} \
--atomic
env:
KUBECONFIG: ${{ secrets.kubeconfig }}Call it from any workflow:
jobs:
deploy-prod:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
image-tag: ${{ github.sha }}
secrets:
kubeconfig: ${{ secrets.PRODUCTION_KUBECONFIG }}Common Mistakes
- Checking in secrets — never hardcode credentials in workflow files; use
secrets.*context values. - Not pinning action versions — use
actions/checkout@v4notactions/checkout@main; unpinned actions are a supply chain risk. - Skipping
needsdependencies — withoutneeds, jobs run in parallel by default; use it to enforce sequential execution. - Not caching dependencies — every run reinstalling
node_moduleswastes 1-3 minutes; useactions/cacheor the built-in cache insetup-node. - Overusing
workflow_dispatchfor production deploys — useenvironmentprotection rules with required reviewers instead.
Best Practices
- Use environment protection rules — configure required reviewers, deployment branches, and wait timers in GitHub repository Settings for production environments.
- Pin actions to a commit SHA — for maximum security, pin third-party actions to their commit SHA:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683. - Use job summaries — add
$GITHUB_STEP_SUMMARYoutput to create rich markdown reports visible in the Actions UI. - Self-host runners for private infra — deploy self-hosted runners in your VPC for jobs that need direct access to internal services without public network exposure.
- Use
concurrencyto cancel stale runs — addconcurrency: group: ${{ github.ref }}to cancel in-progress runs when a new push occurs.
Key Takeaways
- GitHub Actions workflows live in
.github/workflows/as YAML files and are triggered by repository events likepush,pull_request, orschedule. - Jobs run in parallel by default; use
needs: [job-name]to enforce sequential execution and pass outputs between jobs. - Matrix builds run a job across multiple OS/version combinations in parallel — ideal for cross-platform testing.
- The
environmentkeyword enables deployment protection rules with required reviewers, branch filters, and deployment URLs. - Reusable workflows (
workflow_call) let you centralize CI/CD logic across multiple repositories without copy-pasting YAML. - Always pin third-party actions to a specific version tag or commit SHA to prevent supply chain attacks.
- Use
actions/cacheor built-in caching in setup actions to cache dependencies and reduce workflow run time by 50-80%. - The
GITHUB_TOKENsecret is automatically provided in every workflow and can authenticate to GHCR, the GitHub API, and other GitHub services without additional configuration.
Advertisement