Skip to main content

How to build a GitHub Actions pipeline for a web app

Build a GitHub Actions CI workflow that lints, tests, and builds an app on push and pull request. Covers triggers, runtime setup, dependency caching, and verifying runs.

Difficulty
Beginner
Duration
40 minutes
Steps
6

What and why

GitHub Actions runs automated workflows directly inside your repository, triggered by events such as pushes and pull requests. A continuous integration (CI) pipeline is the most common use: it checks every change automatically so defects surface before they reach the main branch. Instead of relying on each developer to remember to run the linter and tests, the pipeline enforces a consistent quality bar for everyone.

This tutorial builds a practical workflow that lints, tests, and builds an application on each push and pull request. The same structure applies to almost any language; only the runtime setup and commands change. By the end you will have a green check on every pull request and fast feedback when something breaks.

Prerequisites

  • A GitHub repository you can push to.
  • An application that exposes lint, test, and build commands.
  • Basic familiarity with YAML indentation, which is significant in these files.

Steps

1. Create the workflow file

Workflows live under the .github/workflows directory. Create a file named .github/workflows/ci.yml. The file name is arbitrary, but the directory path is fixed; GitHub only discovers workflows stored there. You can have many workflow files, each handling a different concern such as CI, releases, or scheduled jobs.

2. Define triggers

Tell Actions when to run by listing events under on:

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

This configuration runs the workflow on direct pushes to main and on every pull request, regardless of its target branch. Running on pull requests is what gives reviewers a status check before merge; running on main catches anything that slips through.

3. Set up the runtime

Define a job on a hosted runner, check out the code, and install the language runtime:

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

The checkout action pulls your repository into the runner. The setup-node action installs the requested runtime version and, with cache: 'npm', enables built-in dependency caching so repeat runs are faster. Pin action versions to a major tag so behavior is predictable.

4. Install dependencies with caching

      - run: npm ci

Use npm ci rather than npm install in CI. It installs exactly what the lockfile specifies, never updates the lockfile, and fails fast if the lockfile and manifest disagree. This makes builds reproducible: the same commit always installs the same dependency tree.

5. Run lint, test, and build

      - run: npm run lint
      - run: npm test
      - run: npm run build

Each run step executes in order in the same working directory. If any command exits with a non-zero status, the step fails, the job fails, and later steps are skipped. Ordering matters: linting first gives the cheapest, fastest feedback, while the build runs last because it is usually the slowest step.

Verification

Commit and push the workflow file, then open the Actions tab in GitHub. You should see the CI workflow running against your push. Next, open a pull request and confirm the checks appear directly on it. A green check means every step passed. If a step fails, click into the run, expand the failed step, and read its log; the exact command output appears there, which is usually enough to diagnose the problem.

To confirm the gate works, push a deliberate lint error on a branch and open a pull request. The check should turn red and the failed step should point at the offending file.

Next Steps

Once the basic pipeline is green, split lint, test, and build into separate jobs so they run in parallel and you see all failures at once. Add a build matrix to test multiple runtime versions. Move shared logic into a reusable workflow so other repositories can call it instead of copying YAML. Finally, require the check to pass in branch protection so no pull request can merge while CI is red.

Prerequisites

  • A GitHub repository
  • An app with test and build scripts
  • Basic YAML familiarity

Steps

  • 1
    Create the workflow file
  • 2
    Define triggers
  • 3
    Set up the runtime
  • 4
    Install dependencies with caching
  • 5
    Run lint, test, and build
  • 6
    Verify the run

Category

CI/CD