Anatomy of a GitHub Actions Pipeline

First lesson of the Continuous Integration and Deployment course, free to read: about 15 minutes, 4 exercises and 4 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Explain what continuous integration and continuous deployment are
  • Read and write a workflow: triggers, jobs, steps, runners
  • Chain jobs with needs and protect the main branch with required checks

Continuous integrationGitHub Actions workflowTriggersBranch protection

Continuous integration (CI) means automatically checking every change pushed to the repository: installation, static analysis, tests. Continuous deployment (CD) extends the chain: a validated change is delivered automatically, to staging and then to production. The whole forms a pipeline. The goal is not speed for its own sake, but confidence: a problem is detected minutes after it is introduced, by a machine that never tires of rerunning the tests.

In this course, we use GitHub Actions, built into GitHub. The concepts (trigger, job, step, artifact, environment, secret) are found almost identically in GitLab CI, Azure Pipelines or Jenkins.

A first workflow

A workflow is a YAML file placed in the repository’s .github/workflows/ folder. The file name is free (ci.yml, deploy.yml).

# .github/workflows/ci.yml
name: CI                         # name displayed in the Actions tab

on:                              # when the workflow is triggered
  push:
    branches: [main]
  pull_request:

jobs:
  test:                          # job identifier
    runs-on: ubuntu-latest       # virtual machine provided by GitHub
    steps:
      - uses: actions/checkout@v4            # fetches the repository code
      - uses: actions/setup-node@v4          # installs Node.js
        with:
          node-version: 22
      - run: npm ci                          # shell command
      - run: npm test

Let’s read it from top to bottom:

  • on lists the triggers: here, every push to main and every pull request.
  • jobs contains one or more jobs. Each job starts on a fresh runner, a blank machine: nothing is kept from one job to another, except what you explicitly pass along (artifacts, outputs).
  • steps is the ordered list of steps. A step does either uses: (a reusable action, published in a repository, with its version after @), or run: (a shell command, run by bash on a Linux runner).
  • with: passes parameters to an action.

If a step fails (exit code other than 0), the following steps are skipped and the job is marked as failed. That is exactly what we want: a failing npm test turns the pipeline red.

Triggers

on:
  push:
    branches: [main]
    paths-ignore: ["docs/**", "**.md"]   # don't rerun for docs
  pull_request:
    branches: [main]                     # PRs targeting main
  workflow_dispatch:                     # "Run workflow" button
    inputs:
      environnement:
        type: choice
        options: [staging, production]
  schedule:
    - cron: "0 3 * * 1"                  # every Monday at 3:00 UTC
  • push: on every push, filterable by branches, tags and paths.
  • pull_request: when a pull request is opened and on each new commit. The code tested is the PR’s code merged with the target branch.
  • workflow_dispatch: manual launch from the Actions tab, with parameters (inputs) read via ${{ inputs.environnement }}.
  • schedule: scheduled execution, in cron format, in UTC.

Chaining jobs with needs

By default, a workflow’s jobs run in parallel. The needs keyword creates a dependency:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run lint
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test
  build:
    needs: [lint, test]          # waits for lint AND test to succeed
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

lint and test start together; build only starts if both are green. If one fails, build is marked “skipped”. The repository’s Actions tab displays this graph, each job’s status and each step’s log, which you expand to read the error.

Making CI mandatory

A red pipeline prevents nothing as long as you can merge despite it. In Settings > Branches (protection rule) or Settings > Rules > Rulesets, you protect main:

  1. require a pull request before merging (no more direct pushes);
  2. require the selected status checks to be green, for example the lint and test jobs;
  3. optionally require the branch to be up to date with main.

The names offered for the checks are those of the jobs: choose stable names, because renaming a required job blocks pull requests until the rule is updated.

Good habits from the start

  • Keep the pipeline fast (a few minutes): otherwise people stop waiting for its verdict.
  • Use npm ci (or pip install -r requirements.txt, composer install): reproducible installation from the lock file.
  • Add timeout-minutes to a job that may get stuck.
  • Check the syntax locally with a workflow linter such as actionlint before pushing.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. TaskDifficulty 11 hint

    First green workflow

    In a GitHub repository containing a Node.js project with a test script in package.json, create the file .github/workflows/ci.yml with name: CI, the triggers push on main and pull_request, and a job named test on ubuntu-latest that runs actions/checkout@v4, actions/setup-node@v4 (Node 22), npm ci then npm test. Push to main.

    • The file is located exactly at `.github/workflows/ci.yml`
    • The Actions tab shows a run of the "CI" workflow triggered by the push
    • The `test` job contains the steps checkout, setup-node, `npm ci` and `npm test`
    • The run ends with a green status (Success)
  2. TaskDifficulty 21 hint

    Chain lint, test and build

    Modify ci.yml to get three jobs: lint (npm run lint), test (npm test) and build (npm run build) with needs: [lint, test]. Then deliberately introduce a lint error in a branch essai-lint, open a pull request to main, observe, then fix it.

    • On main, the run graph shows `lint` and `test` in parallel then `build`
    • On the pull request with the error, `lint` is red and `build` is marked "skipped"
    • After the fix and a new push to the branch, the three jobs are green
    • No step is needlessly duplicated between jobs apart from checkout, setup-node and `npm ci`
  3. TaskDifficulty 21 hint

    Make CI mandatory on main

    In the repository settings, create a protection rule (or a ruleset) for main that requires a pull request and the lint and test status checks. Verify that a pull request whose test fails cannot be merged.

    • The rule targets the `main` branch
    • The required checks are exactly `lint` and `test`
    • On a pull request with a broken test, the merge button is disabled with a note that required checks failed
    • A direct push to main is rejected (or subject to the pull request)
  4. Multiple choiceDifficulty 11 hint

    Job execution order

    A workflow defines three jobs a, b and c, with no needs at all. How do they run?

    • In the order a, b, c
    • In parallel, each on its own runner
    • On the same runner, sharing files

End-of-lesson quiz

  1. Where must a GitHub Actions workflow file be located?

    • At the root of the repository
    • In .github/workflows/
    • In .actions/
  2. What is the difference between uses and run in a step?

    • None
    • uses calls a reusable action, run executes a shell command
    • run calls an action, uses a command
  3. Which trigger adds a manual launch button?

    • push
    • workflow_dispatch
    • schedule
  4. If the test job fails, what happens to a build job that has needs: test?

    • It runs anyway
    • It is skipped
    • It is rerun automatically

The rest of the course

  1. Tests and Quality in CI20 min
  2. Artifacts, Job Outputs and Releases15 min
  3. Automated Deployment and Environments20 min
  4. Secrets and Pipeline Security20 min

Continuous Integration and Deployment · Intermediate · 1 h 30 min