GitHub Actions Tutorial — Triggers Exercises

3 minute read

Practice Exercises — Triggers & Events

Test your understanding of triggers, filtering, and event chaining with these hands-on exercises.


Exercise 1 — Path Filtering for a Monorepo

Task: You have a monorepo with this structure:

frontend/
backend/
docs/
shared/

Create two workflow files:

  1. frontend-ci.yml — runs npm test in frontend/ only when frontend/** or shared/** files change
  2. backend-ci.yml — runs ./gradlew test in backend/ only when backend/** or shared/** files change

Both should trigger on push to main and pull requests to main.

Show Solution

frontend-ci.yml:


name: Frontend CI

on:
  push:
    branches: [main]
    paths:
      - 'frontend/**'
      - 'shared/**'
  pull_request:
    branches: [main]
    paths:
      - 'frontend/**'
      - 'shared/**'

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

backend-ci.yml:


name: Backend CI

on:
  push:
    branches: [main]
    paths:
      - 'backend/**'
      - 'shared/**'
  pull_request:
    branches: [main]
    paths:
      - 'backend/**'
      - 'shared/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cd backend && ./gradlew test


Exercise 2 — Nightly Build with Conditional Steps

Task: Create a workflow that:

  1. Runs on push to main AND on a nightly schedule (2am UTC)
  2. Always runs unit tests
  3. Runs end-to-end tests only during the nightly run (not on push)
Show Solution

name: CI + Nightly

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'

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

      - name: Unit tests
        run: npm test

      - name: E2E tests (nightly only)
        if: github.event_name == 'schedule'
        run: npm run test:e2e

The if: github.event_name == 'schedule' condition ensures E2E tests only run during scheduled (nightly) executions.


Exercise 3 — Release Pipeline

Task: Create a workflow that:

  1. Triggers when a GitHub release is published
  2. Prints the release tag name and release notes
  3. Only runs the deploy step if the release is NOT a pre-release
Show Solution

name: Release Deploy

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Release info
        run: |
          echo "Tag: ${{ github.event.release.tag_name }}"
          echo "Pre-release: ${{ github.event.release.prerelease }}"
          echo "Notes: ${{ github.event.release.body }}"

      - name: Deploy to production
        if: github.event.release.prerelease == false
        run: echo "Deploying ${{ github.event.release.tag_name }} to production"

      - name: Deploy to staging
        if: github.event.release.prerelease == true
        run: echo "Deploying ${{ github.event.release.tag_name }} to staging"


Exercise 4 — Workflow Chain

Task: Create two workflow files:

  1. build.yml — Triggers on push to main, runs echo "Build complete"
  2. deploy.yml — Triggers when build.yml completes successfully, runs echo "Deploying..."

Make sure deploy does NOT run if build fails.

Show Solution

build.yml:


name: Build

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Build complete"

deploy.yml:


name: Deploy

on:
  workflow_run:
    workflows: ["Build"]
    types: [completed]
    branches: [main]

jobs:
  deploy:
    if: github.event.workflow_run.conclusion == 'success'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying..."

The if condition checks that Build completed successfully before deploying. The workflows: ["Build"] must match the name: in build.yml exactly.


Exercise 5 — Comprehensive Trigger Design

Task: Design the trigger configuration (just the on: section) for a workflow that:

  1. Runs on pushes to main and develop
  2. Runs on pull requests targeting main
  3. Ignores changes to docs/** and **.md files
  4. Can be triggered manually with a reason input
  5. Runs nightly at 3am UTC on weekdays only
Show Solution

on:
  push:
    branches: [main, develop]
    paths-ignore:
      - 'docs/**'
      - '**.md'
  pull_request:
    branches: [main]
    paths-ignore:
      - 'docs/**'
      - '**.md'
  schedule:
    - cron: '0 3 * * 1-5'    # 3am UTC, Mon-Fri
  workflow_dispatch:
    inputs:
      reason:
        description: 'Reason for manual trigger'
        required: false
        type: string

This combines all four trigger types. The paths-ignore applies to both push and pull_request. The schedule uses 1-5 for weekdays (Mon–Fri). The manual trigger accepts an optional reason input.


← Back to: Chapter 3: Triggers

Next Chapter: Chapter 4: Jobs & Runners →

Updated: