Skip to content
6 min read·Lesson 3 of 10

GitHub Actions Basics

Write your first GitHub Actions workflow: triggers, jobs, steps, runners, and the marketplace actions that save you time.

GitHub Actions is GitHub's built-in CI/CD system. Workflows are YAML files in your repo, triggered by GitHub events, executed on hosted or self-hosted runners.

The File Layout

.github/
└── workflows/
    ├── ci.yml          # runs on every push and PR
    ├── release.yml     # runs on tag
    └── nightly.yml     # runs on schedule

Each .yml file is one workflow. You can have as many as you like.

A Minimal Workflow

name: CI

on:
  push:
    branches: [main]
  pull_request:

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

That's a complete CI pipeline. Push to GitHub, head to the Actions tab, watch it run.

Anatomy

FieldMeaning
nameDisplay name in the UI
onTriggers — events that start the workflow
jobsOne or more jobs; default they run in parallel
runs-onThe runner OS — ubuntu-latest, windows-latest, macos-latest
stepsSequential commands or actions inside a job
usesReference a marketplace action
runRun a shell command on the runner

Triggers

on:
  push:
    branches: [main, 'release/**']
    paths-ignore: ['docs/**', '*.md']

  pull_request:
    types: [opened, synchronize, reopened]

  schedule:
    - cron: '0 6 * * *'         # 6am UTC daily

  workflow_dispatch:            # manual run from the UI
    inputs:
      environment:
        description: Target environment
        type: choice
        options: [dev, staging, prod]

  release:
    types: [published]

The richest set of CI triggers in the industry. Combine them; one workflow can be triggered many ways.

Multiple Jobs

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

  build:
    needs: [lint, test]                # only runs if lint and test pass
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

needs: creates dependencies between jobs. Without it, jobs run in parallel. upload-artifact / download-artifact share files between jobs.

Matrix Builds

Run the same job across many configurations:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-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

3 OSes × 3 Node versions = 9 parallel jobs. fail-fast: false lets all jobs finish even if one fails — handy for cross-platform debugging.

Caching

Reinstalling dependencies on every run is wasteful. Use the cache action — most setup actions (setup-node, setup-python, setup-go) handle it for you with one option:

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'                       # caches based on package-lock.json

For arbitrary caching:

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

Expressions and Contexts

Use ${{ ... }} to access workflow context:

- name: Print branch
  run: echo "Branch is ${{ github.ref_name }}"

- name: Run only on main
  if: github.ref == 'refs/heads/main'
  run: echo "Deploying"

- name: Always run
  if: always()
  run: echo "Even after failure"

- name: Skip on docs-only PRs
  if: ${{ !contains(github.event.head_commit.message, 'docs:') }}
  run: ./script.sh

Available contexts include github, env, secrets, matrix, steps, jobs, runner.

Environment Variables

env:
  NODE_ENV: production              # workflow-level
jobs:
  build:
    env:
      DEPLOY_BUCKET: my-app-prod    # job-level
    steps:
      - run: env | grep NODE_ENV

Outputs Between Steps and Jobs

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.ver.outputs.value }}
    steps:
      - id: ver
        run: echo "value=$(date +%Y%m%d-%H%M)" >> "$GITHUB_OUTPUT"

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

Marketplace Actions

Don't write what's already written. The Actions Marketplace has thousands of reusable steps:

  • actions/checkout — clone the repo
  • actions/setup-node, setup-python, setup-go, setup-java
  • actions/cache
  • actions/upload-artifact / download-artifact
  • aws-actions/configure-aws-credentials
  • azure/login / google-github-actions/auth
  • docker/build-push-action
  • codecov/codecov-action

Pin third-party actions to a commit SHA, not a floating tag — a compromised tag could exfiltrate secrets:

- uses: someorg/some-action@a1b2c3d4e5f6...   # safer than @v1

Runners

  • GitHub-hosted runners — ubuntu, windows, macOS; pay-per-minute on private repos, free on public
  • Larger runners — 4-, 8-, 16-vCPU, GPU runners for heavy builds
  • Self-hosted runners — your own VMs / Kubernetes (ARC) for special hardware, on-prem networks, large fleets

What's Next

The next lesson covers reusable workflows and composite actions — how to package pipeline logic so you don't copy-paste the same steps into 50 repos.

Key Takeaways

  • Workflows live in .github/workflows/*.yml and are triggered by events.
  • A workflow has jobs; jobs have steps; steps run commands or use actions.
  • Marketplace actions (actions/checkout, actions/setup-node) handle most setup work.
  • Matrix builds run the same job across many configurations in parallel.
  • Use cache action to keep dependency installs fast.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →