GitHub Actions Cheatsheet

Jobs and Steps

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.

Job Definition

jobs:
  build:
    name: 'Build & Test'        # display name (optional)
    runs-on: ubuntu-latest
    timeout-minutes: 30         # default: 360
    continue-on-error: false    # fail workflow if job fails
    if: github.ref == 'refs/heads/main'
    env:
      NODE_ENV: test
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

Job Dependencies — needs

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: npm run build

  test:
    needs: build               # single dependency
    runs-on: ubuntu-latest
    steps:
      - run: npm test

  deploy:
    needs: [build, test]       # multiple — runs after BOTH succeed
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Dependency Graph with Conditionals

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - run: npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - run: npm test

  notify-failure:
    needs: [lint, test]
    if: failure()              # runs even if lint or test failed
    runs-on: ubuntu-latest
    steps:
      - run: ./notify.sh "Build failed"

Job Outputs

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tag }}
      sha-short: ${{ steps.sha.outputs.short }}
    steps:
      - id: sha
        run: echo "short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
      - id: meta
        run: echo "tag=myapp:$(cat VERSION)" >> $GITHUB_OUTPUT

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

Step Types

steps:
  # Run an Action from Marketplace
  - uses: actions/checkout@v4
    with:
      fetch-depth: 0

  # Run inline shell
  - name: Install deps
    run: npm ci

  # Multi-line shell
  - name: Build
    run: |
      npm run build
      npm run lint

  # Custom shell
  - name: Python script
    shell: python
    run: |
      import os
      print(os.environ.get('HOME'))

  # Conditional step
  - name: Deploy
    if: github.ref == 'refs/heads/main' && success()
    run: ./deploy.sh

Step Conditionals

# Status-based
- if: success()
- if: failure()
- if: always()
- if: cancelled()

# Combine with event/context
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'

# Check a previous step's outcome
- id: tests
  run: npm test
  continue-on-error: true
- if: steps.tests.outcome == 'failure'
  run: echo "Tests failed, sending alert"

# Skip for draft PRs
- if: github.event.pull_request.draft == false
  run: ./expensive-check.sh

Step conclusion values: success, failure, skipped, cancelled Step outcome values: success, failure, skipped, cancelled

outcome = result before continue-on-error; conclusion = result after.

Step Outputs

- id: release
  run: |
    VERSION=$(cat package.json | jq -r .version)
    echo "version=$VERSION" >> $GITHUB_OUTPUT
    echo "tag=v$VERSION" >> $GITHUB_OUTPUT

- run: echo "Version is ${{ steps.release.outputs.version }}"

Environment Files

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

# Add directory to PATH
echo "/opt/my-tool/bin" >> $GITHUB_PATH

# Write step summary (Markdown rendered in Actions UI)
cat << 'EOF' >> $GITHUB_STEP_SUMMARY

Test Results

SuiteStatus
UnitPassed
E2EPassed
EOF

Passing Data Between Steps

steps:
  - id: build
    run: |
      echo "artifact=dist/app.tar.gz" >> $GITHUB_OUTPUT

  - run: echo "Built ${{ steps.build.outputs.artifact }}"

  - run: echo "ARTIFACT=${{ steps.build.outputs.artifact }}" >> $GITHUB_ENV

  - run: echo "Path is $ARTIFACT"   # via env var

Container Jobs

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: node:20-alpine
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
      env:
        NODE_ENV: test
      options: --cpus 2
    steps:
      - uses: actions/checkout@v4
      - run: node --version && npm test

if Expressions — Common Patterns

PatternExpression
Only on maingithub.ref == 'refs/heads/main'
Only on tagsstartsWith(github.ref, 'refs/tags/')
On PR to maingithub.event_name == 'pull_request'
Skip forksgithub.event.repository.fork == false
After failurefailure()
Always runalways()
Commit msg containscontains(github.event.head_commit.message, '[deploy]')
Is not a draft PRgithub.event.pull_request.draft == false
Actor is ownergithub.actor == github.repository_owner

Timeout and Error Handling

jobs:
  flaky-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    continue-on-error: true    # workflow succeeds even if job fails
    steps:
      - name: Flaky test
        continue-on-error: true   # step-level; doesn't fail the job
        timeout-minutes: 5
        run: npm run e2e
      - name: Always cleanup
        if: always()
        run: ./cleanup.sh