Skip to content
Home
GitHub Actions: Custom Workflows and CI/CD Automation

GitHub Actions: Custom Workflows and CI/CD Automation

CI/CD CI/CD 8 min read 1517 words Beginner ExcellentWiki Editorial Team

GitHub Actions is the most widely adopted cloud-native CI/CD platform. Since its launch in 2019, it has become the default choice for GitHub-hosted repositories, powering over 10 million workflows per month across public and private projects. Its tight integration with the GitHub ecosystem — pull request checks, issue automation, package publishing, and deployment environments — makes it a natural fit for teams already using GitHub for version control.

Workflow Syntax and Structure

GitHub Actions workflows are YAML files stored in .github/workflows/ at the repository root. Each workflow file defines one or more jobs that run in response to GitHub events — push, pull request, issue creation, release publication, or scheduled intervals.

Core Components

Events trigger the workflow. The on keyword specifies the triggering events:

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * *'

Jobs run on runners and contain steps. Jobs can depend on other jobs via the needs keyword, creating a directed acyclic graph. Jobs in the same workflow run in parallel by default.

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run lint

Steps are the individual operations within a job. Steps can run shell commands or use actions — reusable units of automation published in the GitHub Marketplace. Actions are the building blocks of GitHub workflows.

Key Event Filters

Event filtering controls exactly when workflows run. Path filters prevent unnecessary execution:

on:
  push:
    paths:
      - 'src/**'
      - 'tests/**'
      - 'Dockerfile'
      - '.github/workflows/**'

This workflow only triggers on pushes that modify source code, tests, the Dockerfile, or other workflow files. Documentation changes, README edits, and non-code changes are ignored, saving runner minutes and reducing noise.

Branch filters and tag filters provide additional control:

on:
  push:
    branches:
      - main
      - 'release/*'
    tags:
      - 'v*'

Reusable Workflows

Reusable workflows are GitHub Actions’ mechanism for sharing workflow definitions across repositories. A reusable workflow is called as a job from another workflow, passing inputs and secrets.

Defining a Reusable Workflow

A reusable workflow is a standard workflow file with workflow_call as a trigger. It defines inputs and secrets that the caller provides:

# .github/workflows/deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      CLOUD_TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - run: echo "Deploying to ${{ inputs.environment }}"

Calling a Reusable Workflow

# .github/workflows/ci.yml
jobs:
  deploy-staging:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging
    secrets:
      CLOUD_TOKEN: ${{ secrets.STAGING_CLOUD_TOKEN }}

  deploy-production:
    needs: [deploy-staging]
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets:
      CLOUD_TOKEN: ${{ secrets.PROD_CLOUD_TOKEN }}

Reusable workflows support monorepo patterns by enabling shared pipeline templates across microservices. Teams define the standard deploy pipeline once and each service calls it with its specific inputs.

Matrix Builds

Matrix builds execute a job across multiple combinations of variables. The strategy.matrix keyword generates a matrix of operating systems, language versions, or environment configurations:

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

This configuration generates 9 jobs (3 operating systems × 3 Node versions), all running in parallel. Matrix builds are the standard approach for cross-platform testing and version compatibility validation.

Including and Excluding Matrix Entries

The include and exclude keywords fine-tune matrix combinations:

strategy:
  matrix:
    os: [ubuntu-latest, macos-latest]
    node: [18, 20]
    include:
      - os: ubuntu-latest
        node: 22
    exclude:
      - os: macos-latest
        node: 18

This generates 4 jobs: ubuntu-18, ubuntu-20, ubuntu-22 (via include), and macos-20.

Environment Secrets and Deployment Environments

GitHub Environments are security boundaries for deployments. An environment groups secrets, protection rules, and deployment history:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - run: deploy-script.sh

Environment-specific secrets are only available to jobs targeting that environment. Production credentials are never exposed to staging or development workflows.

Required reviewers for environments prevent deployments without approval:

environment:
  name: production
  url: https://example.com

When required reviewers are configured, the workflow pauses until an authorized user approves the deployment. This implements the manual approval gate common in continuous delivery practices.

Deployment protection rules can also include a wait timer, allowing automated rollback if verification tests fail within the wait window.

Self-Hosted Runners

Self-hosted runners execute workflows on infrastructure you control. Use cases include:

  • Access to internal networks or on-premises services
  • Specialized hardware (GPU instances for ML builds)
  • Compliance requirements prohibiting third-party code execution
  • Cost optimization at high build volume

Configuring Self-Hosted Runners

Runners are installed on Linux, macOS, or Windows machines and registered with a GitHub repository, organization, or enterprise:

# Download the runner package
curl -o actions-runner-linux-x64.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.316.0/actions-runner-linux-x64-2.316.0.tar.gz

# Extract and configure
tar xzf actions-runner-linux-x64.tar.gz
./config.sh --url https://github.com/org/repo --token AAAA...
./run.sh

Runners can be labeled for targeting:

jobs:
  gpu-tests:
    runs-on: [self-hosted, gpu]
    steps:
      - run: nvidia-smi

Runner Autoscaling

For production self-hosted setups, autoscaling is necessary. The actions/actions-runner-controller (ARC) for Kubernetes manages runner pods that scale based on workflow demand. ARC creates runner pods on demand and destroys them after jobs complete, optimizing cost without sacrificing availability.

Workflow Commands and Logging

GitHub Actions provides workflow commands that control the runner environment and produce structured output. The echo "::group::Title" and echo "::endgroup::" commands collapse log sections in the workflow run output, making logs easier to navigate. The echo "::notice::message", ::warning::, and ::error:: commands annotate the workflow run with visible markers. The echo "::set-output name=key::value" command sets step outputs consumed by subsequent steps:

steps:
  - id: extract
    run: echo "::set-output name=version::$(node -p 'require(\"./package.json\").version')"
  - run: echo "Version is ${{ steps.extract.outputs.version }}"

These commands are essential for creating self-documenting, debuggable workflows that provide clear feedback when they fail.

Workflow Dispatch and Manual Triggers

The workflow_dispatch event enables manual pipeline triggering through the GitHub UI or API. Combined with input parameters, it provides flexibility for ad-hoc operations:

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
      debug:
        description: 'Enable debug mode'
        required: false
        default: false
        type: boolean

Manual triggers are useful for production rollbacks, emergency deployments, and one-off infrastructure tasks that should not run automatically.

Caching and Performance

Dependency caching is the most impactful performance optimization. The actions/cache action stores and retrieves cached directories:

steps:
  - uses: actions/cache@v4
    with:
      path: ~/.npm
      key: npm-cache-${{ hashFiles('package-lock.json') }}
      restore-keys: |
        npm-cache-
  - run: npm ci

Cache keys based on lock file checksums ensure cache invalidation when dependencies change. Fallback restore keys (npm-cache-) retrieve the most recent cache even if the exact key does not match, avoiding a full install on unrelated cache misses.

Build Speed Optimization

Beyond caching, optimize build time through:

  • Parallel job execution — independent jobs run concurrently by default
  • Service containers — database and cache services defined in the workflow avoid setup overhead per run
  • Composite actions — encapsulate repeated step sequences into reusable actions
  • Conditional execution — skip unnecessary steps using if conditions

Debugging and Monitoring

GitHub Actions provides several tools for debugging workflow failures:

Debug logging is enabled by setting secrets ACTIONS_STEP_DEBUG to true. This enables diagnostic runner logs for all steps in the workflow.

Job summary markdown enriches workflow run output with structured information — test results, coverage reports, deployment links.

Workflow telemetry tracks run duration, queue time, and concurrency usage in the GitHub Actions dashboard. Self-hosted runner metrics are available through GitHub’s runner API.

Frequently Asked Questions

What is the difference between a composite action and a reusable workflow?

Composite actions package steps into a single action that runs within the caller’s job. Reusable workflows create separate jobs with their own runner allocations. Composite actions are cleaner for step-level reuse; reusable workflows provide job-level isolation and independent environments. Use composite actions for shared build steps, reusable workflows for shared deployment pipelines.

How do I manage GitHub Actions costs?

GitHub Actions consumes included minutes based on the plan: free accounts get 2,000 minutes/month for private repos (unlimited for public). Paid plans include more minutes. Costs increase with:

  • Matrix builds (many parallel jobs multiply usage)
  • Large runner types (larger VMs cost more per minute)
  • Self-hosted runner maintenance Monitor usage through the Billing & Settings page and set spending limits.

Can I use GitHub Actions with GitLab or Bitbucket?

No. GitHub Actions only works with GitHub repositories. The tight integration — pull request checks, status reporting, environments — is exclusive to the GitHub ecosystem. Multi-platform teams must maintain separate CI configurations.

Recommended Internal Links

Conclusion

GitHub Actions provides a complete CI/CD platform deeply integrated with the GitHub ecosystem. Its YAML-based workflow syntax, reusable workflow pattern, matrix builds, environment secrets, and self-hosted runner support handle everything from simple test automation to complex multi-environment deployment pipelines. For GitHub-centric teams, Actions is the natural choice for continuous integration and deployment.

For a comprehensive overview, read our article on Artifact Management.

For a comprehensive overview, read our article on Ci Cd Best Practices.

Section: CI/CD 1517 words 8 min read Beginner 756 articles in section Report inaccuracy Back to top