Last Updated: November 21, 2025
Advanced Triggers
on: [push, pull_request]
Multiple event triggers
on: workflow_dispatch
Manual trigger from UI
on: schedule: - cron: '0 0 * * *'
Scheduled workflow (daily at midnight)
on: repository_dispatch
Trigger via API webhook
on: workflow_call
Make workflow reusable
paths: ['src/**']
Only trigger on specific file changes
paths-ignore: ['docs/**']
Ignore specific file changes
branches: [main, develop]
Only run on specific branches
tags: ['v*']
Trigger on version tags
types: [opened, synchronize]
Specific PR event types
Matrix Strategies
strategy: matrix: node: [14, 16, 18]
Test across multiple Node versions
matrix: os: [ubuntu-latest, windows-latest, macos-latest]
Test across multiple operating systems
matrix: include: - os: ubuntu, node: 18
Add specific matrix combinations
matrix: exclude: - os: windows, node: 14
Exclude specific combinations
${{ matrix.node }}
Access matrix value in steps
fail-fast: false
Continue all matrix jobs on failure
max-parallel: 2
Limit concurrent matrix jobs
Reusable Workflows
on: workflow_call: inputs:
Define reusable workflow with inputs
inputs: environment: type: string
Define string input parameter
inputs: debug: type: boolean default: false
Boolean input with default
secrets: token: required: true
Define required secret input
uses: owner/repo/.github/workflows/reusable.yml@v1
Call reusable workflow
with: environment: production
Pass inputs to reusable workflow
secrets: inherit
Pass all secrets to reusable workflow
outputs: result: value: ${{ jobs.build.outputs.value }}
Define workflow outputs
Composite Actions
runs: using: composite
Define composite action
inputs: name: description: 'Input description'
Define action input
${{ inputs.name }}
Access input in composite action
outputs: result: value: ${{ steps.id.outputs.value }}
Define action outputs
shell: bash
Specify shell for run step in composite
uses: ./path/to/action
Use local composite action
Contexts & Expressions
${{ github.event_name }}
Event that triggered workflow
${{ github.ref }}
Branch or tag ref
${{ github.sha }}
Commit SHA
${{ github.actor }}
User who triggered workflow
${{ github.repository }}
Repository owner/name
${{ runner.os }}
Runner operating system
${{ job.status }}
Current job status
${{ steps.step_id.outputs.name }}
Access step output
${{ secrets.SECRET_NAME }}
Access repository secret
${{ vars.VARIABLE_NAME }}
Access repository/environment variable
Conditionals
if: github.ref == 'refs/heads/main'
Only run on main branch
if: success()
Run if previous steps succeeded
if: failure()
Run if any previous step failed
if: always()
Always run regardless of status
if: cancelled()
Run if workflow was cancelled
if: contains(github.event.head_commit.message, '[skip ci]')
Check commit message content
if: startsWith(github.ref, 'refs/tags/')
Run only on tags
if: github.event_name == 'pull_request'
Run only for pull requests
Caching
uses: actions/cache@v3
Cache dependencies action
path: ~/.npm
Directory to cache
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
Cache key with file hash
restore-keys: ${{ runner.os }}-node-
Fallback cache keys
${{ steps.cache.outputs.cache-hit }}
Check if cache was restored
Artifacts
uses: actions/upload-artifact@v3
Upload build artifacts
name: build-output
Artifact name
path: dist/
Files to upload
retention-days: 7
How long to keep artifact
uses: actions/download-artifact@v3
Download artifact from previous job
with: name: build-output
Specify artifact to download
Environment & Secrets
environment: production
Deploy to specific environment
environment: name: staging url: https://staging.example.com
Environment with deployment URL
secrets: inherit
Inherit organization secrets
env: NODE_ENV: production
Set environment variable
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Use automatic GitHub token
Concurrency Control
concurrency: group: ${{ github.workflow }}-${{ github.ref }}
Concurrency group per branch
cancel-in-progress: true
Cancel running workflows on new push
concurrency: production
Simple concurrency group name
Job Dependencies
needs: [build, test]
Wait for multiple jobs
needs: build
Wait for single job
if: needs.build.result == 'success'
Check dependency job result
${{ needs.build.outputs.version }}
Access output from dependency job
Permissions
permissions: contents: read
Read-only access to repository
permissions: contents: write
Write access to repository
permissions: pull-requests: write
Write access to pull requests
permissions: issues: write
Write access to issues
permissions: {}
No permissions (most restrictive)
Docker Containers
runs-on: ubuntu-latest container: node:18
Run job in Docker container
container: image: node:18 env: NODE_ENV: production
Container with environment variables
services: postgres: image: postgres:14
Service container for testing
options: --health-cmd pg_isready
Container health check
Best Practices
timeout-minutes: 30
Set job timeout
continue-on-error: true
Allow step to fail without failing job
uses: actions/checkout@v4
Always pin action versions
run: echo "::set-output name=value::$VALUE"
Set step output (deprecated, use GITHUB_OUTPUT)
run: echo "value=$VALUE" >> $GITHUB_OUTPUT
Set step output (recommended)
run: echo "::error::Error message"
Log error message
run: echo "::warning::Warning message"
Log warning message
💡 Pro Tip:
Use matrix strategies to test across multiple configurations simultaneously. Implement reusable workflows to avoid duplicating CI/CD logic across repositories!