# Running GitHub Actions Locally with Act

Developing [GitHub Actions](https://docs.github.com/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.

![Fix workflow GitHub commit](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/f7914c54-9bb9-43e9-2dd4-2bc7bc399900/md2x =4365x1797)

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.

<iframe width="100%" height="315" src="https://www.youtube.com/embed/A6F_8ajlrYQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


[Act](https://github.com/nektos/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!

[ad-logs]

## Prerequisites

To follow this tutorial, you need a recent version of
[Docker](https://docs.docker.com/engine/install/) installed on your system.
Additionally, ensure that the Docker daemon is running before proceeding. You
can check its status with:

```command
sudo systemctl status docker
```

```text
[output]
Place your right thumb on the fingerprint reader
● docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/service.d
             └─10-timeout-abort.conf, 50-keep-warm.conf
[highlight]
     Active: active (running) since Wed 2025-02-12 20:41:10 WAT; 18h ago
[/highlight]
 Invocation: 2a7d5b896ebc4528ada8349330f91b09
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 82184 (dockerd)
      Tasks: 22
     Memory: 243.6M (peak: 651.5M swap: 12M swap peak: 13.8M)
        CPU: 3min 15.813s
     CGroup: /system.slice/docker.service
             └─82184 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
```

If the **Active** status is not `active (running)`, start Docker with:

```command
sudo systemctl start docker
```

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:

```command
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
```

Alternatively, you can explore
[other installation methods](https://nektosact.com/installation/index.html) or
download a pre-built binary from the
[GitHub releases page](https://github.com/nektos/act/releases).

You can confirm that `act` is installed correctly by running:

```command
act --version
```

```text
[output]
act version 0.2.74
```

## 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](https://github.com/betterstack-community/act-tutorial),
which includes a simple Go application with pre-configured workflows, and clone
it to your machine:

```command
git clone https://github.com/<your_username>/act-tutorial
```

Then change into the project directory:

```command
cd act-tutorial
```

You can inspect the workflows in the repository by running:

```command
act --list
```

```text
[output]
Stage  Job ID           Job name                  Workflow name   Workflow file  Events
0      lint             lint                      CI              ci.yml         pull_request
0      test             test                      Run tests       test.yml       push
```

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:

```yaml
[label .github/workflows/ci.yml]
name: CI

on:
  pull_request:
    branches:
      - main

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.24"

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: v1.64.2
```

```yaml
name: Run tests

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.24"

      - name: Run tests
        run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
```

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:

```command
act
```

When running `act` for the first time, you'll be prompted to select a default
Docker image:

```text
[output]
? Please choose the default image you want to use with act:
  - Large size image: ca. 17GB download + 53.1GB storage, you will need 75GB of free disk space, snapshots of GitHub Hosted Runners without snap and pulled docker images
  - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with most actions
  - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions

Default image and other options can be changed manually in /home/ayo/.config/act/actrc (please refer to https://github.com/nektos/act#configuration for additional information about file stru
cture)  [Use arrows to move, type to filter, ? for more help]
  Large
[highlight]
> Medium
[/highlight]
  Micro
```

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:

```text
[output]
INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'
. . .
[Run tests/test] ⭐ Run Main Run tests
[Run tests/test]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/2] user= workdir=
| === RUN   TestCountWords
| === RUN   TestCountWords/basic_counting
| === RUN   TestCountWords/empty_string
| === RUN   TestCountWords/mixed_case
| --- PASS: TestCountWords (0.00s)
|     --- PASS: TestCountWords/basic_counting (0.00s)
|     --- PASS: TestCountWords/empty_string (0.00s)
|     --- PASS: TestCountWords/mixed_case (0.00s)
| PASS
| coverage: 50.0% of statements
| ok    github.com/betterstack-community/act-tutorial   1.027s  coverage: 50.0% of statements
[Run tests/test]   ✅  Success - Main Run tests
[Run tests/test] ⭐ Run Post Setup Go
[Run tests/test]   🐳  docker exec cmd=[/opt/acttoolcache/node/18.20.5/x64/bin/node /var/run/act/actions/actions-setup-go@v5/dist/cache-save/index.js] user= workdir=
| [command]/opt/hostedtoolcache/go/1.24.0/x64/bin/go env GOMODCACHE
| [command]/opt/hostedtoolcache/go/1.24.0/x64/bin/go env GOCACHE
| /root/go/pkg/mod
| /root/.cache/go-build
| [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/ayo/dev/betterstack/demo/act-tutorial --files-from manifest.txt --use-compress-program zstdmt
| Cache Size: ~26 MB (27232261 B)
| Cache saved successfully
| Cache saved with the key: setup-go-Linux-x64-ubuntu20-go-1.24.0-4b11ccdf1e7dd226ff50ddececac0c8ee38714da25a0be96f5d1e7fbde9ecde2
[Run tests/test]   ✅  Success - Post Setup Go
[Run tests/test] ⭐ Run Complete job
[Run tests/test] Cleaning up container for job test
[Run tests/test]   ✅  Success - Complete job
[Run tests/test] 🏁  Job succeeded
```

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:

```command
act --watch
```

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:

```text
[label ~/.config/act/actrc]
-P ubuntu-latest=catthehacker/ubuntu:act-latest
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
-P ubuntu-20.04=catthehacker/ubuntu:act-20.04
-P ubuntu-18.04=catthehacker/ubuntu:act-18.04
```

Each entry follows the format:

```text
-P <runner-name>=<docker-image>
```

These mappings determine which Docker images Act will use when simulating GitHub
Actions runners. For example:

- `-P ubuntu-latest=catthehacker/ubuntu:act-latest` ensures that workflows
  targeting ubuntu-latest run inside the `catthehacker/ubuntu:act-latest`
  container.
- Similarly, the `ubuntu-22.04`, `ubuntu-20.04`, and `ubuntu-18.04` runners 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:

```text
[label ~/.config/act/actrc]
-P ubuntu-latest=my-custom-image:latest
```

Or you can specify a different image dynamically when running `act`:

```command
act -P ubuntu-latest=my-custom-image:latest
```

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:

```command
act -P windows-latest=-self-hosted # Run workflows on Windows host
```

```command
act -P macos-latest=-self-hosted # Run workflows on macOS host
```

This approach also works for Ubuntu-based workflows if you want to avoid Docker
overhead:

```command
act -P ubuntu-latest=-self-hosted # Run workflows on Ubuntu host
```

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](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows)
when running workflows:

```command
act push # equivalent to act
```

```command
act pull_request # simulates a pull_request event
```

```command
act workflow_dispatch # similates a workflow_dispatch event
```

To see all workflows that would be triggered for a given event, use the
`-l/--list` flag:

```command
act --list
```

```text
[output]
Stage  Job ID  Job name  Workflow name  Workflow file  Events
0      lint    lint      CI             ci.yml         pull_request
0      test    test      Run tests      test.yml       push
```

You can run the `lint` job by simulating the `pull_request` event with:

```command
act pull_request
```

The `CI/lint` job will run and it should complete successfully:

```text
[output]
. . .
[CI/lint] ⭐ Run Post golangci-lint
[CI/lint]   🐳  docker exec cmd=[/opt/acttoolcache/node/18.20.5/x64/bin/node /var/run/act/actions/golangci-golangci-lint-action@v6/dist/post_run/index.js] user= workdir=
| Cache hit occurred on the primary key golangci-lint.cache-Linux-2876-524aac7ed694dd8236ed1e3a9988a38036156b49, not saving cache.
[CI/lint]   ✅  Success - Post golangci-lint
[CI/lint] ⭐ Run Post actions/setup-go@v5
[CI/lint]   🐳  docker exec cmd=[/opt/acttoolcache/node/18.20.5/x64/bin/node /var/run/act/actions/actions-setup-go@v5/dist/cache-save/index.js] user= workdir=
| [command]/opt/hostedtoolcache/go/1.24.0/x64/bin/go env GOMODCACHE
| [command]/opt/hostedtoolcache/go/1.24.0/x64/bin/go env GOCACHE
| /root/go/pkg/mod
| /root/.cache/go-build
| [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/ayo/dev/betterstack/demo/act-tutorial --files-from manifest.txt --use-compress-program zstdmt
| Cache Size: ~26 MB (27268645 B)
| Cache saved successfully
| Cache saved with the key: setup-go-Linux-x64-ubuntu20-go-1.24.0-4b11ccdf1e7dd226ff50ddececac0c8ee38714da25a0be96f5d1e7fbde9ecde2
[CI/lint]   ✅  Success - Post actions/setup-go@v5
[CI/lint] ⭐ Run Complete job
[CI/lint] Cleaning up container for job lint
[CI/lint]   ✅  Success - Complete job
[CI/lint] 🏁  Job succeeded
```

### 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:

```json
[label payload.json]
{
  "ref": "refs/tags/v1.0.0",
}
```

Then, pass this file to Act using the `-e/--eventpath` flag:

```command
act --eventpath payload.json
```

### 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:

```command
act --workflows '.github/workflows/ci.yml' # run all jobs in the ci.yml file
```

```command
act --workflows '.github/workflows/checks' # run all workflows in the checks subdirectory
```

You can also run specific jobs with the `-j/--job` flag:

```command
act --job lint
```

### 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:

```yaml
on: push
jobs:
  deploy:
[highlight]
    if: ${{ !github.event.act }} # skip during local actions testing
[/highlight]
    runs-on: ubuntu-latest
    steps:
      . . .
```

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:

```json
[label payload.json]
{
  "act": true
}
```

When running `act`, ensure to provide the payload file:

```command
act --eventpath payload.json
```

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:

```yaml
- name: Some step
  if: ${{ !env.ACT }} # skip this step when running locally
  run: |
    ...
```

Then run `act` while setting the `ACT` environment variable:

```command
act --env ACT=true
```

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.

```command
act --env NODE_ENV=production --env DEBUG=true
```

```command
act --env-file .env
```

```text
[label .env]
NODE_ENV=production
DEBUG=true
PORT=3000
```

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

```command
act --var DEPLOY_ENV=staging --var API_VERSION=v2
```

You can specify repository variables using a file (formatted like `.env`):

```command
act --var-file .vars
```

### 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:

```command
act --secret API_TOKEN
```

![Act prompting for API_TOKEN](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/f67447fd-b211-4068-df56-de00255e0f00/md1x =2228x702)

#### 2. Passing secrets directly (not recommended)

You can also specify a secret inline, but this exposes it in shell history:

```command
act --secret API_TOKEN=secret1234
```

3. Using a secrets file

For multiple secrets, use a secrets file in `.env` format:

```command
act --secret-file .secrets
```

```text
[label .secrets]
API_TOKEN=secret1234
SOME_PASSWORD=s3cureP@ss
```

### 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)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
using any of the methods mentioned above.

```command
act --secret GITHUB_TOKEN # prompt for value securey
```

If you have the [GitHub CLI](https://cli.github.com/) installed, you can
automatically retrieve and pass your authentication token to Act with:

```command
act --secret GITHUB_TOKEN="$(gh auth token)"
```

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:

```command
act --artifact-server-path <path_to_artifact_dir>
```

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:

```yaml
[label .github/workflows/test.yml]
. . .
jobs:
  . . .

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      . . .

[highlight]
      - uses: actions/upload-artifact@v4
        with:
          name: code-coverage-report
          path: ./coverage.txt
[/highlight]
```

Then run `act` and provide a directory where the artifacts should be uploaded
to:

```command
act --artifact-server-path /tmp/artifacts
```

Once the workflow completes, the artifact (in this case, the coverage report) is
stored in the specified directory. You can inspect the contents using:

```command
tree /tmp/artifacts
```

You'll see that the artifact was stored in a zip file which you can subsequently
unzip to inspect its contents.

```text
[output]
/tmp/artifacts/
└── 1
    └── code-coverage-report
        └── code-coverage-report.zip
```

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`:

```command
act --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:

```text
[label ~/.config/actrc/actrc]
-P ubuntu-latest=catthehacker/ubuntu:act-latest
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
-P ubuntu-20.04=catthehacker/ubuntu:act-20.04
-P ubuntu-18.04=catthehacker/ubuntu:act-18.04
```

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:

```text
[label .actrc]
-P ubuntu-latest=catthehacker/ubuntu:act-latest
--action-offline-mode
--artifact-server-path=/tmp/artifacts
--secret-file=.secrets
--env-file=.env
--watch
```

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:

```command
act --artifact-server-path=./artifacts # override the default artifacts path
```

## 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:

```yaml
[label .github/workflows/release.yml]
name: Stable release

on:
  push:
    tags:
      - v*

jobs:
  release-counter:
    name: Build and release binary
    runs-on: ubuntu-latest
    if: github.ref_type == 'tag'
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setting up Go ${{ env.GO_VERSION }}
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}

      - name: Running Go Build
        run: go build -o counter

      - name: Zipping binary
        run: zip counter.zip counter

      - name: Create stable release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ github.ref_name }}
          name: ${{ github.ref_name }}
          draft: true
          prerelease: false
          files: |
            counter.zip
```

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:

```json
[label payload.json]
{
  "ref": "refs/tags/v1.0.0",
  "ref_type": "tag",
  "ref_name": "v1.0.0"
}
```

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:

```command
act \
 -e payload.json \
 -W .github/workflows/release.yml \
 --env GO_VERSION=1.24 \
 --secret GITHUB_TOKEN=$(gh auth token)
```

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
  allow `softprops/action-gh-release` to create the draft release.

Provided that your `GITHUB_TOKEN` has the right permissions, the job will
complete successfully:

![Build and release stable version](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/bf31cf7b-9f0a-44a9-75cd-f11878104d00/md1x =3306x1504)

After the job runs, visit your repository's **Releases** page to verify the
draft release:

![GitHub releases](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/a1b19a7d-8dc7-4bb7-f75c-41c96bace000/public =3486x1110)

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](https://marketplace.visualstudio.com/items?itemName=SanjulaGanepola.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:

![GitHub Local Actions in VS Code](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/0788a17a-b8cf-40bd-0bdf-fa03fb2f4700/orig =3354x2530)

## 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](https://github.com/mxschmitt/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!
