Home/Learn/CI/CD & GitHub Actions/GitHub Actions — Workflows, Jobs & Steps

GitHub Actions — Workflows, Jobs & Steps

Beginner
GitHub Actions

GitHub Actions workflows consist of jobs that run on runners. Jobs contain steps — shell commands or reusable actions. Understanding job dependencies, matrix builds, and artifact passing is essential for effective pipelines.

Overview

A GitHub Actions workflow is a YAML file defining one or more jobs. Jobs run in parallel by default but can be chained with needs. Each job runs on a fresh runner VM (ubuntu-latest, windows-latest, macos-latest) or a self-hosted runner. Steps within a job share the same filesystem and run sequentially. The three most important concepts for real pipelines: contexts and expressions (accessing dynamic values like branch name, commit SHA), matrix builds (test across multiple versions in parallel), and artifacts (passing build outputs between jobs).

Workflow Structure Deep Dive

Understanding the full workflow structure — contexts, expressions, and environment variables — lets you write dynamic, reusable pipelines.

Workflow structure — contexts and conditions
# .github/workflows/build.yml

name: Build and Test



on:

  push:

    branches: [main]

    paths:                         # only trigger if these paths change

      - 'src/**'

      - 'package*.json'

      - '.github/workflows/**'

  workflow_dispatch:               # manual trigger

    inputs:

      environment:

        description: 'Target environment'

        required: true

        default: 'staging'

        type: choice

        options: [staging, production]



env:                               # workflow-level env vars (all jobs)

  NODE_VERSION: '20'

  REGISTRY: ghcr.io



jobs:

  build:

    runs-on: ubuntu-latest

    timeout-minutes: 15            # fail job if it takes > 15 min



    # Permissions for this job's GITHUB_TOKEN

    permissions:

      contents: read

      packages: write              # needed to push to GitHub Container Registry



    # Outputs: pass values to downstream jobs

    outputs:

      image-tag: ${{ steps.meta.outputs.tags }}

      git-sha: ${{ github.sha }}



    steps:

      - uses: actions/checkout@v4



      # Contexts: github, env, secrets, inputs, steps, runner

      - name: Print context info

        run: |

          echo "Branch: ${{ github.ref_name }}"

          echo "SHA: ${{ github.sha }}"

          echo "Actor: ${{ github.actor }}"

          echo "Event: ${{ github.event_name }}"

          echo "Repo: ${{ github.repository }}"



      # Conditional steps

      - name: Deploy to production

        if: github.ref == 'refs/heads/main' && github.event_name == 'push'

        run: echo "Deploying to production..."

Matrix Builds & Job Dependencies

Matrix strategy runs a job multiple times with different configurations — perfect for testing across Node versions, OS, or environment combinations. needs creates job dependency chains.

Matrix builds and job dependency chains
jobs:

  # Matrix build: runs 6 parallel jobs (3 node versions x 2 OS)

  test:

    strategy:

      matrix:

        node: [18, 20, 21]

        os: [ubuntu-latest, windows-latest]

        exclude:

          - node: 21

            os: windows-latest    # exclude this combination

      fail-fast: false            # don't cancel other matrix jobs on first failure

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

    steps:

      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4

        with:

          node-version: ${{ matrix.node }}

      - run: npm ci && npm test



  # Job dependency: 'deploy' only runs if 'test' succeeds

  build:

    needs: test                   # waits for all matrix test jobs to pass

    runs-on: ubuntu-latest

    steps:

      - run: echo "All tests passed, building image..."



  deploy-staging:

    needs: build

    runs-on: ubuntu-latest

    environment: staging           # requires environment approval in GitHub settings

    steps:

      - run: echo "Deploy to staging..."



  deploy-production:

    needs: deploy-staging

    runs-on: ubuntu-latest

    environment: production        # separate approval gate for production

    if: github.ref == 'refs/heads/main'

    steps:

      - run: echo "Deploy to production..."



# Pipeline graph:

# test (x6 matrix) -> build -> deploy-staging -> deploy-production

Artifacts & Caching

Artifacts pass files between jobs (test reports, build outputs). Caching saves and restores directories between runs (node_modules, Maven cache, pip packages) — dramatically speeds up pipelines.

Artifacts and dependency caching
jobs:

  build:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v4



      # Cache node_modules between runs

      - name: Cache dependencies

        uses: actions/cache@v4

        id: cache-deps

        with:

          path: node_modules

          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

          restore-keys: |

            ${{ runner.os }}-node-     # fallback: any node cache for this OS



      - name: Install (if cache miss)

        if: steps.cache-deps.outputs.cache-hit != 'true'

        run: npm ci



      - name: Build

        run: npm run build



      # Upload artifact (pass to another job or download later)

      - name: Upload build artifact

        uses: actions/upload-artifact@v4

        with:

          name: dist-${{ github.sha }}

          path: dist/

          retention-days: 5



  deploy:

    needs: build

    runs-on: ubuntu-latest

    steps:

      # Download artifact from build job

      - name: Download build artifact

        uses: actions/download-artifact@v4

        with:

          name: dist-${{ github.sha }}

          path: dist/



      - run: ls -la dist/         # verify artifact was downloaded

      - run: ./deploy.sh dist/    # deploy the built artifact



# Cache hit rates: node_modules cache saves ~2-3 minutes per run

# Cache key uses package-lock.json hash -> invalidated only when deps change

Key Points to Remember

  • 1Jobs run in parallel by default; use needs: to create sequential dependencies.
  • 2Matrix strategy multiplies a job across combinations — test NxM configurations in parallel.
  • 3Artifacts pass build outputs between jobs; they are not available between workflow runs.
  • 4Cache persists directories between runs — key should include a hash of the lockfile.
  • 5Environment protection rules in GitHub settings create manual approval gates for deployment jobs.
  • 6timeout-minutes prevents runaway jobs from consuming runner minutes forever.

Interview Questions

Sign in to ask Aria
1

How do you pass data between jobs in GitHub Actions?

2

What is a matrix build and when do you use it?

Ask Aria about GitHub Actions — Workflows, Jobs & Steps

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…