Test your GitHub Actions workflows locally with act. Installation, configuration, secrets, debugging and good practices for fast iterative development.
The issue opens pre-filled with this page title and URL.
Test your GitHub Actions workflows in seconds, without pushing to GitHub. act
runs your workflows locally through Docker, which allows fast iterative
development. You save your Actions minutes and avoid the frustrating push, wait,
fail, fix, push loop.
act reads your YAML files in .github/workflows/, creates Docker containers imitating the GitHub runners, and runs the jobs as if they were running on
GitHub. The results (logs, artifacts, status) are printed locally.
What you will learn
Install act on Linux, macOS and Windows
Run workflows with different events (push, PR, workflow_dispatch)
Handle secrets and environment variables
Configure the optimal Docker images
Debug efficiently with the advanced options
Prerequisites
Before installing act, you need:
Docker installed and running (act creates containers)
A terminal (bash, zsh, PowerShell)
A project with workflows in .github/workflows/
To check that Docker works:
Terminal window
dockerversion
Installation
act is a static Go binary with no system dependency: installing it comes down
to fetching an executable and putting it in the PATH. The package managers
below do that work for you and handle the updates; the manual method is mostly
useful when you have to pin a precise version, so that a whole team tests
with the same behaviour for instance.
# Must print "act_Linux_x86_64.tar.gz: OK" before any extraction
sha256sum--check--ignore-missingchecksums.txt
tar-xzfact_Linux_x86_64.tar.gzact
sudoinstall-m0755act/usr/local/bin/act
The checksums.txt file is published by the release pipeline of act next to the
archives. If sha256sum --check does not answer OK, stop there: the
archive has been altered or the download is incomplete.
With Chocolatey:
Terminal window
choco install act-cli
With Scoop:
Terminal window
scoop install act
Checking the installation:
Terminal window
act--version
Expected output
act version 0.2.89
The first run
On the first run, act asks which Docker image to use to simulate the GitHub
runners. Three options are offered:
Image
Size
Compatibility
Micro (~200 MB)
Very light
Limited (many tools are missing)
Medium (~500 MB)
Balanced
Good for most cases
Large (~18 GB)
Complete
Close to the GitHub environment
Swipe to see more
To start with, pick Medium. You can change later.
Terminal window
# First run, pick Medium
act
act saves your choice into ~/.actrc for the next runs.
Basic usage
Running the default workflow
The act command with no argument simulates a push event and runs the
workflows answering that event:
Terminal window
act
Running one specific workflow
The -W option expects a path, not the name: declared in the file. Point
it at a precise file to run only that one, or at a directory to narrow the search
down to a subset of workflows. Without -W, act sweeps the whole
.github/workflows/, which quickly becomes annoying on a repository holding a
dozen of them.
Terminal window
# By file path
act-W.github/workflows/ci.yml
# By directory: every workflow of a folder
act-W.github/workflows/
Running one specific job
If your workflow holds several jobs, you can run just one:
The icons show the status: a star for the step in progress, a tick for success, a
cross for failure.
Terminal window
# Run only the "build" job of the ci.yml workflow
act-W.github/workflows/ci.yml-jbuild
Simulating the different events
GitHub Actions triggers on different events. act can simulate them:
Terminal window
# Simulate a push (the default)
actpush
# Simulate a pull request
actpull_request
# Simulate a manual workflow
actworkflow_dispatch
# Simulate a release event
actrelease
Listing the available workflows
Before starting anything, act -l gives the inventory of what act actually
understood from your files. It is the first diagnosis reflex: a job missing from
that list will never run, usually because its triggering event does not match
the one being simulated, or because the YAML file is in the wrong place.
Terminal window
# See every workflow and its jobs
act-l
Output
Stage Job ID Job name Workflow name Workflow file Events
0 test test CI ci.yml push,pull_request
1 build build CI ci.yml push,pull_request
Every line shows the stage (the execution order), the job ID, its name,
the parent workflow and the triggering events.
Terminal window
# See the jobs for one specific event
act-lpush
act-lpull_request
Visualising the dependency graph
To see the execution order of the jobs graphically:
Terminal window
act-g
Output
╭──────╮
│ test │
╰──────╯
⬇
╭───────╮
│ build │
╰───────╯
Useful to understand the needs: dependencies between jobs.
Handling secrets
Workflows often use secrets (${{ secrets.TOKEN }}). act offers several methods
to supply them.
The .secrets file
Create a .secrets file at the root of the project:
Terminal window
# .secrets (key=value format)
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxx
AWS_ACCESS_KEY_ID=AKIAXXXXXXXX
AWS_SECRET_ACCESS_KEY=xxxxxxxxxx
Then use:
Terminal window
act--secret-file.secrets
Security
Add .secrets to your .gitignore so you never commit it:
Terminal window
echo".secrets">>.gitignore
Secrets on the command line
This form suits a one-off test, not regular use: the value goes into the shell
history and stays visible in ps during the run. If you omit the value (-s GITHUB_TOKEN), act takes the environment variable of the same name, and
failing that asks you interactively. That form avoids writing the secret in plain
text on the command line.
Terminal window
act-sGITHUB_TOKEN=ghp_xxxx-sMY_SECRET=value
Environment variables
For the ${{ vars.X }} values (non-sensitive), use a .vars file:
.vars
ENVIRONMENT=development
API_URL=https://api-dev.example.com
Terminal window
act--var-file.vars
Advanced configuration
The .actrc file
Create a .actrc file at the root of the project to save your options:
Every line corresponds to an option of the act command.
Pin the runner image just like an action. The act-latest tag that most
tutorials recommend is mutable: the image behind it changes without notice,
and your local run stops reproducing your teammates'. The act-24.04 tag matches
the ubuntu-24.04 label your workflows ask for, and the digest following it
freezes the image exactly. This course applies that rule to actions; it applies
just as much to the container running them.
Custom Docker images
act uses Docker images imitating the GitHub runners. By default, those images
are lighter but less complete. For more compatibility:
--verbose is a boolean switch, not a level: act has a single verbosity
step, and repeating the flag changes nothing to the output. That mode mostly
serves to understand what act loaded and which Git context it inferred
from the local repository, two frequent causes of divergence between a local run
and the real runner.
Terminal window
# Detailed logs
act-v
Excerpt of the verbose output
level=debug msg="Loading workflows from '.github/workflows'"
The -v mode reveals the loading of the workflows, the planning of the jobs and
the detected Git environment variables. To go further, two complementary options
exist:
Terminal window
# Structured logs, consumable by an analysis tool
act--json
# Enable the GitHub Actions debug logs (::debug::)
The *DRYRUN* prefix says that no container is created. Useful to validate the
syntax and the structure before a real run.
The limitations of act
act cannot reproduce 100 % of the GitHub Actions environment. Here are the
main limitations to know about:
Limitation
Explanation
No scheduler
act scheduledoes run a workflow declaring on: schedule, but nothing fires it at the stated time: the cron: expression is never evaluated
Limited GitHub API
No access to the GitHub API as in production
Docker services
Service containers can behave differently
Cache
--cache-server-path returns a genuine cache-hit on the second run, but the cache stays local to your machine
Artifacts
The built-in server (--artifact-server-path) does implement the API, but rejects upload-artifact v7 and download-artifact v8: stay on v5 or v6 locally
Marketplace
Some third-party actions are incompatible
Trigger filters ignored
branches:, tags: and paths:are not evaluated: the job runs whatever the local branch or the changed files
Swipe to see more
When to use act:
Syntax and structure tests
Iterative development of workflows
Validation of the commands and scripts
Debugging logic problems
When NOT to rely on act:
The final validation (always test on GitHub)
Integration tests with the GitHub API
Workflows with complex services
The common use cases
Interactive debugging
The --reuse (-r) flag prevents act from deleting the container at the end
of a workflow that succeeded, which lets you both keep the state between two runs
and get into the container with docker exec to inspect the file system. Mind
the trade-off: the container you keep holds the files of the previous run, so a
build passing thanks to a leftover artefact gives you false confidence. Clean
it up before the final validation.
Terminal window
# Run a precise job with detailed logs
act-jbuild-v
# Keep the container of a successful run to inspect it
act--reuse
A workflow with a matrix
act automatically runs every combination of the matrix in parallel:
- run: echo "Deploying to ${{ github.event.inputs.environment }}"
Create a JSON event file:
event.json
{
"inputs": {
"environment": "staging"
}
}
Then run:
Terminal window
actworkflow_dispatch-eevent.json
Output
[Deploy/deploy] ⭐ Run Main
| Deploying to staging
[Deploy/deploy] ✅ Success - Main
[Deploy/deploy] 🏁 Job succeeded
Fitting it into the development flow
A pre-push validation script
Create a Git hook to validate before every push:
.git/hooks/pre-push
#!/bin/bash
echo"Validating the CI workflow..."
act-n-W.github/workflows/ci.yml
if [ $?-ne0 ]; then
echo"The workflow has errors. Push cancelled."
exit1
fi
echo"The workflow is valid"
Do not forget to make the script executable:
Terminal window
chmod+x.git/hooks/pre-push
Combining it with actionlint
For a complete validation, combine the static validation and the execution:
Terminal window
# 1. Validate the syntax with actionlint (fast, no Docker)
actionlint.github/workflows/*.yml
# 2. If it passes, validate the structure with act in dry-run
act-n
# 3. If everything passes, run it for real
act
See the actionlint guide for the
static validation of workflows.
A complete example
The workflow below chains three jobs linked by needs: and applies the hardening
rules of this course: actions pinned by SHA with the tag as a comment,
permissions: {} at workflow level then the strict minimum per job,
persist-credentials: false on the checkout and a pinned runner version. It is
that combination act lets you check locally before the first push.
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
# No rights by default: every job asks for the minimum
Almost every act failure falls into two categories. Either Docker does not
answer as expected (stopped daemon, socket permissions, processor
architecture), or the runner image does not hold the tool the workflow calls.
The error message rarely points at the real cause, so start by checking docker info and the image actually used, printed at the top of every job.
Problem
Cause
Fix
Cannot connect to Docker daemon
Docker not started, or permissions
docker info to check. On Linux: sudo usermod -aG docker $USER then log out and back in
Image not found / an 18 GB pull
The Large image selected by default
Use Medium: -P ubuntu-24.04=catthehacker/ubuntu:act-24.04@sha256:c58e2b36…
Action not found
An incompatible Marketplace action
Check the compatibility on the act repository, use the Full image
exec format error (Mac M1/M2)
An x86 image on ARM
Add --container-architecture linux/amd64
Secret not found
A missing or badly formatted .secrets file
Check the KEY=value format (no spaces) and --secret-file .secrets
The job runs although the filter should exclude it
branches: and paths: are not evaluated
Expected behaviour: check the filter on GitHub itself, with gh run list
Partial actions/cache
Local storage only
Normal, the cache works but is not shared between runs
Swipe to see more
Cheatsheet
The first four lines cover 90 % of the daily usage. The following ones serve when
something does not go as planned or when you test a particular case. Remember the
order above all: act -l to check what is detected, act -n to validate the structure with no container, then act -j to run only the job you care about.
Fast iteration: test your workflows in seconds without pushing to GitHub
Saved resources: no Actions minutes consumed during development
Efficient debugging: verbose mode (-v, a single level) and the option to keep the containers (--reuse)
Configuration: use .secrets and .actrc to centralise your options
Images: start with Medium (~500 MB), move to Large if needed
Complete validation: combine act with actionlint to catch every error
Security: keep act up to date (regular fixes on x/crypto and SELinux)
Limitation: act is not a complete replacement, always validate on GitHub before merging
Check your understanding
Make sure the essentials of this guide are yours. The questions only
cover what was explained here.
Knowledge check
Check what you have learned with this interactive quiz
6 questions
6 min
70% to pass
How it works
The timer starts when you click Start
Multiple choice, true/false and short answer questions
You can move between questions
Detailed results are shown at the end
Starts the quiz and the timer
⏱️--:--
0 / 6
Review
(0/0)
--:--
0%
0Correct
0To review
0:00Time
-Level
Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
# .github/workflows/ci-local.yml
jobs:
test:
strategy:
matrix:
node: [20] # A single version
act -W .github/workflows/ci-local.yml
Performance
Large matrices (3 operating systems by 4 versions means 12 jobs) can take a long time. Prefer testing one combination locally, then validating the complete matrix on GitHub.