Explore the complete learning track from Linux fundamentals to advanced GitOps and Terraform. Packed with practical terminal sessions and real-world architectures.
Lesson 4 of 8ā¢25 min
Triggers, Filters & Events
The on: block determines when your workflow runs. Using the wrong triggers wastes CI minutes and slows feedback. Using the right triggers gives you fast, targeted automation.
Push Events
yaml
on:
push:
# Run on specific branches only
branches:
- main
- develop
- 'release/**' # Glob: any branch starting with release/
- '!feature/**' # Negation: exclude feature branches
# Only trigger if these file paths changed
paths:
- 'src/**'
- 'package.json'
- 'package-lock.json'
- 'Dockerfile'
# Exclude paths (don't trigger if ONLY these changed)
paths-ignore:
- '**.md' # Skip if only markdown docs changed
- 'docs/**' # Skip if only docs/ changed
- '.github/CODEOWNERS'
# Trigger on version tags (e.g., v1.2.3)
tags:
- 'v*.*.*' # Matches v1.2.3, v10.0.0-beta.1, etc.
- 'v[0-9]+.[0-9]+.[0-9]+' # Strict semantic version
Why paths Filters Save Money
Without path filters, pushing a typo fix in README.md triggers your full 10-minute CI pipeline. With path filters, documentation-only changes are completely ignored:
yaml
# Real example: only run backend tests if backend code changed
on:
push:
paths:
- 'apps/api/**' # Run if API code changed
- 'packages/shared/**' # Run if shared lib changed (affects API)
- 'package.json' # Run if deps changed
# NOT triggered by: apps/web/**, docs/**, *.md
Pull Request Events
yaml
on:
pull_request:
# Which branches PRs must target to trigger this workflow
branches:
- main
- 'release/**'
# PR activity types that trigger the workflow
types:
- opened # PR opened
- synchronize # New commits pushed to PR branch
- reopened # Closed PR reopened
- ready_for_review # Converted from draft to ready
# Other types: closed, labeled, unlabeled, assigned, unassigned, review_requested
# Same path filters as push
paths:
- 'src/**'
PR-Specific Context
yaml
steps:
- name: Comment on PR with test results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number, // PR number
owner: context.repo.owner,
repo: context.repo.repo,
body: 'ā All tests passed! Coverage: 87.3%'
})
# Target branch (what the PR merges into)
- run: echo "Merging into: ${{ github.base_ref }}" # e.g., main
# Source branch (the feature branch)
- run: echo "From branch: ${{ github.head_ref }}" # e.g., feature/login
Scheduled Triggers (Cron)
yaml
on:
schedule:
# Nightly security scan at 2:00 AM UTC
- cron: '0 2 * * *'
# Weekly dependency audit every Monday at 9 AM UTC
- cron: '0 9 * * 1'
# Multiple schedules for the same workflow
- cron: '0 */6 * * *' # Every 6 hours
Cron Expression Reference
text
āāāāāāāāāāā minute (0-59)
ā āāāāāāāāā hour (0-23)
ā ā āāāāāāā day of month (1-31)
ā ā ā āāāāā month (1-12 or JAN-DEC)
ā ā ā ā āāā day of week (0-6, 0=Sunday, or SUN-SAT)
ā ā ā ā ā
* * * * *
Examples:
0 0 * * * ā midnight every day
0 9 * * 1-5 ā 9 AM on weekdays (Mon-Fri)
0 */4 * * * ā every 4 hours
30 2 1 * * ā 2:30 AM on 1st of each month
0 0 * * 0 ā midnight every Sunday
Scheduled Workflows Run on Default Branch
Scheduled workflows always run on the repository's default branch. Also, GitHub may skip a scheduled run if no changes have occurred in the repository recently ā don't rely on schedules for critical operations.
Manual Trigger with workflow_dispatch
yaml
on:
workflow_dispatch:
inputs:
# Text input
image-tag:
description: 'Docker image tag to deploy'
required: true
type: string
default: 'latest'
# Dropdown / select
environment:
description: 'Target environment'
required: true
type: choice
options:
- development
- staging
- production
default: staging
# Boolean toggle
skip-tests:
description: 'Skip test suite (emergency deploy only)'
type: boolean
default: false
# Number
replica-count:
description: 'Number of replicas to deploy'
type: number
default: 3
Run a workflow AFTER another workflow completes ā useful for decoupling CI (runs on PR) from CD (runs after CI passes):
yaml
# .github/workflows/deploy.yml
on:
workflow_run:
workflows: ["CI Pipeline"] # Must match the `name:` of the other workflow
branches: [main]
types: [completed] # or: requested, in_progress
jobs:
deploy:
# Only deploy if CI actually passed (not if it was cancelled or failed)
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Get the artifact from the CI run
uses: actions/download-artifact@v4
with:
run-id: ${{ github.event.workflow_run.id }}
name: production-build
- name: Deploy
run: ./deploy.sh
paths Optimization: Advanced Patterns
yaml
# Separate workflows for frontend and backend
# Frontend CI ā only runs if frontend code changed
on:
push:
paths:
- 'apps/web/**'
- 'packages/ui/**'
# Backend CI ā only runs if backend code changed
on:
push:
paths:
- 'apps/api/**'
- 'packages/shared/**'
- 'Dockerfile'
# Infrastructure CI ā only runs if IaC changed
on:
push:
paths:
- 'terraform/**'
- 'k8s/**'
- '.github/workflows/**' # Pipeline changes always trigger
Summary: Trigger Selection Guide
Trigger
When to use
push to main
Deploy to production
push to develop
Deploy to staging
pull_request
Run CI checks, block merge if failing
push with tags: 'v*'
Build release artifacts, publish to registry
schedule
Nightly security scans, weekly dependency audits, cleanup jobs
Combine push with paths filters to only run expensive jobs when relevant code changes ā this can reduce CI minute usage by 30ā60% on large monorepos.
In the next lesson, you will master jobs and steps ā multi-job workflows, service containers, artifacts, and conditional execution patterns.