GitHub Actions Complete Guide 2026 — CI/CD Pipelines, Workflows, and Automation

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

Why This Matters

GitHub Actions is the most widely used CI/CD platform in 2026 — free for public repos, deeply integrated with GitHub, and powerful enough to replace standalone tools like Jenkins or CircleCI. Every push, PR, or schedule can trigger automated testing, building, and deployment. Teams that automate their pipeline ship faster with fewer bugs reaching production.

Core Concepts

A GitHub Actions workflow is a YAML file stored in .github/workflows/. Key terms:

TermDefinition
WorkflowThe YAML file defining the automation
EventWhat triggers the workflow (push, PR, schedule)
JobA set of steps on one runner
StepIndividual task — run a command or use an action
RunnerThe VM where jobs execute
ActionReusable step from the Marketplace

Full CI/CD Pipeline for Next.js

# .github/workflows/ci.yml
name: CI/CD Pipeline
 
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
 
env:
  NODE_VERSION: '20'
 
jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - name: Run tests with coverage
        run: npm run test:coverage
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
 
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

Docker Build and Push to GitHub Container Registry

# .github/workflows/docker.yml
name: Build and Push Docker Image
 
on:
  push:
    branches: [main]
    tags: ['v*']
 
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha,prefix=sha-
 
      - uses: docker/setup-buildx-action@v3
 
      - name: Login to GHCR
        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: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

Matrix Builds and Caching

Test across multiple Node.js versions and operating systems in parallel:

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        node: [18, 20, 22]
        os: [ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci && npm test

Speed up builds with dependency caching:

- name: Cache npm dependencies
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: npm-${{ runner.os }}-
 
- name: Cache Next.js build
  uses: actions/cache@v4
  with:
    path: .next/cache
    key: nextjs-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}

Reusable Workflows and Secrets

Define once, call from many workflows:

# .github/workflows/reusable-deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      DEPLOY_TOKEN:
        required: true
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - name: Deploy to ${{ inputs.environment }}
        run: echo "Deploying..."
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Call the reusable workflow from another file:

jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
    secrets:
      DEPLOY_TOKEN: ${{ secrets.STAGING_TOKEN }}

Common Mistakes

  • Storing secrets in workflow YAML — always use ${{ secrets.NAME }}, never hardcode values
  • Not pinning action versions — use @v4 or a specific commit SHA to avoid supply-chain attacks
  • Missing needs between jobs — without it, jobs run in parallel and may deploy before tests pass
  • No caching — npm install on every run wastes 60-90 seconds per job
  • Running expensive jobs on every PR — use if: conditions to limit deploy jobs to the main branch only

Best Practices

  • Use concurrency groups to cancel in-progress runs when a new push arrives
  • Store all sensitive values in GitHub Secrets or Environments, never in env files committed to the repo
  • Use workflow_dispatch to allow manual re-runs without a new commit
  • Set timeout-minutes on every job to prevent runaway builds from consuming free minutes
  • Use permissions: at the workflow level to follow least-privilege principle
# Concurrency example — cancel old runs on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Key Takeaways

  • GitHub Actions workflows live in .github/workflows/ as YAML files and trigger on events like push, pull_request, or schedule
  • Jobs within a workflow run in parallel by default; use needs: to enforce sequential execution
  • Use matrix strategy to test across multiple Node.js versions and OS combinations in one workflow definition
  • Action caching with actions/cache@v4 can cut build times by 60-90% by reusing node_modules and build artifacts
  • Reusable workflows (workflow_call) let you define deploy logic once and call it from multiple pipelines
  • Always scope permissions: to the minimum required — default is read-all for security
  • Use concurrency groups to automatically cancel stale workflow runs when new commits are pushed
  • GitHub Environments add required reviewers and secret scoping for production deployments

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading