act: run GitHub Actions locally and stop the fix-ci commit loop
TL;DR
act reads .github/workflows and runs jobs in Docker containers on your machine. act -l lists, act -j runs one job, -s or --secret-file passes secrets. It is not identical to GitHub, so guard deploy steps with the ACT variable.
The familiar way to fix GitHub Actions: edit the YAML, commit "fix ci", push, wait a few minutes, see it fail, repeat. Your history fills with "fix ci 2", "fix ci 3", and each loop costs minutes. act runs GitHub Actions workflows on your own machine: it reads .github/workflows/, spins up Docker containers matching the runners, and executes the steps as GitHub would. The feedback loop drops from minutes to seconds.

Installation
brew install act # macOS/Linux
gh extension install nektos/gh-act # use it as "gh act"
act needs Docker running. On first run, act asks you to pick a default runner image: Micro (small, missing many tools), Medium (balanced, enough for most) or Large (closest to GitHub's runners but tens of GB). The choice is saved in an .actrc file.
Basic usage
act -l # list every job and its trigger events
act # run workflows triggered by push
act pull_request # simulate a pull_request event
act -j test # run only the job named "test"
act -W .github/workflows/ci.yml # run only one workflow file
act -n # dry run: show what would happen
Secrets, variables and events
| Need | How |
|---|---|
| Pass one secret | act -s GITHUB_TOKEN="$(gh auth token)" |
| Many secrets from a file | act --secret-file .secrets (.env format; add it to .gitignore) |
vars.* variables | act --var ENV=staging or --var-file |
| A specific event payload | act pull_request -e event.json with JSON mimicking a PR |
| Different image for a runner | act -P ubuntu-latest=catthehacker/ubuntu:act-latest |
| Apple Silicon Macs | act --container-architecture linux/amd64 if you hit architecture errors |
An example workflow
# 1. See which jobs exist
act -l
# 2. Run just the lint job while editing it
act -j lint
# 3. Run the full PR CI with a real token
act pull_request -s GITHUB_TOKEN="$(gh auth token)"
# 4. Push only once it's green locally
Limits to know
- Containers aren't identical to GitHub runners: preinstalled tools, permissions and OS differ. macOS and Windows runners aren't emulated as fully as Linux.
- Features tied to GitHub's infrastructure (OIDC, cache, artifacts, some GitHub-only actions) may not work or need extra setup.
- A real deploy step really deploys if you pass real secrets. Use a condition like
if: ${{ !env.ACT }}to skip dangerous steps under act (act sets theACTenvironment variable). - The Large image is huge; start with Medium and switch only if tools are missing.
How to apply it
- Dev: When editing a workflow, run
act -j <job>before pushing. Fewer "fix ci" commits, fewer CI minutes burned. - Tech Lead: Add act instructions to CONTRIBUTING and guard deploy steps with the
ACTcondition so local runs are safe. - PM: A shorter CI feedback loop means pipelines get fixed faster, with less waiting and fewer billed GitHub Actions minutes.
Sources & references
- nektos/act — nektos (GitHub)