Skip to content

GitHub Actions Workflows

4 min read

GitHub Actions Overview

GitHub Actions is GitHub’s built-in CI/CD platform. It defines workflows through YAML files that automatically execute builds, tests, and deployments when triggered by code pushes, PR creation, and other events. It integrates deeply with GitHub repositories and works out of the box without additional configuration.

graph LR
    Event[Trigger Event<br/>push/PR/schedule] --> WF[Workflow]
    WF --> J1[Job 1: Build]
    WF --> J2[Job 2: Test]
    WF --> J3[Job 3: Deploy]
    J1 -->|Dependency| J2
    J2 -->|Dependency| J3
    J1 --> S1[Step: Checkout Code]
    J1 --> S2[Step: Install Dependencies]
    J1 --> S3[Step: Compile Build]

Workflow Syntax

Basic Structure

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Concurrency control: only keep the latest run for the same PR
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  pull-requests: write

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

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

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for version generation

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Unit tests
        run: npm test

      - name: Build application
        run: npm run build

      - name: Generate version
        id: version
        run: echo "value=v$(date +%Y%m%d)-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

  test-e2e:
    needs: 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: npx playwright install --with-deps
      - run: npx playwright test
      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/

  deploy:
    needs: [build, test-e2e]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to production
        run: echo "Deploying ${{ needs.build.outputs.version }}"

Trigger Conditions

on:
  push:
    branches: [main]
    tags: ['v*']           # Tag trigger
    paths:                 # Path filter
      - 'src/**'
      - 'package.json'
  pull_request:
    types: [opened, synchronize]
  schedule:
    - cron: '0 2 * * *'   # Daily at 2 AM
  workflow_dispatch:       # Manual trigger
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'

Common Actions

Action Purpose
actions/checkout@v4 Checkout repository code
actions/setup-node@v4 Install Node.js
actions/setup-python@v5 Install Python
actions/setup-go@v5 Install Go
actions/cache@v4 Cache dependencies
actions/upload-artifact@v4 Upload build artifacts
actions/download-artifact@v4 Download build artifacts

Caching Strategy

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-

Environments and Secrets

Secrets Management

# Configure in repository Settings > Secrets
steps:
  - name: Login to container registry
    uses: docker/login-action@v3
    with:
      registry: ghcr.io
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}  # Automatically provided token

  - name: Use custom secret
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      API_KEY: ${{ secrets.API_KEY }}
    run: npm run migrate

Environment Protection Rules

deploy:
  runs-on: ubuntu-latest
  environment:
    name: production
    url: https://app.example.com
  # Reviewers need to be configured in repository Settings > Environments

Container Build and Push

Build and Push Docker Image

docker-build:
  runs-on: ubuntu-latest
  permissions:
    contents: read
    packages: write
  steps:
    - uses: actions/checkout@v4

    - name: Login to GHCR
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Setup Docker Buildx
      uses: docker/setup-buildx-action@v3

    - 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=

    - 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

Advanced Patterns

Reusable Workflows

Extract common processes 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:
      deploy-key:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - name: Deploy
        run: |
          echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"
          # Actual deployment logic
# Call reusable workflow
jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
      image-tag: ${{ needs.build.outputs.version }}
    secrets:
      deploy-key: ${{ secrets.STAGING_DEPLOY_KEY }}

Matrix Strategy

Test multiple version combinations in parallel:

test:
  runs-on: ${{ matrix.os }}
  strategy:
    fail-fast: false  # One failure doesn't cancel others
    matrix:
      os: [ubuntu-latest, macos-latest]
      node-version: [18, 20, 22]
      exclude:
        - os: macos-latest
          node-version: 18  # Exclude specific combination

  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm test
graph TB
    subgraph "Matrix Execution"
        M1["Ubuntu + Node 18"]
        M2["Ubuntu + Node 20"]
        M3["Ubuntu + Node 22"]
        M4["macOS + Node 20"]
        M5["macOS + Node 22"]
    end
    All[Matrix Strategy] --> M1
    All --> M2
    All --> M3
    All --> M4
    All --> M5

Workflow Execution Flow

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub
    participant Runner as Runner
    participant Reg as Registry

    Dev->>GH: git push
    GH->>GH: Match trigger conditions
    GH->>Runner: Assign Job
    Runner->>Runner: Checkout code
    Runner->>Runner: Install dependencies
    Runner->>Runner: Run tests
    Runner->>Runner: Build image
    Runner->>Reg: Push image
    Runner->>GH: Report results
    GH->>Dev: Notify status

GitHub Actions embeds CI/CD directly into the development workflow, achieving fully automated software delivery from code push to production deployment. Mastering workflow syntax, caching strategies, and advanced patterns significantly improves build efficiency and deployment reliability.

Edit this page

Comments