Core Concepts
How it worksโ
Push code โ GitHub detects trigger โ runs Workflow โ spins up Runner โ executes Jobs โ runs Steps
Workflowโ
A YAML file in .github/workflows/ that defines what to automate. One repo can have many workflows.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Hello World"
Triggers (on:)โ
What starts the workflow.
| Trigger | When it fires |
|---|---|
push | On every push |
pull_request | When a PR is opened/updated |
workflow_dispatch | Manual trigger (button in GitHub UI) |
schedule | Cron schedule |
release | When a release is published |
workflow_call | Called by another workflow |
on:
push:
branches: [main, develop]
paths:
- 'src/**' # only when src/ changes
pull_request:
branches: [main]
schedule:
- cron: '0 8 * * 1' # every Monday at 8am UTC
workflow_dispatch: # manual trigger button
Jobsโ
A job is a set of steps that run on the same runner. Jobs run in parallel by default.
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "running tests"
build:
runs-on: ubuntu-latest
steps:
- run: echo "building"
Run jobs sequentially with needs:
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
deploy:
needs: test # waits for test to pass first
runs-on: ubuntu-latest
steps:
- run: echo "deploying"
Stepsโ
Each step in a job either runs a command or uses an action.
steps:
# Run a shell command
- name: Install dependencies
run: npm install
# Use a pre-built action
- name: Checkout code
uses: actions/checkout@v4
# Multi-line command
- name: Build and test
run: |
npm run build
npm test
Runnersโ
The machine that executes the job. GitHub provides hosted runners:
| Runner | OS |
|---|---|
ubuntu-latest | Ubuntu (most common) |
windows-latest | Windows Server |
macos-latest | macOS |
You can also set up self-hosted runners on your own infrastructure.
Actionsโ
Reusable units of code from the GitHub Marketplace. Always pin to a version tag.
steps:
- uses: actions/checkout@v4 # checks out your repo
- uses: actions/setup-node@v4 # installs Node.js
with:
node-version: '20'
- uses: docker/login-action@v3 # logs into Docker Hub
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
Environment Variablesโ
env:
NODE_ENV: production # workflow-level (all jobs)
jobs:
build:
env:
APP_NAME: my-app # job-level (all steps in this job)
steps:
- name: Print env
env:
STEP_VAR: hello # step-level
run: echo "$APP_NAME $STEP_VAR"
Secretsโ
Stored in GitHub repo/org settings โ referenced with ${{ secrets.NAME }}.
steps:
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: aws s3 sync ./build s3://my-bucket
Secrets are masked in logs โ they won't appear even if you
echothem.
Artifactsโ
Share files between jobs or download after a workflow run.
# Upload
- uses: actions/upload-artifact@v4
with:
name: build-output
path: ./build
# Download (in another job)
- uses: actions/download-artifact@v4
with:
name: build-output
path: ./build
Matrix (Run job across multiple versions)โ
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test
Runs the job 3 times in parallel โ once per Node version.