Skip to content
HELiX

Local CI with act

apps/docs/src/content/docs/guides/local-ci Click to copy
Copied! apps/docs/src/content/docs/guides/local-ci

HELiX uses nektos/act to run GitHub Actions workflows locally inside Docker containers. The local Docker pass via act is one piece of the full preflight gate — the canonical “ran the full local quality gate” command is pnpm run preflight, which composes lint, format, type-check, build, smart tests, CEM drift, changeset check, and an act-ci.sh invocation (Docker-CI parity).

This is a mandatory quality gate. Code does not push to GitHub until pnpm run preflight passes (which includes the act Docker-CI step on machines that have Docker available).


Before running act, you need:

  1. Docker Desktop — running and healthy

    Terminal window
    docker info # must succeed with no errors
  2. act — install via Homebrew

    Terminal window
    brew install act
  3. .actrc — already checked into the repo root (use the project’s repo-root file; act also reads ~/.actrc if present, but the repo-root file is the source of truth here). Contains performance flags (--bind, --reuse, --pull=false).


Run all quality gates locally:

Terminal window
./scripts/act-ci.sh

This runs the act-ci.yml workflow — a lightweight mirror of the production ci.yml that avoids GitHub-specific actions incompatible with local Docker.

Total time: ~4 minutes (jobs run in parallel where possible).


Terminal window
./scripts/act-ci.sh
Terminal window
./scripts/act-ci.sh --job lint
./scripts/act-ci.sh --job format
./scripts/act-ci.sh --job type-check
./scripts/act-ci.sh --job build
./scripts/act-ci.sh --job test
Terminal window
./scripts/act-ci.sh --list

--clean removes all act-* containers (whether running or stopped) before the run. Normal runs already prune exited containers in passing — use --clean when a leaked container is blocking re-runs.

Terminal window
./scripts/act-ci.sh --clean

FlagDescription
--job <name>Run a specific job instead of all gates
--listPrint available job names and exit
--help / -hShow usage text and exit
--nativeUse linux/arm64 containers (no Rosetta emulation — faster on Apple Silicon)
--fullRun the complete test suite instead of smart tests (changed components only)
--matrixRun the full Node.js × Playwright matrix test job (test-full) — slow
--batchRun tests in batches of 10 to avoid Docker OOM
--cleanRemove all act-* containers before running
--fastRun the consolidated single-job fast workflow

The act-ci.yml workflow runs these jobs. The table reflects the default subset run by ./scripts/act-ci.sh; the workflow also defines a test-full job that only fires with --matrix (ACT_MATRIX_TESTS / ACT_FULL_TESTS):

JobWhat it checksApproximate time
lintESLint strict~12s (warm)
formatPrettier check~10s (warm)
type-checkTypeScript strict — no any~15s (warm)
buildFull library build + CEM validation~90s
testVitest browser tests (Playwright/Chromium)~120s
test-fullFull Node × Playwright matrix test (opt-in)~10–15min
quality-gatesAggregate — required jobs must pass~1s

Jobs that need Playwright browsers will use a cached volume mount on repeat runs — first run downloads browsers, subsequent runs reuse the cache.


By default, act runs linux/amd64 containers via Rosetta 2 emulation. This uses 2–3× more memory than native.

Recommended setup for Apple Silicon:

Terminal window
# Native ARM64 containers — no Rosetta overhead
./scripts/act-ci.sh --native
# Best combo for full test runs (avoids OOM)
./scripts/act-ci.sh --native --batch

When to use each flag:

  • --native — faster, less memory for all runs
  • --batch — runs tests in groups of 10 to avoid Docker OOM on large test suites
  • --full — runs every test (slow; use only when you need complete coverage locally)

Terminal window
brew install act

Start Docker Desktop, then verify:

Terminal window
docker info

act pulls images on first run. Ensure you have network access and the repo-root .actrc keeps --pull=false only after the initial pull. Remove --pull=false from the repo-root .actrc temporarily to force a fresh pull:

Terminal window
act pull_request -W .github/workflows/act-ci.yml --pull=always

Reduce memory pressure with --native (skips Rosetta overhead) and --batch (limits concurrent tests):

Terminal window
./scripts/act-ci.sh --native --batch

Also increase Docker Desktop memory in Settings → Resources → Memory (recommend 8 GB+).

Terminal window
./scripts/act-ci.sh --clean

Or manually:

Terminal window
docker ps -aq --filter "name=act-" | xargs docker rm -f

Playwright browsers re-downloading every run

Section titled “Playwright browsers re-downloading every run”

act mounts a host-side Playwright cache at ~/.cache/ms-playwright-{arch}. Verify the directory exists:

Terminal window
ls ~/.cache/ms-playwright-amd64 # default
ls ~/.cache/ms-playwright-arm64 # with --native

If the directory is missing, act creates it automatically on the next run and Playwright downloads browsers once.

quality-gates fails with a passing job name

Section titled “quality-gates fails with a passing job name”

The aggregate gate requires all jobs to succeed. Check the output for the specific failing job name and re-run it in isolation:

Terminal window
./scripts/act-ci.sh --job <failing-job>

pnpm run preflight is the canonical pre-push gate; it composes the fast local gates and the ./scripts/act-ci.sh Docker run into a single command:

pnpm run format # fix formatting
pnpm run preflight # full local CI gate (lint/format/type-check/build/smart tests/CEM/changesets + Docker CI via act-ci.sh)
git push # only after preflight passes

If preflight fails, fix the errors before pushing. Do not skip this gate. See Build Pipeline for the full gate list and the production CI counterpart.