act: Run GitHub Actions Locally Without Pushing to GitHub
If you've ever pushed a commit just to test a GitHub Actions workflow change, you know the pain: wait for a runner, watch it fail on line 3 of your YAML, fix it, push again, repeat. act breaks this loop by running your workflows locally in Docker containers that mirror the GitHub environment.
Photo by Laine Cooper on Unsplash
What act Does
act reads your .github/workflows/*.yml files and runs them locally. It pulls the same runner images GitHub uses (or lightweight alternatives), sets up the same environment variables, and executes your steps using the same action syntax.
The result: a tight feedback loop. A workflow that would take 5 minutes on GitHub to test a 3-line YAML change can be verified in seconds locally.
Installation
The fastest way on macOS is Homebrew:
brew install act
On Linux, use the install script:
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
Or install via GitHub releases directly. If you use mise or asdf, act is available there too:
mise use act
Basic Usage
Run all workflows triggered by the push event:
act push
Run a specific workflow by name:
act push -W .github/workflows/ci.yml
Run a specific job within a workflow:
act push -j build
List all available workflows and jobs without running them:
act -l
Runner Images
The first time you run act, it asks which runner size to use:
- Micro (~200MB): Minimal image, fastest to pull. Missing many common tools.
- Medium (~500MB): Most common tools included. Good starting point.
- Large (~17GB): Closest match to actual GitHub runners. Slowest download.
For most CI pipelines, the medium image works fine. For workflows that depend on pre-installed tools like specific Python versions or system packages, you may need the large image.
You can set a default in ~/.config/act/actrc:
-P ubuntu-latest=catthehacker/ubuntu:act-latest
Or specify per-run with -P:
act -P ubuntu-latest=catthehacker/ubuntu:act-latest push
Secrets and Environment Variables
Workflows that reference ${{ secrets.MY_SECRET }} need values locally. Pass them with --secret:
act push --secret MY_SECRET=value
Or use a .secrets file (similar to .env):
MY_SECRET=value
ANOTHER_SECRET=other-value
act push --secret-file .secrets
Add .secrets to your .gitignore. Never commit real credentials.
For environment variables (not secrets), use --env or a .env file:
act push --env NODE_ENV=test
Handling Common Failures
Actions that require GitHub tokens: Some actions need GITHUB_TOKEN for API access. Pass a personal access token:
act push -s GITHUB_TOKEN=ghp_yourtoken
Platform mismatches: act runs Docker containers on your host, so runs-on: macos-latest won't work on Linux. You can override the runner mapping for platform-specific jobs:
act push -P macos-latest=ubuntu-latest
This lets the workflow run on an Ubuntu container even if it specifies macOS. Jobs with macOS-specific commands will fail, but you can still test the platform-agnostic steps.
Docker-in-Docker: Workflows that build and push Docker images need privileged mode. Enable it with --privileged:
act push --privileged
github.event context: Local runs don't have a real GitHub event payload. act injects a synthetic payload, but some workflows inspect github.event.pull_request or similar fields. You can provide a custom event JSON:
act pull_request -e event.json
Where event.json contains a mock payload matching the event schema.
Workflow for Debugging CI
A typical debugging session looks like this:
- A workflow fails on GitHub. You look at the logs, spot the issue.
- Edit the workflow YAML locally.
- Run
act push -j failing-jobto test the fix. - Iterate until act shows green.
- Push the fix. One commit, one verify.
This is dramatically faster than the push-wait-read-push cycle, especially for multi-step workflows where failures happen deep in the pipeline.
Caching and Artifacts
act supports the actions/cache action with a local cache directory. Enable it by mounting a cache path:
act push --artifact-server-path /tmp/act-artifacts
For actions/upload-artifact and actions/download-artifact, act runs a local artifact server. Files are saved to the specified path instead of GitHub's artifact storage.
Limitations
act is excellent for most workflows but has real gaps:
- GitHub-hosted services: Workflows that use
services:(e.g.,postgres,redis) work via Docker networking, but the setup is sometimes fiddly. - Reusable workflows:
workflow_callsupport is partial — complex multi-repo reuse patterns may not work. - Matrix builds:
strategy.matrixworks but runs sequentially by default. Use--matrixto target a specific combination. - GITHUB_OUTPUT / GITHUB_ENV: Modern step output syntax works, but some older patterns may behave differently.
- GitHub Apps and installation tokens: Not available locally; you'll need to mock or skip steps that use them.
For these cases, a short "sanity run" with act still catches most issues before the real push. Save the platform-specific testing for GitHub.
Practical Configuration
Keep a .actrc in your repo root for project-specific defaults:
-P ubuntu-latest=catthehacker/ubuntu:act-22.04
--secret-file .secrets
--env-file .env.test
Now running act push automatically uses your preferred image and picks up local secrets without extra flags.
When to Use act
- Iterating on CI configuration: Any time you're modifying workflow YAML, act pays for itself immediately.
- Onboarding new contributors: New contributors can verify their environment runs clean before opening a PR.
- Testing new actions: Trying out a third-party action? Verify it works before making it part of your real pipeline.
- Offline development: No GitHub access needed — act runs entirely locally.
If your team pushes "fix CI" commits more than once per change, act is worth the 20 minutes it takes to set up. The feedback loop improvement compounds across every CI iteration from that point forward.
