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:
- act runs your GitHub Actions workflows in containers on your machine.
- OrbStack is a light Docker runtime for Mac. It uses fewer resources than Docker Desktop, so CI containers feel less heavy on battery and RAM.
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:
- On GitHub it runs for pull requests to
main. It also allows a manualworkflow_dispatchrun. - The job id is
verify. That is the name act cares about, not the pretty label in the GitHub UI. - That job is a full check: install, typecheck, lint, unit tests, database tests against Postgres, then a production build.
- Postgres comes from a workflow service container, the same way GitHub Actions would start it.
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 versionact talks to Docker. OrbStack provides that engine without the Docker Desktop tax on RAM and CPU.
2. Install act
brew install actOr use the install script from the project readme.
3. List what act can see
From the repo root:
act -lYou 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_dispatchworkflow_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 .secretsA simple loop with agents
- Change code, or let an agent change it.
- Run
act workflow_dispatch. - Fix whatever the workflow reports.
- 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:
- the required status check on protected branches
- deploy jobs
- jobs that need private network access only available in the cloud
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.