GitHub Actions CI/CD

Beginner · CI/CD · GitHub Actions

Build Software Like a Factory Assembly Line

Imagine a car factory: raw parts arrive, robots weld and paint, quality inspectors check every unit, and finished cars roll off the line. GitHub Actions is your software factory — it automatically builds, tests, and ships your code every time you push a change.

No prior CI/CD needed Real YAML examples Monitoring & metrics
01

Prerequisites

Before stepping onto the factory floor, make sure you have these tools and concepts in place. Don't worry — we'll explain everything else from scratch.

Required Tools

  • A GitHub account (free tier works)
  • Basic Git knowledge (clone, commit, push)
  • A code editor (VS Code recommended)
  • Optional: Node.js or Python installed locally

Helpful Concepts

  • What a repository (repo) is
  • Branches like main and pull requests
  • Reading simple YAML files (indentation matters!)
  • Command-line basics (npm test, etc.)
FACTORY READINESS CHECKLIST GitHub Account ✓ Git Basics Clone/Push ✓ Editor VS Code ✓ Ready! Start Line →
Each prerequisite is a station on the conveyor — complete them before the line starts moving.
02

What Is CI/CD?

CI/CD stands for Continuous Integration and Continuous Delivery/Deployment. Think of it as an automated factory line for your code.

Continuous Integration (CI)

Every time a developer pushes code, the factory automatically builds the project and runs tests. Broken code is caught immediately — like a quality inspector rejecting a defective part before it reaches the next station.

Continuous Delivery / Deployment (CD)

After tests pass, the factory can automatically package and ship your app to staging or production. Delivery means it's ready to deploy (with a human click); Deployment means it goes live automatically.

THE CI/CD ASSEMBLY LINE CodePush / PRDeveloper BuildCompileCI Step TestUnit + E2ECI Step DeployStaging/ProdCD Step MonitorLogs/AlertsFeedback ← CI ZONE → ← CD ZONE →
CI catches problems early; CD moves verified code to users automatically.
03

GitHub Actions Overview

GitHub Actions is GitHub's built-in automation platform. You write small recipe files (called workflows) that tell GitHub what to do when events happen — like pushing code, opening a pull request, or clicking a button.

TermFactory AnalogyWhat It Does
WorkflowEntire assembly line blueprintYAML file defining automation
EventStart button / alarm bellTrigger (push, PR, schedule…)
JobWork station on the lineGroup of steps that run together
StepSingle robot taskOne command or action
ActionPre-built tool attachmentReusable unit (from Marketplace)
RunnerThe factory floor machineServer that executes your jobs
GitHub Repository .github/workflows/ ActionsTab MarketplaceActions Runnersubuntu/win/mac
GitHub Actions lives inside your repo — workflows, the Actions tab, Marketplace tools, and runners all connect here.
04

Workflow Anatomy

Every workflow is a YAML file stored in .github/workflows/. YAML uses indentation (spaces, not tabs) to show structure — like an indented factory org chart.

.github/workflows/ci.yml
# Name shown in the Actions tab
name: CI Pipeline

on:                    # WHEN the line starts
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:                   # Work stations
  build-and-test:       # Job ID
    runs-on: ubuntu-latest  # Runner machine
    steps:                # Robot tasks
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test
WORKFLOW FILE STRUCTURE name + on (trigger) jobs → build-and-test runs-on: ubuntu-latest (runner) checkoutstep 1 setup-nodestep 2 npm cistep 3 npm teststep 4 Steps run top-to-bottom, sequentially, on the same runner
05

Writing Your First Workflow

Let's build a real workflow step by step. We'll create a simple CI pipeline for a Node.js project.

  1. 1

    Create the workflows folder

    In your repo root, create .github/workflows/ci.yml. The dot-folder is required — GitHub looks there automatically.

  2. 2

    Define the trigger

    Use on: push to run on every push, or limit to specific branches to save factory resources.

  3. 3

    Add checkout & setup steps

    Always start with actions/checkout@v4 to copy your code onto the runner. Then install your language runtime.

  4. 4

    Run your build & tests

    Use run: for shell commands. If any step fails, the whole job stops — the red light turns on.

  5. 5

    Commit, push, and watch

    Go to your repo's Actions tab. You'll see a yellow (running), green (success), or red (failure) indicator for each run.

Complete first workflow example
name: My First CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test
      - run: npm run build
06

Jobs, Steps & Runners

Jobs run in parallel by default

Multiple jobs are like parallel assembly lines. Use needs: to make one job wait for another — creating a dependency chain.

jobs:
  lint:
    runs-on: ubuntu-latest
    steps: [...]
  test:
    needs: lint          # waits for lint to finish
    runs-on: ubuntu-latest
    steps: [...]
  deploy:
    needs: test          # waits for test
    runs-on: ubuntu-latest
    steps: [...]

Matrix builds — test every combination

A strategy.matrix runs the same job on multiple OS versions or Node versions simultaneously — like running five quality checks at once.

strategy:
  matrix:
    node-version: [18, 20, 22]
    os: [ubuntu-latest, windows-latest]
steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}

Runners: GitHub-hosted vs self-hosted

GitHub-hosted runners are free virtual machines (with monthly minute limits). Self-hosted runners are your own servers — useful for private networks, GPUs, or special hardware.

JOB DEPENDENCY GRAPH (needs:) lint test deploy parallel possible security-scan needs: lint
07

Secrets, Variables & Environments

Your factory handles sensitive materials — API keys, passwords, tokens. Never put these in your workflow file. GitHub provides a secure vault.

Secrets

Encrypted values. Set in Settings → Secrets and variables → Actions. Access via ${{ secrets.MY_KEY }}. Never logged in plain text.

Variables

Non-sensitive config like region names. Access via ${{ vars.APP_ENV }}. Can be repo, org, or environment scoped.

Environments

Named targets like production with protection rules — required reviewers, wait timers, branch restrictions.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production    # triggers protection rules
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to cloud
        env:
          API_KEY: ${{ secrets.DEPLOY_API_KEY }}
          REGION:  ${{ vars.AWS_REGION }}
        run: ./deploy.sh
GitHub Secrets Encrypted vault Workflow ${{ secrets.* }} Deploy Script env vars only Secrets are injected at runtime — never appear in logs
08

Monitoring Success & Failure

Every factory needs dashboards and alarm bells. GitHub Actions gives you multiple ways to gauge whether your pipeline is healthy.

Built-in GitHub UI

  • Actions tab — see all workflow runs with ✅ success, ❌ failure, 🟡 in-progress, or ⏸ cancelled status.
  • Job logs — click any run to expand steps and read stdout/stderr output line by line.
  • Annotations — tools like linters can attach inline comments on the exact file and line that failed.
  • Workflow badges — embed a status shield in your README: ![CI](https://github.com/user/repo/actions/workflows/ci.yml/badge.svg)

Programmatic Access & Metrics

GitHub REST API

Query workflow runs, jobs, and conclusions programmatically:

GET /repos/{owner}/{repo}/actions/runs
GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs

# Key fields in response:
# status: "completed" | "in_progress" | "queued"
# conclusion: "success" | "failure" | "cancelled" | "skipped"

Workflow Commands & Outputs

Set outputs and mark steps explicitly inside a workflow:

- name: Set result
  id: check
  run: echo "passed=true" >> $GITHUB_OUTPUT

- name: Fail with message
  run: echo "::error file=app.js,line=10::Tests failed"

- name: Summary report
  run: echo "## Test Results" >> $GITHUB_STEP_SUMMARY

Third-Party Observability

Send metrics to Datadog, Grafana, Sentry, or Slack via actions. Track pass rate, duration (P50/P95), flaky test rate, and MTTR (mean time to recovery).

CI/CD OBSERVABILITY STACK GitHub Actions Run (source of truth) Actions Tab UILogs, badges, annotations REST / GraphQL APIAutomated dashboards Alerts & WebhooksSlack, PagerDuty, email Key metrics: success rate · build duration · failure frequency · queue time
09

Best Practices & Common Patterns

Pin action versions

Use @v4 or commit SHAs instead of @main to prevent surprise breaking changes.

Cache dependencies

Use actions/cache or built-in cache options to speed up builds — less idle conveyor belt time.

Fail fast with path filters

Use paths: in triggers so docs-only changes don't run the full test suite.

Use concurrency groups

Cancel outdated runs on the same PR to save minutes: concurrency: group: ${{ github.ref }}.

Reusable workflows

Extract shared pipelines into workflow_call workflows — one blueprint, many repos.

Reusable workflow pattern
# .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
10

Real-World Examples & Troubleshooting

Example: Python + Docker Deploy

name: Python CI/CD

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - run: pytest

  docker:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: myapp:latest

Common Failures & Fixes

SymptomLikely CauseFix
Workflow not triggeringWrong branch or path filterCheck on: block matches your push
Permission deniedMissing permissions:Add contents: write if pushing tags
Secret not foundTypo or wrong scopeVerify name in Settings → Secrets
YAML syntax errorTabs or bad indentationUse spaces only; validate with actionlint
Out of minutesFree tier limit reachedOptimize caching, use path filters, or upgrade plan
TROUBLESHOOTING DECISION TREE Run Failed? Check Actions logs No run at all? Fix failing step Check on: trigger ✅ Green build ✅ Green build

You're Ready for the Factory Floor!

You've learned CI/CD fundamentals, workflow anatomy, secrets, monitoring, and troubleshooting. Push your first workflow and watch the assembly line run.