GitHub 17 đ GitHub Actions Fundamentals and Workflow Files
GitHub Actions is an automation platform built into GitHub. It runs workflowsâa sequence of jobs and stepsâin response to events like a push, a pull request, or a scheduled time. A workflow is defined in a YAML file inside .github/workflows/. The file names the triggers, defines the jobs, specifies the runner environment, and lists the steps to execute. The platform is powerful, but its vocabulary is precise: a workflow contains jobs, a job contains steps, a step runs a command or an action, and an action is a reusable unit of code from the marketplace or a local repository. Understanding this hierarchy is the difference between writing a workflow that works and one that fails mysteriously.
Key point: A workflow is a YAML file in .github/workflows/. Jobs run in parallel by default, and each job runs on a fresh virtual machine. Steps within a job run sequentially and share the same filesystem.
Why GitHub Actions exists
The CI/CD integration problem. Before Actions, continuous integration required a separate serviceâTravis CI, CircleCI, Jenkinsâconfigured with its own tokens, webhooks, and billing. GitHub Actions embeds the runner directly into the repository. The workflow file is versioned alongside the code, the secrets are managed in GitHub’s settings, and the results appear in the pull request interface. There is no third-party service to configure or pay for separately.
The event-driven problem. A workflow is triggered by events: push, pull_request, schedule, workflow_dispatch, release, and dozens more. The trigger is defined at the top of the YAML file. A push to main can trigger a deployment; a pull request can trigger a test suite; a cron schedule can trigger a security scan. The event-driven model means workflows are not just CIâthey are automation for the entire repository lifecycle.
The reusable action problem. Every workflow step is either a shell command (run:) or an action (uses:). An action is a reusable packageâwritten in JavaScript or packaged as a Docker containerâthat performs a specific task. actions/checkout clones the repository. actions/setup-node installs Node.js. actions/cache caches dependencies. The marketplace hosts thousands of actions, and the same action can be used across repositories, eliminating duplicated shell scripts.
The cost problem. Public repositories run GitHub Actions for free. Private repositories receive a monthly quota of minutes (2,000 for Free tier, 3,000 for Pro, more for Team and Enterprise). The runners are hosted by GitHubâubuntu-latest, windows-latest, macos-latestâwith macOS runners costing more minutes per minute of runtime. Self-hosted runners can be attached to avoid the quota entirely.
The trade-off. GitHub Actions is not the fastest CI platform. The cold start time for a workflow is typically 10â30 seconds, and the runners are shared with other users. For projects that need sub-minute CI times, a self-hosted runner or a dedicated CI service may be preferable. The trade-off is convenience and integration versus raw speed.
a. The workflow file structure
A workflow file is a YAML document with a name, one or more triggers (on:), and one or more jobs (jobs:). The name is optional but appears in the Actions tab. The trigger determines when the workflow runs.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
The on: block defines the events. push triggers on every push to main. pull_request triggers on every pull request targeting main. workflow_dispatch enables manual triggering from the Actions tab or the API.
The jobs: block defines the jobs. Each job has a name (test), a runner (ubuntu-latest), and a list of steps. Steps run sequentially. If a step fails, the job stops (unless continue-on-error: true is set).
b. Runners, jobs, and parallel execution
Each job runs on a separate runnerâa fresh virtual machine or container. Jobs are independent unless dependencies are declared with needs:. This means jobs run in parallel by default, which reduces total workflow time but requires explicit ordering when one job depends on another.
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building"
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Testing after build"
deploy:
needs: [build, test]
runs-on: ubuntu-latest
steps:
- run: echo "Deploying after both"
The needs: keyword declares dependencies. The test job waits for build to complete. The deploy job waits for both. Without needs:, all three jobs run in parallel.
Available runner labels include ubuntu-latest, ubuntu-24.04, ubuntu-22.04, windows-latest, windows-2022, macos-latest, macos-14, and macos-13. The -latest label is a moving targetâit points to the most recent stable image. Pinning a specific version is recommended for reproducibility.
c. Steps, actions, and expressions
A step is a single unit of work. It is either a shell command (run:) or a reusable action (uses:). The run: step executes commands in the runner’s default shell. The uses: step downloads and executes an action from the marketplace or a local path.
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
Expressions provide conditional logic and data access. ${{ github.ref }} is the branch or tag reference. ${{ runner.os }} is the operating system. ${{ secrets.MY_TOKEN }} is a secret stored in the repository settings. ${{ matrix.node }} is a value from a matrix strategy.
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: ./deploy.sh
The if: condition controls whether a step runs. Without it, every step runs. With it, the step only runs when the expression evaluates to true.
Complete Example Session
# ============================================
# PART 1: CREATE THE WORKFLOW DIRECTORY
# ============================================
mkdir -p .github/workflows
# ============================================
# PART 2: CREATE A BASIC WORKFLOW
# ============================================
cat > .github/workflows/ci.yml <<'EOF'
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
EOF
# ============================================
# PART 3: COMMIT AND PUSH
# ============================================
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push origin main
# ============================================
# PART 4: VIEW THE RUN
# ============================================
# Navigate to the repository â Actions tab.
# The "CI" workflow appears with the latest run.
# ============================================
# PART 5: ADD A MATRIX STRATEGY
# ============================================
# Edit ci.yml to add a matrix:
# strategy:
# matrix:
# node: [18, 20, 22]
# Then use ${{ matrix.node }} in setup-node.
# ============================================
# PART 6: ADD CONDITIONAL STEP
# ============================================
# Add to steps:
# - name: Deploy
# if: github.ref == 'refs/heads/main'
# run: ./deploy.sh
# ============================================
# PART 7: ADD A SECOND JOB WITH needs
# ============================================
# Add a "deploy" job:
# deploy:
# needs: test
# runs-on: ubuntu-latest
# ============================================
# PART 8: TRIGGER MANUALLY
# ============================================
# Add "workflow_dispatch:" to the on: block.
# Then click "Run workflow" in the Actions tab.
# ============================================
# PART 9: VIEW LOGS
# ============================================
# Click on the run â click on the job â expand steps.
# Each step shows its output.
The nine parts trace the workflow lifecycle: directory creation, YAML authoring, commit, viewing the run, adding a matrix, adding a conditional step, adding a dependent job, manual triggering, and inspecting logs.
Quick Reference
Workflow Structure
| Key | Purpose |
|---|---|
name | Workflow name (display) |
on | Trigger events |
jobs | Jobs to run |
runs-on | Runner label |
steps | Steps within a job |
uses | Action reference |
run | Shell command |
with | Action inputs |
needs | Job dependency |
Common Triggers
| Trigger | Event |
|---|---|
push | Push to a branch |
pull_request | Pull request opened or updated |
schedule | Cron schedule |
workflow_dispatch | Manual trigger |
release | Release published |
Common Runner Labels
| Label | OS |
|---|---|
ubuntu-latest | Ubuntu 24.04 |
ubuntu-22.04 | Ubuntu 22.04 |
windows-latest | Windows 2022 |
macos-latest | macOS 14 |
Best Practices
â Do This:
uses: actions/checkout@v4
<!-- Pin action versions --> # â
needs: build
<!-- Declare dependencies between jobs --> # â
if: github.ref == 'refs/heads/main'
<!-- Gate deployment steps by branch --> # â
â Don’t Do This:
# Use latest tag for actions
<!-- Versions may change unexpectedly --> # â
# Hardcode secrets in the YAML
<!-- Use GitHub secrets instead --> # â
# Run all jobs without needs
<!-- Parallel jobs may race on shared state --> # â
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Workflow not running | File outside .github/workflows/ | Move to correct directory |
actions/checkout fails | Missing uses step | Add actions/checkout@v4 |
| Job runs before dependency | No needs: specified | Add needs: to dependent job |
| Secrets not accessible | Secrets not defined in repo | Add in Settings â Secrets |
| Action fails on version | Using @latest | Pin specific version |
Real-World Examples
1. Basic CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
2. Matrix Strategy
strategy:
matrix:
node: [18, 20, 22]
3. Conditional Step
if: github.ref == 'refs/heads/main'
4. Job Dependency
needs: [build]
5. Manual Trigger
on:
workflow_dispatch:
6. Scheduled Run
on:
schedule:
- cron: '0 0 * * *'
7. Secret Access
env:
TOKEN: ${{ secrets.MY_TOKEN }}
8. Artifact Upload
uses: actions/upload-artifact@v4
with:
path: dist/
9. Cache Dependencies
uses: actions/cache@v4
10. Multiple Runners
runs-on: ${{ matrix.os }}
Visual
Workflow Hierarchy
ââââââââââââââââââââââââââââââââââââââââââââââââ
â Workflow (ci.yml) â
â âââââââââââââââââââââââââââââââââââââââââââ â
â â on: push, pull_request â â
â âââââââââââââââââââââââââââââââââââââââââââ â
â â
â âââââââââââââââââââââââââââââââââââââââââââ â
â â Job: test (runs-on: ubuntu-latest) â â
â â âââââââââââââââââââââââââââââââââââââ â â
â â â Step 1: checkout â â â
â â â Step 2: npm ci â â â
â â â Step 3: npm test â â â
â â âââââââââââââââââââââââââââââââââââââ â â
â âââââââââââââââââââââââââââââââââââââââââââ â
â â
â âââââââââââââââââââââââââââââââââââââââââââ â
â â Job: deploy (needs: test) â â
â â âââ Step: ./deploy.sh â â
â âââââââââââââââââââââââââââââââââââââââââââ â
ââââââââââââââââââââââââââââââââââââââââââââââââ
Job Execution Flow
push to main
â
âź
ââââââââââââââââ
â Job: build â
ââââââââŹââââââââ
â
âź
ââââââââââââââââ
â Job: test â (needs: build)
ââââââââŹââââââââ
â
âź
ââââââââââââââââ
â Job: deploy â (needs: test)
ââââââââââââââââ
Jobs without needs run in parallel. With needs, they run in sequence.
Summary
| Item | Value |
|---|---|
| Workflow directory | .github/workflows/ |
| File extension | .yml or .yaml |
| Trigger key | on |
| Job key | jobs |
| Runner key | runs-on |
| Step types | uses (action) or run (shell) |
| Job dependency | needs |
| Secret access | ${{ secrets.NAME }} |
| Manual trigger | workflow_dispatch |
Key takeaways:
- A workflow is YAML in
.github/workflows/. Theonblock defines triggers, thejobsblock defines what runs, and thestepsblock defines the commands. - Jobs run in parallel by default. The
needskeyword creates a sequential dependency. Without it, jobs race. - Steps run sequentially within a job. If a step fails, the job stops unless
continue-on-erroris set. - Actions are reusable packages.
usesreferences an action from the marketplace or a local path.runexecutes a shell command. - Expressions and conditions control behavior.
if:gates a step, and${{ ... }}injects values from the context, secrets, or matrix. - Pinning action versions is essential.
@v4is stable;@latestis a moving target that can break unexpectedly.
Remember: A workflow is a script for the repository’s lifecycle. The trigger starts it, the jobs define the work, the steps execute the commands, and the actions provide the reusable pieces. The YAML is declarative: it describes what should happen, not how to do it. GitHub Actions handles the provisioning, the execution, and the reporting. The file is the only artifact.
Stop using slow, ad-bloated tool sites! đ¤Ž
đ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
â
Finance (Mortgage, Interest, Inflation)
â
Tech (Base64, JSON, Dev Suite, IP)
â
Health (BMI, BMR, TDEE)
â
Productivity (Timer, Workspace, QR)
âĄď¸ Fast & Private
đ No data leaves your device
đ 100% Free
đ Use it now: https://tools.kandz.me
đ Bookmark itâyouâll need it later!