GitHub Actions CI Pipeline Setup: Step-by-Step Guide

Continuous integration (CI) means every push and pull request automatically gets built and tested, so bugs surface in minutes instead of during a release scramble. GitHub Actions is the built-in way to do this on GitHub—no external service to wire up, no extra account. This guide walks through a complete GitHub Actions CI pipeline setup, from your first workflow file to caching, matrix builds, and a status badge.
What you're building
A GitHub Actions pipeline is defined in a plain text configuration file. A workflow is a configurable automated process that will run one or more jobs, defined by a YAML file checked in to your repository, and it runs when triggered by an event in your repository, or can be triggered manually or on a defined schedule. The moving parts are worth knowing before you write anything:
- Workflow — the whole automated process, one YAML file.
- Event — what triggers it (a push, a pull request, a schedule).
- Job — a set of steps that run together on one machine.
- Step — a single command or a reusable action.
A workflow consists of one or more jobs, each of which will execute on a runner machine and run a series of one or more steps, and each step can either run a script that you define or run an action, which is a reusable extension.
Step 1: Create the workflow file in the right place
GitHub only discovers workflows in one specific location. For GitHub to discover any GitHub Actions workflows in your repository, you must save the workflow files in a directory called .github/workflows, and you can give the workflow file any name you like, but you must use .yml or .yaml as the file name extension.
Create the folder and a file—say .github/workflows/ci.yml—at the root of your repo. You can do this locally and commit it, or create it directly in the GitHub web UI. If the Actions tab doesn't appear on your repository, Actions may be disabled in your repo settings.
Step 2: Choose your triggers
The on key controls when the pipeline runs. For CI you almost always want it to run on pushes and on pull requests, so code is validated both when it lands on a branch and when someone proposes a merge. Workflows are triggered by GitHub events such as push and pull_request, and you can specify multiple events.
A basic starting point:
- name: CI
- on: push (branches: main) and pull_request (branches: main)
The name is cosmetic but useful. GitHub displays the names of your workflows on your repository's actions page, and if you omit name, GitHub sets it to the YAML file name.
Step 3: Define a job and pick a runner
Jobs run on GitHub-hosted virtual machines. Each job runs in a fresh instance of the virtual environment specified by runs-on. For most projects, ubuntu-latest is the fastest and cheapest choice.
Here's a job that checks out your code and sets up Node.js—using the current major versions of GitHub's official actions:
- runs-on: ubuntu-latest
- uses: actions/checkout@v6
- uses: actions/setup-node@v7 with node-version: '24'
- run: npm ci
- run: npm test
The uses keyword pulls in a prebuilt action. The uses keyword specifies that a step will run the actions/checkout action, which copies your repository onto the runner so the rest of the steps have your code to work with. The run steps execute shell commands—here, a clean install of dependencies followed by your test suite. Swap npm commands for your stack's equivalents (pip, Maven, Go, and so on); the structure stays the same. If you're building container-based workflows, our comparison of Kubernetes vs Docker Swarm covers where these pipelines often deploy to next.
Step 4: Commit and watch it run
Commit the file and push. GitHub picks it up immediately. GitHub searches the .github/workflows directory in the root of your repository for workflow files present in the associated commit SHA or Git ref of the event, and a workflow run is triggered for any workflows that have on values that match the triggering event.
Open the Actions tab to see the run. Click the job, then expand any step to read its logs. This is where you'll debug failures—a red X on a step tells you exactly which command broke.
Step 5: Cache dependencies to speed things up
Reinstalling packages from scratch on every run is slow. The setup-node action can cache your package manager's downloads for you. The official setup-node action supports caching by using actions/cache under the hood, abstracting out the setup required to cache the required package manager cache directories, and it supports caching for npm, yarn, and pnpm with the cache input.
Add one line to the setup step:
- uses: actions/setup-node@v7 with node-version: '24' and cache: 'npm'
Caching works best when you have a lock file (like package-lock.json) committed, since the cache key is derived from it. On later runs, unchanged dependencies restore from cache instead of downloading again.
Step 6: Test across multiple versions with a matrix
If your project needs to work on several language versions, don't copy-paste jobs—use a matrix. By default, all jobs in a workflow run in parallel. A matrix fans a single job out into parallel variants:
- strategy: matrix: node-version: ['20', '22', '24']
- uses: actions/checkout@v6
- uses: actions/setup-node@v7 with node-version: ${{ matrix.node-version }} and cache: 'npm'
- run: npm ci
- run: npm test
This runs your test suite three times in parallel—one per Node version—so you catch version-specific breakage before it ships.
Step 7: Order jobs when you need to
Sometimes stages must run in sequence: lint and test first, then build, then deploy. A workflow run is made up of one or more jobs, which run in parallel by default, and to run jobs sequentially, you can define dependencies on other jobs using the jobs.<job_id>.needs keyword.
- test job: runs-on ubuntu-latest, runs your tests
- deploy job: needs: test, runs only if tests pass
The deploy job waits for test to succeed. If tests fail, deploy never starts.
Step 8: Add a status badge
A badge in your README shows at a glance whether the main branch is passing. You can build the URL for a workflow status badge using the name of the workflow file: https://github.com/OWNER/REPOSITORY/actions/workflows/WORKFLOW-FILE/badge.svg
In Markdown that's:

Replace OWNER, REPO, and the workflow filename with yours.
A note on cost
For open source, this is essentially free. GitHub Actions usage is free for standard GitHub-hosted runners in public repositories. Private repositories draw from a monthly allowance tied to your plan. For private repositories, each GitHub account receives a quota of free minutes and storage for use with GitHub-hosted runners depending on the account's plan, and any usage beyond the included amounts is billed to your account. Caching and running only the jobs you need keeps you comfortably inside those limits.
Wrapping up
You now have the full pattern: put a YAML file in .github/workflows, trigger it on pushes and pull requests, run your build and tests on a hosted runner, then layer on caching, a version matrix, job ordering, and a badge. Start with the minimal ci.yml, get it green, and add pieces one at a time—each step above works on its own, so you can grow the pipeline as your project does. For more guides like this, browse our Dev Tools coverage.
