Introduction to GitHub Actions

By Vishvesh Patel · DevOps EngineerUpdated September 28, 2026

What GitHub Actions is and how it runs your build. Workflows, jobs, steps and runners explained, with a first working pipeline you can commit today.

GitHub Actions is the CI/CD system built into GitHub. It runs automated jobs in response to repository events: you commit a YAML file describing what should happen, and GitHub provisions a machine, checks out your code and runs your commands. There is no separate server to install, no runner to register before you can see something work, and no second set of credentials to manage. If your code already lives on GitHub, you are one file away from a working pipeline.

Verified against GitHub Actions as of September 2026. Action versions in this series are pinned to major tags that are current at that date.

Why It Exists

Before CI became normal, "does it build" was answered on a developer's laptop. The laptop had the right Node version, the right environment variables and a node_modules directory that had accumulated over months. The build server, when a team eventually bought one, had none of those things, and the gap between the two is where most release incidents lived.

CI closes that gap by building on a machine that starts empty every time. GitHub Actions does this with a runner: a fresh virtual machine that exists for the length of one job and is destroyed afterwards. Nothing carries over between runs unless you explicitly cache or upload it, which is inconvenient for about a week and then becomes the reason you trust the result.

The Four Concepts

Almost everything in GitHub Actions is one of four things, and the rest of this series assumes them.

A workflow is a YAML file in .github/workflows/. One file, one workflow. A repository can have many, and they run independently.

An event is what starts a workflow: a push, a pull request, a schedule, a manual click, a release being published. A workflow declares which events it responds to in its on: block.

A job is a set of steps that run on one runner. Jobs in a workflow run in parallel by default, which is a common surprise. If job B needs job A to finish first, you say so with needs:.

A step is a single unit of work inside a job. A step either runs a shell command with run:, or invokes a reusable action with uses:.

Your First Workflow

Create .github/workflows/ci.yml:

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'
          cache: 'npm'

      - run: npm ci
      - run: npm test

Commit that and push. Open the Actions tab and you will see the run.

Four details in that file are worth understanding rather than copying.

actions/checkout@v4 is not optional. The runner starts with no copy of your code. Without this step your repository is simply not there, and the first npm ci fails with a missing package.json. This is the single most common first-workflow mistake.

runs-on: ubuntu-latest picks the runner image. GitHub also provides windows-latest and macos-latest, and macOS minutes are billed at a much higher multiplier than Linux on paid plans, so reach for Linux unless you genuinely need Xcode.

cache: 'npm' on setup-node is the easiest performance win available. It caches the npm download cache keyed on your lockfile, with no separate cache step to configure.

npm ci, not npm install. npm ci installs exactly what the lockfile specifies and fails if package.json and the lockfile disagree. npm install will happily resolve something new, which means your CI can pass against dependencies your teammates never had.

How This Differs from GitLab CI and Jenkins

If you have used GitLab CI/CD, the mental model transfers directly: GitLab's stages and jobs map onto GitHub's jobs and needs:. The biggest practical difference is that GitLab organises a pipeline into named stages that run in sequence, while GitHub Actions has no stage concept at all. Ordering comes only from dependencies you declare.

Coming from Jenkins, the difference is ownership. A Jenkinsfile describes work that a server you maintain will perform. A GitHub Actions workflow describes work that GitHub performs on infrastructure you do not see. You trade control for having nothing to patch.

Permissions, Before You Go Further

Every workflow run gets an automatic GITHUB_TOKEN. For repositories created since 2023 its default permission is read-only, which is the right default and also the cause of a lot of confused debugging when a workflow tries to push a tag or publish a package.

Grant only what a job needs, at the smallest scope:

permissions:
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

A top-level permissions: block sets the default for every job. A job-level block overrides it for that job only. Setting contents: read at the top and widening per job is the pattern worth adopting from the start, because narrowing it later means auditing every workflow you have written.

Stopping Duplicate Runs

By default, pushing three times in a minute starts three runs, and all three keep going. On a busy pull request that wastes minutes and gives you stale results.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

This groups runs by workflow and branch, and cancels an in-progress run when a newer commit arrives. It is two lines and it is worth adding to almost every workflow.

What to Learn Next

The rest of this series builds on this file. Workflow syntax and event filtering come next, then secrets, caching, artifacts, matrix builds, container images and finally deploying to AWS without storing long-lived keys.

If a workflow of yours is failing right now, the DevOps troubleshooting section covers the specific CI errors that come up most, including workflows that never trigger and OIDC role assumption failures.

Frequently Asked Questions

Is GitHub Actions free?

Public repositories get unlimited free minutes on GitHub-hosted runners. Private repositories get a monthly allowance that depends on your plan, after which minutes are billed. Linux runners consume minutes at the base rate, Windows at twice that and macOS at ten times, so runner choice has a direct cost effect on private repositories.

Where do workflow files have to live?

In .github/workflows/ at the root of the repository, with a .yml or .yaml extension. GitHub does not discover workflows anywhere else. A file in .github/ directly, or nested a level deeper, is silently ignored, which is a frequent cause of a workflow that appears to do nothing.

Do jobs in a workflow run in order?

No. Jobs run in parallel unless you declare a dependency with needs:. Steps within a single job always run in order, and the job stops at the first failing step unless that step sets continue-on-error: true.

What is the difference between uses and run?

run executes a shell command on the runner. uses invokes a packaged action, which is someone else's reusable code, referenced by repository and version, for example actions/checkout@v4. Prefer run for anything simple enough to be one command, and an action where it wraps real complexity such as authentication or caching.

Can I test a workflow without pushing to main?

Yes, and you should. Add pull_request: to the on: block and open a draft pull request from a branch. For manual testing add workflow_dispatch:, which puts a "Run workflow" button in the Actions tab. Local simulators exist but they approximate the runner environment rather than reproducing it.

Continue Learning

Explore Related Topics

Try the Tool

When it breaks

Related Resources