TL;DR:

  • Act (github.com/nektos/act) runs GitHub Actions workflows locally using Docker — test your CI changes without pushing commits or waiting for GitHub’s runners
  • act push simulates a push event; act pull_request simulates a PR; act -j build runs a specific job
  • Secrets and environment variables go in a .secrets file or via --secret-file; the main limitation is that Act doesn’t replicate every GitHub-specific API perfectly

The feedback loop for GitHub Actions development is frustrating. You write a workflow, push a commit, wait 30–90 seconds for a runner to start, and then find out your syntax was wrong or an environment variable wasn’t set. Repeat. By the time you’ve got a working workflow, you’ve pushed a dozen “fix CI” commits and burned through your Actions minutes.

Act solves this by running your workflows locally in Docker containers that mirror the GitHub Actions runner environment. You get the same YAML parsing, the same execution order, the same environment variables — without leaving your terminal.

Installing Act

The simplest installation path on macOS and Linux:

# macOS
brew install act

# Linux (curl installer)
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash

# Windows (winget)
winget install nektos.act

Go binary releases are also available on the GitHub releases page if you prefer not to use a package manager.

Act requires Docker — it needs a Docker daemon running locally to pull and run the runner images. Docker Desktop (macOS/Windows) or the Docker Engine on Linux both work fine.

On first run, Act asks which runner size to use. The “Medium” option (node:16-buster-slim) is a good starting point — it’s smaller and faster to pull than the full GitHub-hosted runner image but covers most common workflow needs. If you’re using actions/setup-node, actions/setup-python, or similar setup actions, you’ll want the “Large” image which includes more pre-installed tooling.

Basic Usage

# Run all workflows triggered by a push event
act push

# Run all workflows triggered by a pull_request event
act pull_request

# Run a specific job
act push -j build

# Run a specific workflow file
act push -W .github/workflows/ci.yml

# List all available workflows and jobs (doesn't run anything)
act -l

Act infers the event type from the workflow’s on: triggers. If your workflow triggers on push, act push runs it. If it triggers on pull_request, act pull_request runs it.

Running act with no arguments defaults to push.

Handling Secrets and Environment Variables

GitHub Actions secrets aren’t available locally, so you need to supply them separately. The recommended approach is a .secrets file at your project root:

GITHUB_TOKEN=ghp_your_personal_access_token
AWS_ACCESS_KEY_ID=your_key_id
AWS_SECRET_ACCESS_KEY=your_secret_key
NPM_TOKEN=your_npm_token

Then pass it to act:

act push --secret-file .secrets

Make absolutely sure .secrets is in your .gitignore — it contains real credentials and must never be committed.

For environment variables (as opposed to secrets), use --env-file with a similar format.

You can also pass individual secrets inline:

act push --secret GITHUB_TOKEN=ghp_yourtoken

.actrc for Persistent Configuration

Typing the same flags every run gets old. Create a .actrc file in your project root or home directory to set defaults:

-P ubuntu-latest=catthehacker/ubuntu:act-22.04
--secret-file .secrets
--env-file .env

The -P flag overrides the runner image used for a given runner label. The catthehacker/ubuntu:act-22.04 images are maintained specifically for Act and are much smaller than the full GitHub runner images while being more complete than the minimal defaults.

Practical Example: Testing a Node.js CI Workflow

Given a workflow like:

name: CI
on: [push, pull_request]

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

Running act push will:

  1. Pull the runner image (once, cached after first run)
  2. Execute actions/checkout@v4 — this copies your local working directory into the container
  3. Execute actions/setup-node@v4 — installs Node 20 in the container
  4. Run npm ci and npm test inside the container

Output appears in your terminal as the steps execute, with the same green tick / red X formatting you see in the GitHub UI.

Where Act Falls Short

Act isn’t a perfect replica of GitHub’s runners. A few things to be aware of:

GitHub-specific APIs and contexts${{ github.sha }}, ${{ github.ref }}, and similar expressions are populated by Act with plausible values, but not the same values GitHub would use. Workflows that make API calls to GitHub (checking PR status, creating releases, commenting on issues) need real GitHub credentials and may behave differently locally.

Runner pre-installed software — GitHub’s hosted runners come with hundreds of pre-installed tools (language runtimes, build tools, cloud CLIs). The Act runner images don’t include all of these. If your workflow assumes terraform or kubectl is available without an install step, it will fail locally.

Docker-in-Docker — workflows that build Docker images or run containers within steps can be tricky. Act itself runs in Docker, so Docker-in-Docker configuration is needed. It’s possible but adds complexity.

Platform-specific runners — Act only supports Linux containers. Workflows using windows-latest or macos-latest runners can’t be replicated locally with Act.

For most standard workflows — lint, test, build, and deploy steps on ubuntu-latest — Act covers the important cases and eliminates the push-and-wait cycle for the vast majority of CI development work.

The Workflow Development Loop

The practical payoff of Act is being able to iterate on your CI configuration in seconds rather than minutes. Syntax errors, missing environment variables, incorrect step ordering — all of these are visible immediately in your terminal without a GitHub push. By the time you do push, the workflow is already working locally, and the CI run is a confirmation rather than a debugging session.

For projects with longer CI pipelines, Act’s -j flag to run a single job is particularly useful — you can develop and test the failing job in isolation without executing the full pipeline on every iteration.