Skip to main content

GitHub Actions Cheat Sheet

GitHub Actions is an event-driven automation engine built into GitHub.

# This is a GitHub Actions workflow file
# Workflows are defined in YAML files in your repository's .github/workflows/ directory
# Comments in YAML look like this.

# Name of the workflow (displayed in the Actions tab)
name: Learn GitHub Actions

# Optional: The name for workflow runs generated from the workflow.
run-name: Learning GitHub Actions workflow triggered by ${{ github.actor }}

# Events that trigger the workflow
on:
  # Trigger on push
  push:
    # Path filters
    paths:
      - "src/**" # Any file in src/ and subdirectories
      - "docs/*.md" # Markdown files in docs/ only
      - "**.js" # JavaScript files anywhere
      - "!src/test/**" # Exclude test files
      - "config/[a-z]*.yml" # YAML files starting with lowercase letter
      - ".github/workflows/**" # Any workflow file changes

    # Branch filters
    branches:
      - main # main branch
      - "release/v[0-9].[0-9]" # release/v1.0, release/v2.1, etc.
      - "feature/*" # Any feature branch
      - "!experimental" # Exclude experimental branch

    # Tag filters
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+" # Semantic version tags
      - "!*-alpha" # Exclude alpha tags

  # Trigger on pull requests targeting main
  pull_request:
    branches: [main]

  # Trigger on pull request reviews
  pull_request_review:
    types: [submitted]

  # Trigger manually from the GitHub UI
  workflow_dispatch:
    # Optional: Define inputs for manual trigger
    inputs:
      example-input:
        description: "An example input for manual trigger"
        required: false # Whether this input is required
        default: "default-value"
        type: string # type can be string, boolean, choice, environment, or number

  # Trigger on a schedule (cron syntax)
  schedule:
    - cron: "0 0 * * *" # Daily at midnight UTC

# Environment variables available to all jobs
env:
  NODE_VERSION: 18
  PYTHON_VERSION: "3.9"

# Use permissions to modify the default permissions granted to the GITHUB_TOKEN
permissions:
  write-all # write-all|read-all or disable with {}
  # actions: read|write|none
  # attestations: read|write|none
  # checks: read|write|none
  # contents: read|write|none
  # deployments: read|write|none
  # id-token: write|none
  # issues: read|write|none
  # models: read|none
  # discussions: read|write|none
  # packages: read|write|none
  # pages: read|write|none
  # pull-requests: read|write|none
  # security-events: read|write|none
  # statuses: read|write|none

# Provide defaults for steps
defaults:
  run:
    shell: bash # Default shell for all run steps
    working-directory: ./ # Default working directory for all run steps

# Concurrency allows you to control the concurrency of workflow runs.
concurrency:
  # The concurrency key is used to group workflows or jobs together into a concurrency group.
  group: ci-${{ github.ref }}
  # Cancel any in-progress job or run
  cancel-in-progress: true

# Jobs are the core building blocks of workflows
# All jobs run in parallel by default, each job is a new clean runner environment
jobs:
  # Job ID (can be referenced by other jobs)
  basic-job:
    # Human-readable name for the job
    name: Basic Job Example

    # The type of runner to use
    runs-on: ubuntu-latest
    timeout-minutes: 10 # Maximum time this job can run before being cancelled

    permissions: # Permissions for the GITHUB_TOKEN in this job
      contents: read # Read access to repository contents
      issues: write # Write access to issues

    # Job level environment variables
    env:
      JOB_ENV: "development"

    # Conditionally run this job
    if: 1 == 1

    # Outputs from this job can be used by other jobs
    outputs:
      example-output: "Hello, World!"

    # List of steps to execute
    steps:
      # GitHub Actions are called with the uses keyword
      - uses: sormuras/hello-world-java-action@v1 # {owner}/{repo}/{path}@{ref}
        name: My first step # Steps can have names for better readability
        timeout-minutes: 5 # The maximum time this step can run before being cancelled
        continue-on-error: true # Whether to continue running the workflow if this step fails
        with:
          # Inputs are parameters passed to the action
          who-to-greet: "Mona the Octocat"

      # Reference a specific commit
      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
      # Reference the major version of a release
      - uses: actions/checkout@v4
      # Reference a specific version
      - uses: actions/checkout@v4.2.0
      # Reference a branch
      - uses: actions/checkout@main

      # Run a simple shell command
      - run: echo "Hello, GitHub Actions!"

      # Run multiple commands with literal data types
      - name: Demonstrate data types and basic commands
        run: |
          echo "Current directory: $(pwd)"
          echo "Files: $(ls -la)"
          echo "Node version: $NODE_VERSION" # print job level environment variable
          echo "GitHub Token: ${{ secrets.GITHUB_TOKEN }}" # Secrets are automatically masked in logs
          echo "Value: $myIntegerNumber" # print step level environment variable
          echo "step-output=Hello, World!" >> $GITHUB_OUTPUT # Set step output
          echo "::group::Examples"
          echo "::add-mask::This is secret and masked"
          echo "::debug::This is a debug message"
          echo "::notice file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
          stopMarker=$(uuidgen)
          echo "::stop-commands::$stopMarker"
          echo '::error:: This will NOT be rendered as a error, because stop-commands has been invoked.'
          echo "::$stopMarker::"
          echo '::warning:: This is a warning again, because stop-commands has been turned off.'
          echo "::endgroup::"
          echo "### Hello world! :rocket:" >> $GITHUB_STEP_SUMMARY # Create a job summary
          echo "Contains: ${{ contains('hello world', 'hello') }}"
          echo "Starts with: ${{ startsWith('hello world', 'hello') }}"
          echo "Ends with: ${{ endsWith('hello world', 'world') }}"
          echo "Format: ${{ format('Hello {0}!', 'world') }}"
          echo "Join: ${{ join(fromJSON('["apple", "banana", "cherry"]'), ', ') }}"
          echo "Object to JSON: ${{ toJSON(fromJSON('{"name": "octocat", "type": "mascot"}')) }}"
          echo "Hash Files: ${{ hashFiles('**/package-lock.json') }}"
        env:
          # Literal data types in expressions
          myNull: ${{ null }}
          myBoolean: ${{ false }}
          myIntegerNumber: ${{ 711 }}
          myFloatNumber: ${{ -9.2 }}
          myHexNumber: ${{ 0xff }}
          myExponentialNumber: ${{ -2.99e-2 }}
          myString: Mona the Octocat
          myStringInBraces: ${{ 'It''s open source!' }}
          # Secrets are automatically masked in logs
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        id: example-step
        working-directory: ./

        # Contexts are a way to access information about workflow runs
      - name: Dump GitHub context
        run: |
          echo '${{ toJSON(github) }}'
          echo '${{ toJSON(env) }}'
          echo '${{ toJSON(vars) }}'
          echo '${{ toJSON(job) }}'
          echo '${{ toJSON(steps) }}'
          echo '${{ toJSON(runner) }}'
          echo '${{ toJSON(secrets) }}'
          echo '${{ toJSON(strategy) }}'
          echo '${{ toJSON(matrix) }}'
          echo '${{ toJSON(needs) }}'
          echo '${{ toJSON(inputs) }}'
          printenv

      # Use the outputs from a previous step
      - run: echo "${{ steps.example-step.outputs.step-output }}"

      # Step that continues on error
      - name: Step that might fail
        continue-on-error: true
        run: exit 1

      # Upload artifacts
      - name: Create artifact
        run: |
          mkdir -p artifacts
          echo "Build artifact" > artifacts/output.txt
          echo "Build number: ${{ github.run_number }}" >> artifacts/output.txt

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: artifacts/
          retention-days: 30

      # Conditionally run a step
      # The github context contains information about the workflow run and the event that triggered the run.
      - if: contains(fromJSON('["push", "pull_request"]'), github.event_name)
        run: echo "Deploying to production server on branch $GITHUB_REF"

      - if: ${{ always() }}
        run: echo "This step always runs"

      - if: ${{ cancelled() }}
        run: echo "This step runs if the workflow is cancelled"

      - if: ${{ failure() }}
        run: echo "This step runs if any previous step failed"

        # Object Filter Example
      - if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'bug') }}
        run: echo "This PR has a bug label"

        # Composite actions let your group multiple steps into a single yml file
      - name: Inline Composite Action
        uses: ./.github/actions/hello-world
        with:
          name: "GitHub Actions"
          greeting: "Hello"

  # Job with conditions and matrix strategy
  build-matrix:
    name: Build Matrix
    runs-on: ${{ matrix.os }}

    # Run this job only if the basic-job succeeds
    needs: basic-job

    # Run this job only on push events (not PRs)
    if: github.event_name == 'push'

    # Matrix strategy: run job multiple times with different configurations
    strategy:
      # Don't cancel other matrix jobs if one fails
      fail-fast: false
      # The maximum number of jobs to run in parallel
      max-parallel: 10
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        # Exclude specific combinations
        exclude:
          - os: windows-latest
            node-version: 16

    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      # Leverage caching to speed up builds
      - name: Cache node modules
        id: cache-npm
        uses: actions/cache@v4
        env:
          cache-name: cache-node-modules
        with:
          # npm cache files are stored in `~/.npm` on Linux/macOS
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}

      - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
        name: List the state of node modules
        continue-on-error: true
        run: npm list

      - run: npm i
      - run: npm run build
      - run: npm test

  container-job:
    runs-on: ubuntu-latest
    container:
      image: node:18
      env:
        NODE_ENV: development
      ports:
        - 80
      volumes:
        - my_docker_volume:/volume_mount
      options: --cpus 1
    steps:
      - name: Check for dockerenv file
        run: (ls /.dockerenv && echo Found dockerenv) || (echo No dockerenv)

  # Job demonstrating services (databases, caches, etc.)
  services-job:
    name: Services Example
    runs-on: ubuntu-latest

    # Define service containers
    services:
      # PostgreSQL service
      postgres:
        image: postgres:13
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      # Redis service
      redis:
        image: redis:6
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - name: Test database connection
        run: |
          sudo apt-get update
          sudo apt-get install -y postgresql-client
          PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d testdb -c '\l'
      - name: Test Redis connection
        run: |
          sudo apt-get install -y redis-tools
          redis-cli -h localhost -p 6379 ping

  # Reusable workflows are reusable jobs
  reusable-job:
    uses: austenstone/actions-playground/.github/workflows/reusable-called.yml@main
    with:
      username: "GitHub Actions User"
    secrets: inherit # You must pass secrets explicitly to reusable workflows

  # Job for deployment (typically runs after tests pass)
  deploy:
    name: Deploy Application
    runs-on: ubuntu-latest

    # Only run on main branch and after other jobs succeed
    if: github.ref == 'refs/heads/main'
    needs: [basic-job, build-matrix]

    # Use environments to track deployments and protect sensitive operations
    environment:
      name: github-pages
      url: https://myapp.example.com

    steps:
      - uses: actions/checkout@v4

      # Download artifacts from previous jobs
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-artifacts
          path: ./artifacts
      - name: Deploy to production
        run: |
          echo "Deploying to production..."
          echo "Artifact contents:"
          cat ./artifacts/output.txt

      # The GitHub CLI is pre-installed on GitHub-hosted runners
      - run: |
          gh pr comment ${{ github.event.pull_request.number }} \
            --body "🚀 **Deployment Successful!**
        if: github.event_name == 'pull_request' && success()
        # To use the GitHub CLI, you need to authenticate with a token
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Basic GitHub Script example - create an issue comment
      - name: Comment on issue
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '👋 Comment from github-script!'
            })

View on GitHub

More Resources​