Blog

Using act and OrbStack to save on GitHub Actions CI cost

GitHub Actions is easy. It is also not free once you burn through the free minutes. In the age of AI, that bill grows faster than before.

You push more often. Agents open more PRs. Tokens already cost real money. Waiting on a hosted runner for a check you already know will fail wastes both time and cash.

Your Mac is fast. Use it.

The idea

Run the same workflow locally first. Fix issues on your machine. Open or update the PR when the suite is green. Keep GitHub Actions as the final gate on the remote, not as your every-edit feedback loop.

Two tools make this smooth on a Mac:

I use this on Timekeep, my payroll product. The notes below follow that shape of CI.

What the CI actually looks like

Timekeep has one main workflow file and one main job. The important details for local runs are:

So local CI is not “run your unit tests alone.” It is “run the same verify job the workflow defines.”

Setup

1. Install OrbStack

Install it, then open it once so the Docker engine is running.

# confirm docker works
docker version

act talks to Docker. OrbStack provides that engine without the Docker Desktop tax on RAM and CPU.

2. Install act

brew install act

Or use the install script from the project readme.

3. List what act can see

From the repo root:

act -l

You should see the verify job under the events your workflow actually declares. For Timekeep that includes pull_request and workflow_dispatch, not the default push event.

The command I actually type

Day to day, with .actrc in place:

act workflow_dispatch

workflow_dispatch is GitHub’s “run this workflow by hand” event. Locally that is what you want. You are not opening a PR. You are saying run CI now.

Why not act pull_request? That event is for simulating a PR. It can need more PR context, and it is not how you think about a local loop. Keep pull_request for GitHub. Use workflow_dispatch on your laptop. The workflow just needs both triggers listed under on:.

Bare act defaults to push. If your workflow does not listen for push, that default can run nothing useful.

A .actrc line by line

Put defaults in .actrc so the command stays short. Here is the one I use for Timekeep on an Apple Silicon Mac:

-W .github/workflows/ci.yml
-j verify
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-24.04
--container-architecture linux/arm64
--rm
-q

-W .github/workflows/ci.yml

Only load this workflow file. Timekeep has one CI file, so there is no need to scan every YAML under .github/workflows.

-j verify

Always run the verify job. That is the job id under jobs:, not the display name in the GitHub UI.

-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-24.04

Map runs-on: ubuntu-latest to a full Ubuntu image act can run in Docker. Without this, act may pick a smaller default image that is missing tools your workflow expects.

--container-architecture linux/arm64

Run containers as arm64. That matches Apple Silicon. On an Intel Mac, drop this line or use linux/amd64.

--rm

Remove the containers when the job finishes. Keeps Docker from filling up with leftover act containers after each run.

-q

Quiet mode. Less act chatter in the terminal so you can focus on the workflow output.

With that file in place, act workflow_dispatch is enough. act fills in the workflow, job, image, arch, and cleanup flags for you.

Secrets and env

Many apps only need dummy values in CI. Timekeep’s workflow sets those inline for things like session secrets and fake cloud credentials, so a local act run can reuse the same approach.

If you do need real secrets locally, use a file you never commit:

# .secrets (gitignored)
SOME_TOKEN=...
act workflow_dispatch --secret-file .secrets

A simple loop with agents

  1. Change code, or let an agent change it.
  2. Run act workflow_dispatch.
  3. Fix whatever the workflow reports.
  4. Push or update the PR when green.

That loop keeps GitHub Actions minutes for the check that matters on the remote. It also keeps token-heavy agent work off the billable runner.

When to still use hosted CI

Local act is great for the full verify path: typecheck, lint, tests, service containers, and build.

Keep hosted Actions for:

Bottom line

Use OrbStack for a light Docker engine on Mac. Use .actrc for the long act flags. Use act workflow_dispatch as the local command. Let GitHub keep the real pull_request gate.

You already pay for a fast laptop. In a world where AI burns tokens and CI burns minutes, local first is the cheap path.