GitHub Actions¶
GitHub Actions is a CI/CD platform integrated into GitHub that allows you to automate build, test, and deployment workflows directly from your repository.
Workflow Basics¶
Workflows are defined in YAML files in .github/workflows/.
Minimal Workflow¶
# .github/workflows/ci.yml
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Hello, World!"
Triggers (on)¶
Push and Pull Request¶
on:
push:
branches: [main, develop]
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
Manual Trigger¶
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
debug:
description: 'Enable debug mode'
required: false
type: boolean
default: false
Schedule (Cron)¶
on:
schedule:
# Every day at 2 AM UTC
- cron: '0 2 * * *'
# Every Monday at 9 AM UTC
- cron: '0 9 * * 1'
Other Triggers¶
on:
release:
types: [published, created]
workflow_run:
workflows: ["Build"]
types: [completed]
workflow_call: # Reusable workflow
repository_dispatch:
types: [deploy]
Jobs¶
Basic Job¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build
Job Dependencies¶
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
deploy:
needs: [build, test]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: echo "Deploying..."
Matrix Strategy¶
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 18
include:
- os: ubuntu-latest
node: 22
experimental: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
Concurrency¶
jobs:
deploy:
runs-on: ubuntu-latest
concurrency:
group: production-deploy
cancel-in-progress: false
# Or at workflow level
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Steps¶
Run Commands¶
steps:
- name: Single command
run: echo "Hello"
- name: Multi-line command
run: |
echo "First line"
echo "Second line"
- name: With working directory
run: npm test
working-directory: ./packages/core
- name: With shell
run: |
echo $SHELL
shell: bash
- name: With environment
run: echo $MY_VAR
env:
MY_VAR: hello
Use Actions¶
steps:
# From GitHub
- uses: actions/checkout@v4
with:
fetch-depth: 0
# From a specific commit
- uses: actions/checkout@a81bbbf
# From another repository
- uses: owner/repo@v1
# From current repository
- uses: ./.github/actions/my-action
Conditionals¶
steps:
- name: Only on main
if: github.ref == 'refs/heads/main'
run: echo "On main branch"
- name: Only on PR
if: github.event_name == 'pull_request'
run: echo "Pull request"
- name: Only on failure
if: failure()
run: echo "Previous step failed"
- name: Always run
if: always()
run: echo "Cleanup"
- name: On success
if: success()
run: echo "All good"
- name: Complex condition
if: |
github.event_name == 'push' &&
contains(github.event.head_commit.message, '[deploy]')
run: echo "Deploy triggered"
Common Actions¶
Checkout¶
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history (for tags, etc.)
submodules: true # Checkout submodules
token: ${{ secrets.PAT }} # For private repos
Setup Node.js¶
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # or 'pnpm', 'yarn'
registry-url: 'https://registry.npmjs.org'
Setup Python¶
Setup Go¶
Setup Rust¶
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
Setup Java¶
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven' # or 'gradle'
Cache¶
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Upload/Download Artifacts¶
# Upload
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 5
# Download (in another job)
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
Secrets and Variables¶
Using Secrets¶
env:
API_KEY: ${{ secrets.API_KEY }}
steps:
- name: Deploy
run: ./deploy.sh
env:
TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Environment Variables¶
env:
NODE_ENV: production
jobs:
build:
runs-on: ubuntu-latest
env:
CI: true
steps:
- run: echo $NODE_ENV
GitHub Context¶
steps:
- run: |
echo "Repository: ${{ github.repository }}"
echo "Branch: ${{ github.ref_name }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Event: ${{ github.event_name }}"
echo "Run ID: ${{ github.run_id }}"
echo "Run Number: ${{ github.run_number }}"
Environments¶
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo "Deploying to staging"
deploy-production:
runs-on: ubuntu-latest
needs: deploy-staging
environment:
name: production
url: https://example.com
steps:
- run: echo "Deploying to production"
Complete Examples¶
Node.js CI¶
name: Node.js CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
Python CI¶
name: Python CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Install dependencies
run: uv sync
- name: Run tests
run: uv run pytest --cov
- name: Lint
run: uv run ruff check .
Rust CI¶
name: Rust CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-features
Docker Build and Push¶
name: Docker
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Release Automation¶
name: Release
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: changelog
uses: orhun/git-cliff-action@v3
with:
config: cliff.toml
args: --latest --strip header
- name: Create Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.content }}
draft: false
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}
Scheduled Dependency Updates¶
name: Dependency Review
on:
schedule:
- cron: '0 0 * * 1' # Every Monday
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Update dependencies
run: |
npm update
npm audit fix || true
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update dependencies'
title: 'chore: update dependencies'
branch: deps/update
delete-branch: true
Reusable Workflows¶
Define Reusable Workflow¶
# .github/workflows/reusable-build.yml
name: Reusable Build
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
secrets:
NPM_TOKEN:
required: false
outputs:
artifact-name:
description: 'Name of the build artifact'
value: ${{ jobs.build.outputs.artifact-name }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-name: ${{ steps.upload.outputs.artifact-name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build
- id: upload
uses: actions/upload-artifact@v4
with:
name: build-${{ github.sha }}
path: dist/
Use Reusable Workflow¶
# .github/workflows/ci.yml
name: CI
on: push
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '22'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploying ${{ needs.build.outputs.artifact-name }}"
Composite Actions¶
# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Setup Node.js and install dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
shell: bash
run: npm ci
- name: Build
shell: bash
run: npm run build
Use in workflow:
Best Practices¶
- Pin action versions: Use
@v4not@main - Use caching: Speed up builds with action caches
- Minimize secrets: Only use what's needed
- Use environments: For deployment protection
- Fail fast: Use
fail-fast: falseonly when needed - Limit concurrency: Prevent parallel deployments
- Use reusable workflows: DRY principle