GitHub Actions is like having a robot assistant that automatically does tasks for you whenever something happens in your GitHub repository. This tutorial will take you from complete beginner to advanced user, putting you ahead of 90% of developers.
Table of Contents
- What is GitHub Actions? (ELI5)
- Core Concepts
- Your First Workflow
- Intermediate Techniques
- Advanced Patterns
- Real-World Examples
- Pro Tips & Best Practices
- Useful Resources
What is GitHub Actions? (ELI5)
Imagine you have a lemonade stand. Every time you make lemonade, you need to:
- Squeeze lemons
- Add sugar
- Mix with water
- Taste test
- Pour into cups
GitHub Actions is like hiring a robot that automatically does steps 2-4 every time you finish step 1. In programming terms:
- Event = You push code to GitHub
- Workflow = The recipe (steps to follow)
- Actions = Individual tasks (test code, deploy website, send notifications)
Core Concepts
1. Workflows
A YAML file (.github/workflows/name.yml) that defines when and what to run.
2. Events (Triggers)
What starts your workflow:
push- Code is pushedpull_request- PR is opened/updatedschedule- Run on a timer (like cron)workflow_dispatch- Manual triggerrelease- New release is created
3. Jobs
A set of steps that run on the same machine. Jobs run in parallel by default.
4. Steps
Individual tasks within a job (run commands, use actions).
5. Runners
The computer that runs your workflow (GitHub-hosted or self-hosted).
Your First Workflow
Let’s create a simple workflow that runs tests when you push code.
File: .github/workflows/test.yml
name: Run Tests
# When to run
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# What to run
jobs:
test:
runs-on: ubuntu-latest
steps:
# Step 1: Get your code
- name: Checkout code
uses: actions/checkout@v4
# Step 2: Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
# Step 3: Install dependencies
- name: Install dependencies
run: npm install
# Step 4: Run tests
- name: Run tests
run: npm test
What’s happening:
- Triggers on push/PR to
mainbranch - Uses Ubuntu machine
- Checks out your code
- Installs Node.js 20
- Installs npm packages
- Runs your tests
Intermediate Techniques
1. Matrix Builds (Test Multiple Versions)
Test your code on multiple Node.js versions simultaneously:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
This creates 3 parallel jobs - one for each Node version!
2. Environment Variables & Secrets
jobs:
deploy:
runs-on: ubuntu-latest
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
steps:
- uses: actions/checkout@v4
- name: Deploy
run: |
echo "Deploying to production..."
./deploy.sh
Add secrets: Repository Settings → Secrets and variables → Actions → New repository secret
3. Conditional Steps
steps:
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: ./deploy-prod.sh
- name: Deploy to staging
if: github.ref == 'refs/heads/develop'
run: ./deploy-staging.sh
4. Caching Dependencies
Speed up workflows by caching node_modules:
steps:
- uses: actions/checkout@v4
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm test
Advanced Patterns
1. Reusable Workflows
Create a workflow that other workflows can call:
File: .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
deploy-token:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to ${{ inputs.environment }}
run: ./deploy.sh ${{ inputs.environment }}
env:
TOKEN: ${{ secrets.deploy-token }}
Use it:
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
secrets:
deploy-token: ${{ secrets.STAGING_TOKEN }}
2. Composite Actions (Custom Actions)
Create your own reusable action:
File: .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 }}
- name: Install dependencies
run: npm ci
shell: bash
- name: Build
run: npm run build
shell: bash
Use it:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
with:
node-version: '18'
3. Dynamic Matrix
Generate matrix values dynamically:
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
echo "matrix={\"include\":[{\"project\":\"web\"},{\"project\":\"api\"}]}" >> $GITHUB_OUTPUT
build:
needs: setup
strategy:
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- run: echo "Building ${{ matrix.project }}"
4. Artifacts & Outputs
Share data between jobs:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: ./deploy.sh
Real-World Examples
Example 1: Complete CI/CD Pipeline
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v3
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to production
run: |
echo "Deploying to production..."
# Your deployment script here
Example 2: Automated Releases
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
npm ci
npm run build
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Example 3: Scheduled Tasks
name: Daily Cleanup
on:
schedule:
# Run at 2 AM UTC every day
- cron: '0 2 * * *'
workflow_dispatch: # Allow manual trigger
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Clean old logs
run: |
find ./logs -type f -mtime +30 -delete
- name: Commit changes
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "Automated cleanup" || echo "No changes"
git push
Pro Tips & Best Practices
1. Use workflow_dispatch for Manual Triggers
Always add this to test workflows manually:
on:
push:
branches: [ main ]
workflow_dispatch: # Add this!
2. Pin Action Versions
❌ Bad:
- uses: actions/checkout@v4
✅ Good:
- uses: actions/checkout@v4.1.1
3. Use Concurrency to Cancel Old Runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
4. Fail Fast vs. Fail Safe
strategy:
fail-fast: false # Continue other jobs even if one fails
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
5. Use GitHub CLI in Workflows
- name: Create issue
run: |
gh issue create \
--title "Build failed" \
--body "Build failed on commit ${{ github.sha }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6. Debugging Workflows
- name: Debug
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
Enable debug logging: Repository Settings → Secrets → Add ACTIONS_STEP_DEBUG = true
7. Optimize Workflow Speed
- Use caching for dependencies
- Run jobs in parallel
- Use
ubuntu-latest(fastest runner) - Minimize checkout depth:
fetch-depth: 1
8. Security Best Practices
- Never hardcode secrets
- Use
GITHUB_TOKENwhen possible - Limit permissions:
permissions:
contents: read
pull-requests: write
Useful Resources
Official Documentation
- GitHub Actions Documentation - Official docs
- Workflow Syntax - Complete YAML reference
- GitHub Actions Marketplace - Pre-built actions
Learning Resources
- GitHub Actions by Example - Practical examples
- GitHub Skills - Interactive tutorials
- Awesome Actions - Curated list of actions
Tools
- act - Run GitHub Actions locally
- actionlint - Lint your workflows
- GitHub Actions VSCode Extension - Syntax highlighting & validation
Community
- GitHub Community Forum - Ask questions
- r/github - Reddit community
Conclusion
You now know:
- ✅ What GitHub Actions is and why it’s powerful
- ✅ How to create basic workflows
- ✅ Intermediate techniques (matrix, caching, secrets)
- ✅ Advanced patterns (reusable workflows, composite actions)
- ✅ Real-world CI/CD pipelines
- ✅ Pro tips and best practices
You’re now ahead of 90% of GitHub users! 🚀
Start small, experiment, and gradually build more complex workflows. The key is to automate repetitive tasks and let GitHub Actions do the heavy lifting.