GitHub Actions — Complete Guide
Comprehensive reference for building CI/CD pipelines with GitHub Actions.
Why / When to Use
Use when automating build, test, deploy, or any recurring workflow on a GitHub-hosted repository. Supports GitHub-hosted and self-hosted runners — the latter useful for accessing local LLM proxies (LiteLLM, DeepSeek).
Core Concepts
Repository layout:
.github/
└── workflows/
├── ci.yml ← each .yml file is one workflow
├── deploy.yml
└── release.yml
| Term | Meaning |
|---|---|
| Workflow | Automated process defined in a .yml file |
Trigger (on) | What starts the workflow |
| Job | Group of steps running on the same machine |
| Step | Individual command or action |
| Action | Reusable unit (marketplace or custom) |
| Runner | Machine that executes jobs |
Core Concept / Commands
1. Basic CI — test on every push
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install
- run: npm test2. Multiple jobs with dependencies
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install && npm test
build:
runs-on: ubuntu-latest
needs: test # only runs if test passes
steps:
- uses: actions/checkout@v4
- run: npm run build
deploy:
runs-on: ubuntu-latest
needs: build # only runs if build passes
steps:
- run: echo "Deploying..."3. Using secrets
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: ./deploy.shAdd secrets: Repo → Settings → Secrets and variables → Actions
4. Matrix strategy — test multiple versions
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm install && npm testRuns 9 jobs (3 OS × 3 Node versions) in parallel.
5. Docker build and push
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: myuser/myapp:latest6. Scheduled jobs (cron)
on:
schedule:
- cron: '0 2 * * *' # every day at 2am UTC
workflow_dispatch: # also allow manual trigger7. Conditional steps
steps:
- run: npm test
- name: Notify on failure
if: failure()
run: curl -X POST ${{ secrets.SLACK_WEBHOOK }} -d '{"text":"Build failed!"}'
- name: Deploy only on main
if: github.ref == 'refs/heads/main'
run: npm run deploy
- name: Skip on draft PR
if: github.event.pull_request.draft == false
run: npm run heavy-tests8. Reusable workflow
# .github/workflows/reusable-test.yml
on:
workflow_call:
inputs:
node-version:
required: true
type: string
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm test# Caller workflow
jobs:
call-test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'9. Self-hosted runner
jobs:
build:
runs-on: self-hosted # uses your own machine
steps:
- uses: actions/checkout@v4
- run: npm install && npm testSetup:
# Repo → Settings → Actions → Runners → New self-hosted runner
./config.sh --url https://github.com/user/repo --token YOUR_TOKEN
./run.shKey use case: self-hosted runner can call a local LiteLLM/DeepSeek proxy directly, enabling AI-powered CI steps without egressing tokens externally.
10. Full real-world pipeline
name: Full Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci && npm run lint
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci && npm test
build-docker:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
deploy:
runs-on: self-hosted
needs: build-docker
if: github.ref == 'refs/heads/main'
steps:
- name: Pull and restart
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker compose up -dKey Options / Variants
| Syntax | Meaning |
|---|---|
on: push | Trigger on push |
needs: jobname | Wait for another job |
if: failure() | Run only on failure |
${{ secrets.X }} | Use a secret |
${{ github.ref }} | Current branch ref |
runs-on: self-hosted | Use your own machine |
strategy.matrix | Run multiple combinations |
workflow_dispatch | Allow manual trigger |
Gotchas
workflow_dispatchis required for manual runs from the GitHub UI- Matrix jobs run in parallel — add
max-parallelto throttle if needed GITHUB_TOKENis auto-generated per workflow run; no manual secret needed for GHCR pushes- Self-hosted runners persist state between runs (node_modules, Docker cache) — useful for speed but can cause stale-cache bugs
Source
Conversation “Multiple GitHub repositories in one workspace” — 2026-05-19 (Claude Code project)