Running GitHub Actions Locally with Act
Developing GitHub Actions workflows can be a frustrating experience of trial and error. A typical scenario involves writing a workflow, pushing it to a feature branch, discovering syntax errors or mistakes, pushing multiple fixes, and finally squashing dozens of "fix workflow" commits before merging.
The problem isn't just the wait times between runs, but the inability to debug workflows locally. While you can test individual scripts or commands on your machine, you can't verify how they'll behave in GitHub's environment until you push them.
Act significantly improves this experience by enabling you to run and debug GitHub Actions workflows locally by simulating GitHub's environment using Docker containers. Instead of pushing changes and waiting for results, you can test workflows on your machine, catch and fix issues immediately, and only push once you're confident everything works.
This tutorial will walk you through setting up and using Act for turning workflow development from a frustrating remote debugging process into a seamless local development exercise.
Let's get started!
Prerequisites
To follow this tutorial, you need a recent version of Docker installed on your system. Additionally, ensure that the Docker daemon is running before proceeding. You can check its status with:
If the Active status is not active (running), start Docker with:
With Docker properly set up, you're ready to install and use Act.
Installing the Act CLI
To get started with Act, run the command below to install the latest version available for your operating system:
Alternatively, you can explore other installation methods or download a pre-built binary from the GitHub releases page.
You can confirm that act is installed correctly by running:
How does Act work?
Act allows you to run GitHub Actions workflows locally by simulating the CI/CD
environment within Docker containers. When executed, Act first reads your
workflow files from .github/workflows/ and analyzes which actions need to run
based on the specified triggers and conditions.
The tool then interacts with Docker to prepare the necessary container images.
It either pulls pre-built images (like catthehacker/ubuntu:act-latest) or
builds custom ones as defined in your workflows.
Throughout execution, Act maintains a local context that simulates GitHub's environment, allowing workflows to run as they would on GitHub's infrastructure. This includes managing workspace directories, setting up action runners, handling event payloads, and providing GitHub-compatible environment variables and contexts.
The key difference from GitHub's actual environment is that Act runs everything locally, using Docker to isolate and execute actions while providing similar capabilities to GitHub's hosted runners. This allows developers to test and debug their workflows locally before pushing changes to their repository.
Running Act for the first time
Before running act, you need a project that contains GitHub Actions workflows.
To follow along,
fork this sample project,
which includes a simple Go application with pre-configured workflows, and clone
it to your machine:
Then change into the project directory:
You can inspect the workflows in the repository by running:
This output shows the available workflows and their corresponding jobs. The
lint job runs as part of the CI workflow when a pull request is made, while
the test job runs within the Run tests workflow upon a push to the
repository.
To understand these jobs better, let's examine the workflow files:
These are simple workflows that lints pull requests to maintain code quality, and runs the project's tests on push events to verify that changes do not break the application.
By default, running act without any arguments executes all workflows
associated with push events. Since the "CI" workflow only runs on pull
requests, only the "Run tests" workflow will execute.
To see this in action, run:
When running act for the first time, you'll be prompted to select a default
Docker image:
The Medium image is recommended as it supports most actions while keeping
the disk usage manageable. Once selected, act downloads the image and executes
the workflows.
Once the image is set up, act will begin running the workflow. You'll see logs
similar to the following:
Each step in the workflow is prefixed with [Run tests/test], indicating which
job is being executed. The test job successfully runs go test to verify code
correctness, and reports code coverage.
If you're actively modifying workflows and want act to re-run automatically when files change, enable watch mode with:
This will detect modifications and re-execute the relevant workflows, making it easier to iterate on your CI/CD configurations.
In the next section, we'll explore how to customize the runner images used by Act to match the GitHub-hosted environment or your own requirements.
Customizing Act's runner images
Act allows you to define and modify its behavior using actrc configuration
files, which persist your preferred settings across runs, ensuring that you
don't have to specify them manually each time.
If you selected the Medium image in the previous section, you'll see the
configuration file in $XDG_CONFIG_HOME/act/actrc (~/.config/act/actrc on
Linux) with the following contents:
Each entry follows the format:
These mappings determine which Docker images Act will use when simulating GitHub Actions runners. For example:
-P ubuntu-latest=catthehacker/ubuntu:act-latestensures that workflows targeting ubuntu-latest run inside thecatthehacker/ubuntu:act-latestcontainer.- Similarly, the
ubuntu-22.04,ubuntu-20.04, andubuntu-18.04runners are mapped to their respective container images.
The catthehacker images are designed to resemble GitHub-hosted runners and
provide a compatible environment for most workflows. However, they do not
include all pre-installed tools available in GitHub Actions' official runners so
full compatibility with your workflows isn't guaranteed.
Overriding default runner images
If your workflow requires a custom environment or additional dependencies, you
can override the default images in your actrc file.
For example, to replace the ubuntu-latest runner with a custom image, you can
use:
Or you can specify a different image dynamically when running act:
This flexibility allows you to use your own Docker images that better match your production environment or include pre-installed tools needed by your actions.
Windows and macOS runners
GitHub Actions supports Windows and macOS runners (such as windows-latest and
macos-latest), but Act only supports Ubuntu-based containers. Attempting to
use act with non-Ubuntu runners will result in an "unsupported platform"
message, and those jobs will be skipped.
If you're running Act on a Windows or macOS host, you can bypass this limitation
by running workflows directly on your host machine instead of inside Docker. To
do this, use -self-hosted as the runner value:
This approach also works for Ubuntu-based workflows if you want to avoid Docker overhead:
Beyond customizing runner images, Act offers additional configuration options to fine-tune its behavior. In the next section, we'll explore how to control workflow execution and specify event payloads.
Customizing Act's behavior
Act provides various options to tailor its execution, allowing you to control events, workflows, jobs, and individual steps. These customizations help simulate different GitHub Actions triggers and improve efficiency when testing workflows locally.
By default, Act runs workflows as if they were triggered by a push event (as
we saw earlier). However, you can specify different
event types
when running workflows:
To see all workflows that would be triggered for a given event, use the
-l/--list flag:
You can run the lint job by simulating the pull_request event with:
The CI/lint job will run and it should complete successfully:
Using custom event payloads
Some workflows depend on event-specific properties that Act cannot infer automatically. To fully simulate these events, you need to provide a custom event payload file.
For example, to simulate a push event with a tag, you can create a JSON file
with the following payload:
Then, pass this file to Act using the -e/--eventpath flag:
Filtering workflows and jobs
Another common need is narrowing down what workflows or jobs should be ran which can be achieved in various ways.
For instance, to execute only specific workflows, use the -W/--workflows flag:
You can also run specific jobs with the -j/--job flag:
Skipping jobs and steps
In some cases, certain jobs should only run on GitHub Actions and be skipped when using Act locally.
You can prevent Act from running specific jobs or steps by leveraging custom event properties or environment variables.
Let's start with skipping jobs:
You can conditionally skip a job in Act by adding a custom event property
(github.event.act) and checking for it in the job's if condition.
Then, in your payload.json file, set the act property:
When running act, ensure to provide the payload file:
This ensures that the deploy job is skipped when running Act locally but runs on GitHub Actions.
If you'd like to skip a step when running workflows locally, use an environment
variable (such as env.ACT), which can be set dynamically:
Then run act while setting the ACT environment variable:
Note that this environmental variable approach cannot work for skipping jobs
because the env context isn't available in job-level if conditions.
Specifying variables and secrets
Act provides multiple ways to handle environment variables, repository variables, and secrets when testing GitHub Actions workflows locally. Let's look at each one in turn.
Environmental variables
Environment variables are configuration values accessible using
${{ env.VARIABLE_NAME }} in workflows or $VARIABLE_NAME in scripts. Act
allows you to define these variables using the --env flag or an .env file.
Repository variables
In GitHub Actions, repository variables provide reusable configuration values
across workflows, accessible via ${{ vars.VARIABLE }}. Act supports these
using --var or --var-file.
You can specify repository variables using a file (formatted like .env):
Secrets
Secrets are encrypted variables used for sensitive data like API keys,
credentials, or passwords. They are specified in the GitHub repository settings
and accessed via ${{ secrets.SECRET_NAME }} in workflow files.
Since Act runs locally, you must explicitly provide secrets when testing workflows, and there are three ways to achieve this:
1. Secure prompt
Running Act with --secret SECRET_NAME (without assigning a value) prompts you
to enter the secret securely:
2. Passing secrets directly (not recommended)
You can also specify a secret inline, but this exposes it in shell history:
- Using a secrets file
For multiple secrets, use a secrets file in .env format:
Using GITHUB_TOKEN in Act
On GitHub, a GITHUB_TOKEN is automatically generated for each workflow
execution. However, when running Act locally, this token is not available by
default, which may cause authentication errors.
To prevent this, you need to supply a Personal Access Token (PAT) using any of the methods mentioned above.
If you have the GitHub CLI installed, you can automatically retrieve and pass your authentication token to Act with:
Note that passing an actual GITHUB_TOKEN would make act able to access and
modify your GitHub resources so use with caution.
Working with GitHub Action artifacts
GitHub Action artifacts allow jobs to share data within a workflow and retain files after execution. They are commonly used for:
- Sharing build outputs between jobs
- Storing test results and coverage reports
- Preserving logs for debugging and analysis
However, when testing workflows locally with Act, special configuration is required because GitHub's artifact storage system isn't available.
To use artifacts with Act, specify a local storage path using the
--artifact-server-path flag:
Without this flag, jobs using actions/upload-artifact or
actions/download-artifact will fail with a ACTIONS_RUNTIME_TOKEN error.
To see artifact handling in action, add the following steps to the test job in
your workflow:
Then run act and provide a directory where the artifacts should be uploaded
to:
Once the workflow completes, the artifact (in this case, the coverage report) is stored in the specified directory. You can inspect the contents using:
You'll see that the artifact was stored in a zip file which you can subsequently unzip to inspect its contents.
This local artifact storage allows you to easily test multi-job workflows
depending on upload-artifact and download-artifact without modification
before running on GitHub.
Speeding up Act
By default, Act always pulls the Docker images for the specified runner even if its already present. If you want to speed up the workflow runs, you can enable offline mode which has the following behaviors:
- It prevents downloading container images if they already exist locally.
- If an action has been used before and is cached, offline runs can used these cached resources.
- Since Act won't make repeated requests to GitHub for action downloads, it helps avoid hitting GitHub's API rate limits.
- It eliminates timeouts caused by slow or unstable internet connections.
To activate offline mode, provide the --action-offline-mode:
Setting defaults in actrc
Act allows you to define default settings in an actrc configuration file,
enabling persistent configurations without needing to specify options manually
each time.
The syntax of the configuration file is one argument per line, with no comments allowed:
Aside from the user-specific file located at $XDG_CONFIG_HOME/act/actrc, you
can provide a project-specific .actrc at your project root to customize Act's
behavior on a per-project basis:
For example:
This configuration sets a default runner image, enables offline mode, defines local artifact storage, loads secrets and environment variables, and enables watch mode for automatic workflow re-runs.
You can then execute act or specify the options for filtering workflows and
jobs as needed. Note that most Defaults in actrc can be overridden per run
using command-line arguments:
Example
To demonstrate some of the features we just covered, let's create a GitHub
Actions workflow that automatically generates a release when a new version tag
(v*) is pushed to the repository:
This workflow defines a single job named release-counter, which runs on the
ubuntu-latest runner. It begins by checking out the repository, setting up the
required Go version, and building the project into a binary named counter.
It then packages the binary into a zip file and uses
softprops/action-gh-release to create a draft GitHub release, attaching the
zip file from the previous step for distribution.
With Act, the workflow can be run and debugged before committing changes. However, since it cannot automatically generate a tag push event, you need to provide a simulated event payload:
This mimics a push event where a tag (v1.0.0) is pushed. The ref_type
ensures the event behaves as a tag push, allowing the condition
if: github.ref_type == 'tag' in the workflow to evaluate as true.
You can then run act with the following arguments:
Here's a breakdown of the command arguments:
-e payload.json: Specifies the simulated event file.-W .github/workflows/release.yml: Runs only the release workflow.--env GO_VERSION=1.24: Sets the Go version required by the workflow.--secret GITHUB_TOKEN=$(gh auth token): Passes an authentication token to allowsoftprops/action-gh-releaseto create the draft release.
Provided that your GITHUB_TOKEN has the right permissions, the job will
complete successfully:
After the job runs, visit your repository's Releases page to verify the draft release:
Once confirmed, you can deploy this workflow for automated version releases on GitHub.
Integrating Act with Visual Studio Code
If you're using VS Code, you can integrate your Act workflows into your editor through the GitHub Local Actions extension. Once its installed, you can click its icon on the sidebar to see all your workflows and run them as desired:
Final thoughts
While Act significantly improves the GitHub Actions development experience, it has limitations. Workflows that rely heavily on GitHub's API operations may not work locally, and platform-specific actions or certain GitHub contexts may also behave differently in Act's environment.
For scenarios where local testing isn't sufficient, action-tmate provides a powerful complement to Act as it opens an interactive SSH session within your running workflow, and enables real-time debugging in the actual GitHub environment.
Despite its limitations, Act remains an invaluable tool for catching common
issues early and reducing the iterative push-fix-push cycle typically needed
when developing GitHub Actions' workflows. When used alongside action-tmate,
it provides a comprehensive solution for efficient CI/CD workflow development.
Thanks for reading!