Home/Blog/GitHub Actions CI/CD: Complete Guide to Workflows, Runners, and Automation
Software Engineering

GitHub Actions CI/CD: Complete Guide to Workflows, Runners, and Automation

Master GitHub Actions for CI/CD with workflows, matrix builds, caching, secrets, artifacts, reusable workflows, and deployment patterns for AWS, Azure, GCP, and Vercel.

By Inventive HQ Team
GitHub Actions CI/CD: Complete Guide to Workflows, Runners, and Automation

GitHub Actions transforms your repository into a powerful CI/CD platform. Build, test, and deploy code directly from GitHub without managing external services. This guide covers everything from basic workflows to advanced deployment strategies.

GitHub Actions Fundamentals

┌─────────────────────────────────────────────────────────────────────────┐
│                    GitHub Actions Architecture                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  TRIGGER                    WORKFLOW                    RESULT           │
│  ───────────────────────────────────────────────────────────────────    │
│                                                                          │
│  ┌──────────┐              ┌──────────────────────┐    ┌──────────┐     │
│  │ Events   │──────────────│ .github/workflows/   │────│ Artifacts│     │
│  │ • push   │              │                      │    │ Logs     │     │
│  │ • PR     │              │ ┌────────────────┐   │    │ Status   │     │
│  │ • cron   │              │ │    Job 1       │   │    └──────────┘     │
│  │ • manual │              │ │ ┌────────────┐ │   │                     │
│  └──────────┘              │ │ │ Step 1     │ │   │    ┌──────────┐     │
│                            │ │ │ Step 2     │ │   │────│ Deploy   │     │
│                            │ │ │ Step 3     │ │   │    └──────────┘     │
│                            │ │ └────────────┘ │   │                     │
│                            │ └────────────────┘   │                     │
│                            │                      │                     │
│                            │ ┌────────────────┐   │                     │
│                            │ │    Job 2       │   │                     │
│                            │ └────────────────┘   │                     │
│                            └──────────────────────┘                     │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Key Concepts

ConceptDescription
WorkflowAutomated process defined in YAML in .github/workflows/
EventTrigger that starts a workflow (push, PR, schedule)
JobSet of steps running on same runner, jobs run in parallel by default
StepIndividual task within a job (action or shell command)
ActionReusable unit from Marketplace or custom
RunnerServer executing workflows (GitHub-hosted or self-hosted)
ArtifactFiles produced by workflow, persist after completion

Basic CI Workflow

Start with a simple workflow that runs on every push and pull request:

# .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: Setup 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 build

Workflow Anatomy

name: CI                           # Workflow name (shown in Actions tab)

on:                                # Triggers
  push:
    branches: [main]               # Only main branch
    paths:                         # Only when these files change
      - 'src/**'
      - 'package.json'
  pull_request:
    types: [opened, synchronize]   # Specific PR events

jobs:
  job-name:                        # Unique job identifier
    runs-on: ubuntu-latest         # Runner type
    timeout-minutes: 10            # Job timeout

    steps:
      - uses: actions/checkout@v4  # Use an action
        with:                      # Action inputs
          fetch-depth: 0

      - name: Run command          # Step name
        run: echo "Hello"          # Shell command
        env:                       # Environment variables
          MY_VAR: value

Matrix Builds

Test across multiple versions, operating systems, or configurations:

name: Matrix CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false             # Don't cancel other jobs if one fails
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        exclude:                   # Skip specific combinations
          - os: windows-latest
            node-version: 18
        include:                   # Add specific combinations
          - os: ubuntu-latest
            node-version: 20
            coverage: true

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - run: npm ci
      - run: npm test

      - name: Upload coverage
        if: matrix.coverage
        uses: codecov/codecov-action@v4

Caching Dependencies

Speed up workflows by caching downloaded dependencies:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Option 1: Built-in cache in setup actions
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'              # Automatic npm caching

      # Option 2: Manual cache control
      - name: Cache node modules
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      - run: npm ci

Cache Strategies by Language

LanguageCache PathKey Pattern
Node.js~/.npm or node_moduleshashFiles('package-lock.json')
Python~/.cache/piphashFiles('requirements.txt')
Go~/go/pkg/modhashFiles('go.sum')
Rust~/.cargo, targethashFiles('Cargo.lock')
Java~/.m2/repositoryhashFiles('pom.xml')

Working with Secrets

Store sensitive data securely and access in workflows:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Access secrets
      - name: Deploy to production
        env:
          API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          ./deploy.sh

      # GITHUB_TOKEN is automatic
      - name: Create release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: v1.0.0

Secret Best Practices

  1. Never echo secrets - They're masked, but don't risk it
  2. Use OIDC for cloud providers - Avoid long-lived credentials
  3. Scope appropriately - Use environment secrets for production
  4. Rotate regularly - Update secrets periodically
  5. Audit access - Review who can access secrets

Artifacts and Outputs

Share data between jobs and preserve build outputs:

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}

    steps:
      - uses: actions/checkout@v4

      - name: Get version
        id: version
        run: echo "version=$(cat package.json | jq -r .version)" >> $GITHUB_OUTPUT

      - name: Build
        run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-files
          path: dist/
          retention-days: 5

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: build-files
          path: dist/

      - name: Deploy version ${{ needs.build.outputs.version }}
        run: ./deploy.sh dist/

Deployment Workflows

Deploy to Cloud Providers

# Deploy to AWS
jobs:
  deploy-aws:
    runs-on: ubuntu-latest
    permissions:
      id-token: write      # Required for OIDC
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          aws-region: us-east-1

      - name: Deploy to S3
        run: aws s3 sync ./dist s3://my-bucket/

# Deploy to Azure
jobs:
  deploy-azure:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to Azure
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy to Azure Web App
        uses: azure/webapps-deploy@v2
        with:
          app-name: my-app
          package: ./dist

# Deploy to Vercel
jobs:
  deploy-vercel:
    runs-on: ubuntu-latest
    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'

Environment Protection

Use environments for production deployments with approvals:

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging            # No protection needed

    steps:
      - run: ./deploy.sh staging

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com        # Shows link in GitHub UI

    steps:
      - run: ./deploy.sh production

Configure environment protection in Settings > Environments:

  • Required reviewers
  • Wait timer
  • Branch restrictions
  • Deployment branches

Reusable Workflows

Create workflows that can be called from other workflows:

# .github/workflows/reusable-build.yml
name: Reusable Build

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '20'
      environment:
        required: true
        type: string
    secrets:
      npm-token:
        required: false
    outputs:
      artifact-name:
        description: "Name of uploaded artifact"
        value: ${{ jobs.build.outputs.artifact-name }}

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-name: ${{ steps.upload.outputs.artifact-name }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}

      - run: npm ci
        env:
          NPM_TOKEN: ${{ secrets.npm-token }}

      - run: npm run build

      - id: upload
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ inputs.environment }}
          path: dist/

Call reusable workflow:

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '20'
      environment: production
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying ${{ needs.build.outputs.artifact-name }}"

Composite Actions

Create custom actions combining multiple steps:

# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Setup Node.js and install dependencies'

inputs:
  node-version:
    description: 'Node.js version'
    required: false
    default: '20'

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'

    - name: Install dependencies
      shell: bash
      run: npm ci

    - name: Verify installation
      shell: bash
      run: npm --version && node --version

Use composite action:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup project
        uses: ./.github/actions/setup-project
        with:
          node-version: '20'

      - run: npm test

Advanced Patterns

Conditional Jobs

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      frontend: ${{ steps.filter.outputs.frontend }}
      backend: ${{ steps.filter.outputs.backend }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            frontend:
              - 'frontend/**'
            backend:
              - 'backend/**'

  build-frontend:
    needs: changes
    if: needs.changes.outputs.frontend == 'true'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building frontend"

  build-backend:
    needs: changes
    if: needs.changes.outputs.backend == 'true'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building backend"

Scheduled Workflows

name: Nightly Build

on:
  schedule:
    - cron: '0 2 * * *'            # Run at 2 AM UTC daily
  workflow_dispatch:                # Allow manual trigger

jobs:
  nightly:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build:full
      - run: npm run test:e2e

Concurrency Control

name: Deploy

on:
  push:
    branches: [main]

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true         # Cancel previous runs

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Common Workflow Patterns

Pull Request Checks

name: PR Checks

on:
  pull_request:
    types: [opened, synchronize, reopened]

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

  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 -- --coverage
      - uses: codecov/codecov-action@v4

  build:
    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

Release Workflow

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          files: dist/*
          generate_release_notes: true

Debugging Workflows

Enable Debug Logging

Set repository secrets:

  • ACTIONS_RUNNER_DEBUG: true
  • ACTIONS_STEP_DEBUG: true

Interactive Debugging

- name: Setup tmate session
  if: failure()
  uses: mxschmitt/action-tmate@v3
  timeout-minutes: 15

Local Testing with act

# Install act
brew install act

# Run workflow locally
act push

# Run specific job
act -j build

# Use specific event
act pull_request

# Pass secrets
act -s GITHUB_TOKEN=xxx

Conclusion

GitHub Actions provides powerful CI/CD capabilities directly in your repository. Start with basic workflows for testing and linting, then expand to deployment pipelines, matrix builds, and reusable workflows. Key practices:

  1. Cache dependencies for faster builds
  2. Use OIDC for cloud authentication
  3. Protect environments for production
  4. Create reusable workflows for consistency
  5. Enable debug logging when troubleshooting

For security best practices, see our GitHub Actions Security Guide.

Need Expert IT & Security Guidance?

Our team is ready to help protect and optimize your business technology infrastructure.