Blog
Run and test your GitHub Actions locally
This blog post describes how to run and test your GitHub Workflows and Actions locally using act.
Following the motto: "Think globally, act locally".
π Intro
This blog post gives a quick intro on GitHub Workflows and GitHub Actions. The next chapters describe the challenges when developing GitHub Workflows and explain how you can develop, run and test your GitHub Workflows and GitHub Actions locally.
π‘ GitHub Workflows and Actions

GitHub Workflows
GitHub Workflows are a feature of GitHub Actions that enable automated software development processes directly from your GitHub repository.
These workflows are defined in YAML files and can be triggered by several GitHub events such as on push, pull requests, or at scheduled times.
Workflows consist of one or more jobs that can run sequentially or in parallel on GitHub-hosted runners or self-hosted runners. Each job contains steps that perform specific tasks like setting up tools, running scripts, building code, testing code or deploying your code.
GitHub Actions
In GitHub Actions, there are two main types of runners for actions:
- JavaScript actions: These actions run directly on the runner machine using Node.js. JavaScript actions are typically faster to start than Docker container actions because there’s no need to download and start a container.
- Docker container actions: These actions run inside a Docker container on GitHub’s virtual environments. You can use any public Docker image available on Docker Hub or other registries, or you can create a custom Dockerfile to define the environment. This type is particularly useful when you need a specific environment that isn’t provided by GitHub’s hosted runners.
Both types allow you to automate workflows for building, testing, and deploying code directly from your GitHub repositories. Docker container actions provide more flexibility in terms of the environment setup, while JavaScript actions offer in most cases a quicker execution time.
π Challenge
When developing a GitHub Workflow for your project to build, test and deploy your software, there are some challenges because you must commit and push your files to GitHub to make GitHub Actions run your workflow.
So, when you make a small mistake in your Workflow (which can happen easily because GitHub Workflows are defined in YAML, which can introduce some bugs related to indentation and syntax), or when just adding extra steps or updating the code or tests, every time you need to commit the code and wait for GitHub to complete the run from that workflow.
This results in additional waiting time and, in case of Private GitHub Repositories, this can cost additional money in case your free build-minutes are exceeded.
πͺ Using “act”
To overcome these limitations, you can use act.

Act has the motto: “Think globally, act locally”.
What is “act”?
“act” is a command-line tool that simulates the GitHub Actions environment on your local machine. It reads the .github/workflows/*.yml files in a repository and executes the actions as if they were running on GitHub’s servers. This means you can test your workflows locally before pushing changes to your repository and waiting on feedback on the build process from GitHub Actions.
How does “act” work?
When running “act” locally, it works as follows:
- It reads in your GitHub Actions from
.github/workflows/and determines the set of actions that need to be run. - It uses the Docker API to either pull or build the necessary images, as defined in your workflow files.
- Finally it determines the execution path based on the dependencies that were defined. Once it has the execution path, act uses the Docker API to run containers for each action based on the images prepared earlier.
Using”act” for running Workflows
- You can run a specific workflow by using the command
act -j {job-id}where {job-id} is the name of the job you want to execute.
- If you don’t specify a job, act will run the default event (usually
push) across all workflows. - You can specify the GitHub event type with
act -e {event.json}where {event.json} is a JSON file that describes the GitHub event.
There is also support for Environment Variables and Secrets. Since many workflows depend on environment variables and secrets, “act” allows you to pass these through either the command line or a .secrets or .enviroment file.
π» Usage / Demo
π¦ Installation
There are multiple ways to install act on your local system. See this page for all possible installations and supported operating systems.
For this demo I opted to install “act” as a GitHub CLI extension on a Windows PC:
gh extension install https://github.com/nektos/gh-act
π Example project
In order to test “act” locally, I created a simple C# library with an xUnit test-project. (See this link).
Next I added a GitHub Workflow YAML file named `main.yml` with the following content:
name: Build with Tests
env:
CONFIGURATION: Release
RUN_TESTS: true
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v3
with:
dotnet-version: |
8.0.x
- name: Build Project
run: |
dotnet build ./ActExample/ActExample.csproj -c CONFIGURATION
- name: Run Tests
if: env.RUN_TESTS == 'true'
run: |
dotnet test ./ActExampleTests/ActExampleTests.csproj -c CONFIGURATION
This example workflow defines two environment variables, triggers on a push, uses the Ubuntu-runner and contains the following four steps:
- Run the GitHub Action
actions/checkout@v2to checkout the source code.
Note that this step will not be executed when running “act” locally, because the source-code is already available. - Run the GitHub Action
actions/setup-dotnet@v3to download and use the specified .NET 8 version. - Run a command
dotnet buildto build the .NET library. - Run a command
dotnet testto build and run the tests for related the .NET library.
π³ Start Docker
Because the example main.yml file uses Ubuntu as the runner (runs-on: ubuntu-latest), Docker Desktop should be running on your system and be configured to use the “Linux Containers”.
βΆοΈ Running “act”
Running the GitHub CLI version of “act” is simple, just go to the folder where your project is located and run:
gh act
When running “act” for the first time, the default Docker image named catthehacker/ubuntu:act-latest is downloaded.
And because the main.yml file also uses actions/setup-dotnet@v3 and this action is a JavaScript action, this Action is also downloaded to your system and saved in the act cache location (%userprofile%\.cache\act) so that it can be used by the “act” runner.
The logging when running “act” for the first time looks like this:
time="2024-05-10T11:11:42+02:00" level=info msg="Using docker host 'npipe:////./pipe/docker_engine', and daemon socket 'npipe:////./pipe/docker_engine'" [Build with Tests/build] π Start image=catthehacker/ubuntu:act-latest [Build with Tests/build] π³ docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true [Build with Tests/build] using DockerAuthConfig authentication for docker pull [Build with Tests/build] π³ docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host" [Build with Tests/build] π³ docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host" [Build with Tests/build] β git clone 'https://github.com/actions/setup-dotnet' # ref=v3 [Build with Tests/build] β Run Main actions/checkout@v2 [Build with Tests/build] π³ docker cp src=C:\Dev\GitHub\ActExample\. dst=/mnt/c/Dev/GitHub/ActExample [Build with Tests/build] β Success - Main actions/checkout@v2 [Build with Tests/build] β Run Main actions/setup-dotnet@v3 [Build with Tests/build] π³ docker cp src=C:\Users\StefHeyenrath\.cache\act/actions-setup-dotnet@v3/ dst=/var/run/act/actions/actions-setup-dotnet@v3/ [Build with Tests/build] π³ docker exec cmd=[node /var/run/act/actions/actions-setup-dotnet@v3/dist/setup/index.js] user= workdir= | [command]/run/act/actions/actions-setup-dotnet@v3/externals/install-dotnet.sh --channel 8.0 | dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.204/dotnet-sdk-8.0.204-linux-x64.tar.gz | dotnet-install: Extracting zip from https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.204/dotnet-sdk-8.0.204-linux-x64.tar.gz | dotnet-install: Installed version is 8.0.204 | dotnet-install: Adding to current process PATH: `/usr/share/dotnet`. Note: This change will be visible only when sourcing script. | dotnet-install: Note that the script does not resolve dependencies during installation. | dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section. | dotnet-install: Installation finished successfully. [Build with Tests/build] β add-matcher /run/act/actions/actions-setup-dotnet@v3/.github/csc.json [Build with Tests/build] β Success - Main actions/setup-dotnet@v3 [Build with Tests/build] β ::set-env:: DOTNET_ROOT=/usr/share/dotnet [Build with Tests/build] β ::set-output:: dotnet-version=8.0.204 [Build with Tests/build] β ::add-path:: /usr/share/dotnet [Build with Tests/build] β Run Main Build Project [Build with Tests/build] π³ docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/2] user= workdir= | | Welcome to .NET 8.0! | --------------------- | SDK Version: 8.0.204 | | Telemetry | --------- | The .NET tools collect usage data in order to help us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. | | Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry | | ---------------- | Installed an ASP.NET Core HTTPS development certificate. | To trust the certificate, view the instructions: https://aka.ms/dotnet-https-linux | | ---------------- | Write your first app: https://aka.ms/dotnet-hello-world | Find out what's new: https://aka.ms/dotnet-whats-new | Explore documentation: https://aka.ms/dotnet-docs | Report issues and find source on GitHub: https://github.com/dotnet/core | Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli | -------------------------------------------------------------------------------------- | MSBuild version 17.9.8+b34f75857 for .NET | Determining projects to restore... | Restored /mnt/c/Dev/GitHub/ActExample/ActExample/ActExample.csproj (in 83 ms). | ActExample -> /mnt/c/Dev/GitHub/ActExample/ActExample/bin/Release/net8.0/ActExample.dll | | Build succeeded. | 0 Warning(s) | 0 Error(s) | | Time Elapsed 00:00:03.51 [Build with Tests/build] β Success - Main Build Project [Build with Tests/build] β Run Main Run Tests [Build with Tests/build] π³ docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/3] user= workdir= | MSBuild version 17.9.8+b34f75857 for .NET | Determining projects to restore... | Restored /mnt/c/Dev/GitHub/ActExample/ActExampleTests/ActExampleTests.csproj (in 6.82 sec). | 1 of 2 projects are up-to-date for restore. | ActExample -> /mnt/c/Dev/GitHub/ActExample/ActExample/bin/Release/net8.0/ActExample.dll | ActExampleTests -> /mnt/c/Dev/GitHub/ActExample/ActExampleTests/bin/Release/net8.0/ActExampleTests.dll | | Build succeeded. | 0 Warning(s) | 0 Error(s) | | Time Elapsed 00:00:10.89 | Test run for /mnt/c/Dev/GitHub/ActExample/ActExampleTests/bin/Release/net8.0/ActExampleTests.dll (.NETCoreApp,Version=v8.0) | Microsoft (R) Test Execution Command Line Tool Version 17.9.0 (x64) | Copyright (c) Microsoft Corporation. All rights reserved. | | Starting test execution, please wait... | A total of 1 test files matched the specified pattern. | | Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - ActExampleTests.dll (net8.0) [Build with Tests/build] β Success - Main Run Tests [Build with Tests/build] β Run Post actions/setup-dotnet@v3 [Build with Tests/build] π³ docker exec cmd=[node /var/run/act/actions/actions-setup-dotnet@v3/dist/cache-save/index.js] user= workdir= [Build with Tests/build] β Success - Post actions/setup-dotnet@v3 [Build with Tests/build] Cleaning up container for job build [Build with Tests/build] π Job succeeded
βΆοΈ Running “act” (advanced options)
Run on the ‘push’ event
To run only the workflows which have the on: [push] event defined in the workflow file, use the next command:
gh act push
Run specific workflow
Running “act” on a specific workflow is also possible (this can be useful when multiple workflow files are defined for your project):
gh act -W .github/workflows/main.yml
Passing an environment variable
Passing environment variables is also possible. If you want to run the main.yml workflow and compile the library in Debug mode and skip running the unit-tests, you can use this command:
gh act -W .github/workflows/main.yml --env CONFIGURATION=Debug --env RUN_TESTS=false
Reusing the image
In my example, every time I run “act”, the .NET 8 version is downloaded and installed on the runner image, and this takes some time. To keep the container (don’t remove the container on successfully completed workflow to maintain state between runs) use the following command:
gh act -r
All Options
Use help command to list all possible command-line options:
gh act --help
π Conclusion
My experience
I used “act” on this example .NET project and also on another project: LINQKit. Since the LINQKit project uses a Windows runner (runs-on: windows-latest), I do not need to start Docker, as running the main.yml workflow is possible on my own Windows PC.
When running “act”, I just have to make sure that I override the windows-latest image with my local self-hosted environment, the command to do this is:
gh act -W .\.github\workflows\main.yml -P windows-latest=-self-hosted
My take-away
Using “act” is a practical approach to developing and testing GitHub Actions workflows locally, enabling faster iteration and debugging while minimizing the need for multiple commits and pushes just to test the GitHub Workflow logic.
Benefits of using “act”
- Speed: Test workflows quickly without waiting for GitHub’s servers.
- Testing: Easily test and debug your workflows inspecting execution locally.
- Cost-Efficient: Saves money by reducing the number of runs executed on GitHub’s servers, especially useful for private repositories with limited GitHub Actions build-minutes.
Limitations of using “act”
- Compatibility: Not all actions are compatible with “act”, especially those that require specific GitHub-hosted services or configurations.
- Environment differences: There can be differences between the local Docker environment and GitHub’s environment, which might lead to discrepancies in behavior.
- Support: The default runners are intentionally incomplete. These default images do not contain all the tools that GitHub Actions offers by default in their runners. Many things can work improperly or not at all while running those images. Additionally, some software might still not work even if installed properly, since GitHub Actions are running in fully virtualized machines while “act” is using Docker containers. There are some alternative runners available, see this page for more details.
π Links
π Notes
Some content in this blog is created with the help of an AI. I did review and revise the content were needed.
Written by: Stef Heyenrath
Stef started writing software for the Microsoft .NET framework in 2007. Over the years, he has developed into a Microsoft specialist with experience in: backend technologies such as .NET, NETStandard, ASP.NET, Ethereum, Azure, and other cloud providers. In addition he worked with several frontend technologies such as Blazor, React, Angular, Vue.js.
He is the author from WireMock.Net.
Mission: Writing quality and structured software with passion in a scrum team for technically challenging projects.
Want to know more about our experts? Contact us!
