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!'
})