Skip to main content

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.

TriggerWhen it fires
pushOn every push
pull_requestWhen a PR is opened/updated
workflow_dispatchManual trigger (button in GitHub UI)
scheduleCron schedule
releaseWhen a release is published
workflow_callCalled 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:

RunnerOS
ubuntu-latestUbuntu (most common)
windows-latestWindows Server
macos-latestmacOS

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 echo them.


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.