Skip to content
Français
CI/CD & Automatisation medium

act: running GitHub Actions workflows locally

45 min de lecture

Read this page in French

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.

The architecture of act

The act architecture: local workflow, Docker, runner container, results

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:

Fenêtre de terminal
docker version

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.

With Homebrew:

Fenêtre de terminal
brew install act

Checking the installation:

Fenêtre de terminal
act --version
Expected output
act version 0.2.84

The first run

On the first run, act asks which Docker image to use to simulate the GitHub runners. Three options are offered:

ImageSizeCompatibility
Micro (~200 MB)Very lightLimited (many tools are missing)
Medium (~500 MB)BalancedGood for most cases
Large (~18 GB)CompleteClose to the GitHub environment

To start with, pick Medium. You can change later.

Fenêtre de terminal
# 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:

Fenêtre de terminal
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.

Fenêtre de terminal
# 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:

Fenêtre de terminal
# Run only the "test" job
act -j test
Output
[CI/test] 🚀 Start image=catthehacker/ubuntu:act-latest
[CI/test] 🐳 docker pull image=catthehacker/ubuntu:act-latest
[CI/test] 🐳 docker create image=catthehacker/ubuntu:act-latest
[CI/test] ⭐ Run Main Checkout
[CI/test] ✅ Success - Main Checkout
[CI/test] ⭐ Run Main Run tests
| Tests passed!
[CI/test] ✅ Success - Main Run tests
[CI/test] 🏁 Job succeeded

The icons show the status: a star for the step in progress, a tick for success, a cross for failure.

Fenêtre de terminal
# Run only the "build" job of the ci.yml workflow
act -W .github/workflows/ci.yml -j build

Simulating the different events

GitHub Actions triggers on different events. act can simulate them:

Fenêtre de terminal
# Simulate a push (the default)
act push
# Simulate a pull request
act pull_request
# Simulate a manual workflow
act workflow_dispatch
# Simulate a release event
act release

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.

Fenêtre de terminal
# 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.

Fenêtre de terminal
# See the jobs for one specific event
act -l push
act -l pull_request

Visualising the dependency graph

To see the execution order of the jobs graphically:

Fenêtre de terminal
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:

Fenêtre de terminal
# .secrets (key=value format)
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxx
AWS_ACCESS_KEY_ID=AKIAXXXXXXXX
AWS_SECRET_ACCESS_KEY=xxxxxxxxxx

Then use:

Fenêtre de terminal
act --secret-file .secrets

Security

Add .secrets to your .gitignore so you never commit it:

Fenêtre de terminal
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.

Fenêtre de terminal
act -s GITHUB_TOKEN=ghp_xxxx -s MY_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
Fenêtre de terminal
act --var-file .vars

Advanced configuration

The .actrc file

Create a .actrc file at the root of the project to save your options:

.actrc
--secret-file .secrets
--var-file .vars
-P ubuntu-24.04=catthehacker/ubuntu:act-latest
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
--container-architecture linux/amd64

Every line corresponds to an option of the act command.

Custom Docker images

act uses Docker images imitating the GitHub runners. By default, those images are lighter but less complete. For more compatibility:

Fenêtre de terminal
# Use a more complete image
act -P ubuntu-24.04=catthehacker/ubuntu:act-latest
# Or your own image
act -P ubuntu-24.04=my-registry/my-image:tag

Verbose mode for debugging

--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.

Fenêtre de terminal
# Detailed logs
act -v
Excerpt of the verbose output
level=debug msg="Loading workflows from '.github/workflows'"
level=debug msg="Found workflow 'ci.yml'"
level=debug msg="Planning job: test"
level=debug msg="Job.Steps: Checkout"
level=debug msg="Job.Steps: Run tests"
level=debug msg="using github ref: refs/heads/main"
level=debug msg="Detected CPUs: 16"

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:

Fenêtre de terminal
# Structured logs, consumable by an analysis tool
act --json
# Enable the GitHub Actions debug logs (::debug::)
act -s ACTIONS_STEP_DEBUG=true -s ACTIONS_RUNNER_DEBUG=true

Dry-run

To see what would run without actually running it:

Fenêtre de terminal
act -n
Dry-run output
*DRYRUN* [CI/test] ⭐ Run Set up job
*DRYRUN* [CI/test] 🚀 Start image=catthehacker/ubuntu:act-latest
*DRYRUN* [CI/test] 🐳 docker pull image=catthehacker/ubuntu:act-latest
*DRYRUN* [CI/test] ✅ Success - Set up job
*DRYRUN* [CI/test] ⭐ Run Main Checkout
*DRYRUN* [CI/test] ✅ Success - Main Checkout
*DRYRUN* [CI/test] 🏁 Job succeeded

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:

LimitationExplanation
Limited eventsschedule, deployment and page_build are not supported
Limited GitHub APINo access to the GitHub API as in production
Docker servicesService containers can behave differently
Cacheactions/cache works partially (local storage)
Artifactsactions/upload-artifact creates local files, with no API
MarketplaceSome third-party actions are incompatible

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.

Fenêtre de terminal
# Run a precise job with detailed logs
act -j build -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:

.github/workflows/matrix.yml
jobs:
build:
runs-on: ubuntu-24.04
strategy:
matrix:
node: [18, 20, 22]
steps:
- run: echo "Testing Node.js ${{ matrix.node }}"
Fenêtre de terminal
act -W .github/workflows/matrix.yml
Output (3 parallel jobs)
[Matrix Build/build-1] 🚀 Start image=catthehacker/ubuntu:act-latest
[Matrix Build/build-2] 🚀 Start image=catthehacker/ubuntu:act-latest
[Matrix Build/build-3] 🚀 Start image=catthehacker/ubuntu:act-latest
[Matrix Build/build-1] ⭐ Run Main
| Testing Node.js 18
[Matrix Build/build-2] ⭐ Run Main
| Testing Node.js 20
[Matrix Build/build-3] ⭐ Run Main
| Testing Node.js 22

To run only one combination of the matrix:

Fenêtre de terminal
# Filter by matrix value
act --matrix node:20

Testing a workflow_dispatch with inputs

For a workflow with inputs:

.github/workflows/deploy.yml
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
type: choice
options: [dev, staging, prod]
jobs:
deploy:
runs-on: ubuntu-24.04
steps:
- run: echo "Deploying to ${{ github.event.inputs.environment }}"

Create a JSON event file:

event.json
{
"inputs": {
"environment": "staging"
}
}

Then run:

Fenêtre de terminal
act workflow_dispatch -e event.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 [ $? -ne 0 ]; then
echo "The workflow has errors. Push cancelled."
exit 1
fi
echo "The workflow is valid"

Do not forget to make the script executable:

Fenêtre de terminal
chmod +x .git/hooks/pre-push

Combining it with actionlint

For a complete validation, combine the static validation and the execution:

Fenêtre de terminal
# 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
permissions: {}
jobs:
lint:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Lint
run: echo "Linting..."
test:
runs-on: ubuntu-24.04
needs: lint
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Run tests
run: echo "Tests passed!"
build:
runs-on: ubuntu-24.04
needs: test
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Build
run: echo "Building..."
Fenêtre de terminal
# Visualise the structure
act -g
Dependency graph
╭──────╮
│ lint │
╰──────╯
╭──────╮
│ test │
╰──────╯
╭───────╮
│ build │
╰───────╯
Fenêtre de terminal
# Run only the tests (with their dependencies)
act -j test

Troubleshooting

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.

ProblemCauseFix
Cannot connect to Docker daemonDocker not started, or permissionsdocker info to check. On Linux: sudo usermod -aG docker $USER then log out and back in
Image not found / an 18 GB pullThe Large image selected by defaultUse Medium: -P ubuntu-24.04=catthehacker/ubuntu:act-latest
Action not foundAn incompatible Marketplace actionCheck the compatibility on the act repository, use the Full image
exec format error (Mac M1/M2)An x86 image on ARMAdd --container-architecture linux/amd64
Secret not foundA missing or badly formatted .secrets fileCheck the KEY=value format (no spaces) and --secret-file .secrets
Unsupported eventschedule and deployment are not implementedTest on GitHub, act does not support every event
Partial actions/cacheLocal storage onlyNormal, the cache works but is not shared between runs

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.

CommandDescription
actRun every workflow (push event)
act -lList every job
act -gShow the dependency graph
act -nDry-run (validation with no execution)
act -j <job>Run one specific job
act pull_requestSimulate a pull request
act workflow_dispatch -e event.jsonTrigger it with inputs
act -vVerbose mode (a boolean flag, -vv adds nothing)
act --secret-file .secretsLoad the secrets
act --matrix node:20Filter one matrix combination
act -P ubuntu-24.04=catthehacker/ubuntu:act-latestSet the image
act --container-architecture linux/amd64Force the architecture (Mac M1/M2)

Key points

  • 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

Resources

Frequently asked questions

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn