GitHub Actions Cheatsheet

Workflow Syntax

Use this GitHub Actions reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Top-Level Keys

KeyRequiredDescription
namenoDisplay name in the Actions UI
onyesEvent(s) that trigger the workflow
envnoWorkflow-level environment variables
defaultsnoDefault run shell and working directory
concurrencynoCancel or queue duplicate runs
permissionsnoGITHUB_TOKEN permission overrides
jobsyesMap of job IDs to job definitions

on — Trigger Syntax

# Single event
on: push

# Multiple events
on: [push, pull_request]

# Event with filters
on:
  push:
    branches: [main, 'release/**']
    branches-ignore: ['dependabot/**']
    tags: ['v*']
    paths: ['src/**', '!src/**/*.test.ts']
  pull_request:
    types: [opened, synchronize, reopened]
  schedule:
    - cron: '0 6 * * 1-5'
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: staging
        type: choice
        options: [staging, production]

env — Environment Variables

# Workflow level
env:
  NODE_ENV: test
  API_URL: https://api.example.com

jobs:
  build:
    # Job level (merges with workflow env)
    env:
      LOG_LEVEL: debug
    steps:
      - run: echo "NODE_ENV=$NODE_ENV"
        # Step level (highest precedence)
        env:
          NODE_ENV: production

defaults

defaults:
  run:
    shell: bash
    working-directory: ./app

Per-job override:

jobs:
  build:
    defaults:
      run:
        working-directory: ./packages/core

concurrency

# Cancel in-progress runs for the same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Queue instead of cancel (omit cancel-in-progress or set false)
concurrency:
  group: deploy-production
  cancel-in-progress: false

jobs — Job Keys

KeyRequiredDescription
runs-onyesRunner label(s)
stepsyes*Ordered list of steps (*or uses for reusable workflows)
needsnoJob dependencies (run after these job IDs)
ifnoConditional expression
envnoJob-level env vars
outputsnoValues passed to dependent jobs
strategynoMatrix / fail-fast config
timeout-minutesnoJob timeout (default 360)
continue-on-errornotrue = don't fail workflow if job fails
servicesnoDocker service containers
containernoRun all steps inside a container
permissionsnoPer-job GITHUB_TOKEN scopes
environmentnoDeployment environment (gates, URLs)
concurrencynoJob-level concurrency group

steps — Step Keys

KeyDescription
nameDisplay label
idReference in steps.<id>.outputs
usesAction reference (owner/repo@ref)
runShell command(s)
withInput parameters for uses
envStep-level env vars
ifConditional
continue-on-errorAllow step failure without failing job
timeout-minutesStep timeout
working-directoryOverride working dir for run
shellOverride shell for run

Expressions

# Syntax: ${{ <expression> }}
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}

# Functions
${{ contains(github.event.head_commit.message, '[skip ci]') }}
${{ startsWith(github.ref, 'refs/tags/v') }}
${{ toJSON(matrix) }}
${{ fromJSON(steps.meta.outputs.json).version }}
${{ format('Hello {0}', github.actor) }}
${{ join(matrix.os, ', ') }}

# Status functions (use in if: without ${{ }})
if: success()
if: failure()
if: always()
if: cancelled()
if: failure() && steps.build.conclusion == 'failure'

Outputs

jobs:
  build:
    outputs:
      version: ${{ steps.get-version.outputs.version }}
    steps:
      - id: get-version
        run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT

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

Multi-line run

- run: |
    echo "Line 1"
    echo "Line 2"
    npm ci
    npm test

Setting Output / Env from a Step

# Set output
echo "my-key=my-value" >> $GITHUB_OUTPUT

# Set env var for subsequent steps
echo "MY_VAR=hello" >> $GITHUB_ENV

# Add to PATH
echo "/custom/bin" >> $GITHUB_PATH

# Step summary (rendered Markdown in the UI)
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "| Key | Value |" >> $GITHUB_STEP_SUMMARY

services — Docker Sidecars

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - run: psql -h localhost -U postgres -c '\l'
        env:
          PGPASSWORD: postgres