1. Architecture Overview
act is a Go CLI that reads GitHub Actions workflow files (.github/workflows/*.yml), parses them into an execution plan of stages and jobs, and runs each job inside a Docker container configured to emulate GitHub's hosted runner environment. The tool reproduces environment variables, filesystem layout, event payloads, matrix fan-out, secret masking, and action resolution — all driven from a single binary with zero external dependencies beyond Docker.
How It Works
CLI (cmd/root.go)
├── Workflow Parser (pkg/model/)
│ ├── planner.go — reads YAML, builds Plan of Stages/Runs
│ ├── workflow.go — Job, Step, Strategy, Matrix deserialization
│ └── action.go — action.yml parsing (node/docker/composite)
│
├── Runner Engine (pkg/runner/)
│ ├── runner.go — NewPlanExecutor, concurrent stage dispatch
│ ├── job_executor.go — single job lifecycle (setup → steps → post)
│ ├── run_context.go — job-level state (env, secrets, contexts)
│ ├── step_run.go — `run:` steps (shell commands)
│ ├── step_action_local.go — `uses: ./path` local actions
│ ├── step_action_remote.go — `uses: owner/repo@ref` remote actions
│ ├── step_docker.go — `uses: docker://` steps
│ ├── action_composite.go — composite action expansion
│ ├── expression.go — ${{ }} expression evaluation
│ └── reusable_workflow.go — workflow_call support
│
├── Container Layer (pkg/container/)
│ ├── docker_run.go — container lifecycle (create/start/exec/remove)
│ ├── docker_pull.go — image pulling with auth
│ ├── docker_build.go — local Dockerfile builds
│ ├── docker_network.go — network create/remove
│ ├── host_environment.go — host-mode execution (no container)
│ └── docker_socket.go — socket discovery and validation
│
├── Services
│ ├── Artifact Server (pkg/artifacts/) — v1 + v4 HTTP endpoints
│ ├── Cache Server (pkg/artifactcache/) — GitHub Actions cache API
│ └── Expression Parser (pkg/exprparser/) — GitHub expression language
│
└── Utilities
├── pkg/common/ — executor framework, logging, auth tokens
├── pkg/model/ — GitHub context, planner, workflow types
├── pkg/filecollector/ — tar/copy with gitignore support
├── pkg/workflowpattern/ — glob pattern matching for triggers
├── pkg/schema/ — JSON Schema validation for workflows
├── pkg/lookpath/ — cross-platform executable lookup
└── pkg/gh/ — GitHub CLI token retrieval
Component Inventory
| Component | Path | Role |
|---|---|---|
| CLI entry point | main.go → cmd/root.go |
Cobra command setup, flag parsing, .actrc loading |
| Workflow model | pkg/model/ |
YAML parsing, planning, job/step type system |
| Runner engine | pkg/runner/ |
Job execution, step dispatch, expression evaluation |
| Container layer | pkg/container/ |
Docker API interactions, host env fallback |
| Artifact server | pkg/artifacts/ |
Local HTTP server emulating GitHub Artifacts API |
| Cache server | pkg/artifactcache/ |
Local HTTP server emulating GitHub Actions Cache API |
| Expression parser | pkg/exprparser/ |
${{ }} expression interpreter |
| Schema validator | pkg/schema/ |
JSON Schema validation for workflow/action YAML |
| File collector | pkg/filecollector/ |
Tar archive creation with .gitignore support |
| Workflow patterns | pkg/workflowpattern/ |
Path filter pattern matching |
Technology Stack
| Layer | Technology |
|---|---|
| Language | Go 1.25 |
| CLI framework | spf13/cobra + spf13/pflag |
| Docker client | moby/moby/client |
| Git operations | go-git/go-git/v5 |
| YAML parsing | gopkg.in/yaml.v3 |
| Logging | sirupsen/logrus |
| Schema validation | rhysd/actionlint + xeipuuv/gojsonschema |
| Interactive prompts | AlecAivazis/survey/v2 |
| Persistent cache DB | go.etcd.io/bbolt via timshannon/bolthold |
| Protobuf (artifacts v4) | google.golang.org/protobuf |
Note
act uses the Docker Engine API directly — it does not shell out to the
dockerCLI. This means Docker Desktop, Colima, Podman (with Docker-compatible socket), or any Docker API-compatible daemon works.
Data Flow: Workflow Execution
1. CLI parses args + .actrc → Input struct
2. WorkflowPlanner reads .github/workflows/*.yml → Plan (Stages → Runs)
3. Runner.NewPlanExecutor dispatches Stages sequentially
4. Each Stage runs its Runs (jobs) concurrently (up to --concurrent-jobs)
5. JobExecutor for each Run:
a. Creates Docker container with platform image
b. Copies workspace (or bind-mounts)
c. Resolves and caches actions
d. Executes steps: run → shell exec, uses → action dispatch
e. Evaluates ${{ }} expressions, if conditions
f. Collects outputs, manages step results
g. Runs post-steps in reverse order
h. Removes container (unless --reuse)
6. Artifact/Cache servers handle uploads/downloads during execution
2. System Requirements
| Component | Requirement |
|---|---|
| Docker Engine | v17.09+ (API v1.25+), v20.10+ recommended |
| Docker API | v1.41+ required for --container-architecture |
| Go (build only) | 1.25+ |
| Git | Required for repository operations |
| Operating System | Linux, macOS, Windows (WSL2 recommended) |
| Platform support | linux/amd64 (native), linux/arm64, others via QEMU |
Important
On Windows, act requires WSL2 or Docker Desktop with WSL2 backend. Native Windows containers are not supported. The Docker socket location varies by platform — see Docker Socket Discovery below.
3. Configuration Reference
CLI Flags — Complete Reference
Operational Flags
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
[event] |
positional | push |
Event name to trigger. Falls back to push; if workflow handles only one event, uses that |
|
--job |
-j |
string | Run a specific job by ID | |
--watch |
-w |
bool | false |
Watch working directory and re-run on file changes |
--list |
-l |
bool | false |
List workflows (tabular: Stage, Job ID, Job name, Workflow, File, Events) |
--graph |
-g |
bool | false |
Draw ASCII workflow dependency graph |
--validate |
bool | false |
Validate workflow YAML without running | |
--strict |
bool | false |
Enable strict schema validation | |
--dryrun |
-n |
bool | false |
Validate workflows without creating containers |
--detect-event |
bool | false |
Auto-detect event type from the first workflow |
Container Configuration
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--platform |
-P |
string[] | Map runner labels to Docker images (e.g., -P ubuntu-latest=image:tag) |
|
--container-architecture |
string | host arch | OS/architecture for containers (e.g., linux/amd64) |
|
--container-daemon-socket |
string | auto-detect | Docker daemon socket URI (e.g., unix://~/.docker/run/docker.sock, or - to disable mount) |
|
--container-options |
string | Extra docker run options passed to job containers |
||
--container-cap-add |
string[] | Kernel capabilities to add (e.g., SYS_PTRACE). Deprecated — use --container-options --cap-add=SYS_PTRACE |
||
--container-cap-drop |
string[] | Kernel capabilities to remove. Deprecated — use --container-options |
||
--privileged |
bool | false |
Run containers in privileged mode. Deprecated — use --container-options --privileged |
|
--userns |
string | User namespace mode. Deprecated — use --container-options |
||
--network |
string | host |
Docker network name for job containers | |
--reuse |
-r |
bool | false |
Keep containers after successful runs |
--rm |
bool | false |
Auto-remove containers/volumes on workflow failure | |
--bind |
-b |
bool | false |
Bind-mount working directory instead of copying |
--pull |
-p |
bool | true |
Pull Docker images even if present locally |
--rebuild |
bool | true |
Rebuild local Docker action images even if present |
Warning
--privileged,--userns,--container-cap-add, and--container-cap-dropare deprecated. Use--container-optionswith Docker CLI syntax instead. These flags will be removed in a future version.
Secrets, Env, Variables, and Inputs
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--secret |
-s |
string[] | Secret with optional value (e.g., -s NAME=value or -s NAME) |
|
--secret-file |
string | .secrets |
Path to secrets file (dotenv or YAML) | |
--env |
string[] | Environment variable with optional value | ||
--env-file |
string | .env |
Path to environment file (dotenv or YAML) | |
--var |
string[] | Variable with optional value | ||
--var-file |
string | .vars |
Path to variables file | |
--input |
string[] | Action input (e.g., --input name=value) |
||
--input-file |
string | .input |
Path to inputs file | |
--insecure-secrets |
bool | false |
Print secrets in logs (not recommended) |
Path and Workflow Configuration
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--directory |
-C |
string | . |
Working directory |
--workflows |
-W |
string | ./.github/workflows/ |
Path to workflow files |
--no-recurse |
bool | false |
Don't search workflow subdirectories | |
--eventpath |
-e |
string | Path to event JSON file | |
--defaultbranch |
string | auto-detect | Main branch name | |
--remote-name |
string | origin |
Git remote name for repo URL detection |
Actor and GitHub Instance
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--actor |
-a |
string | nektos/act |
User that triggered the event |
--github-instance |
string | github.com |
GitHub instance (for GHE) | |
--replace-ghe-action-with-github-com |
string[] | GHE actions to redirect to github.com | ||
--replace-ghe-action-token-with-github-com |
string | PAT for private github.com actions (used with above) |
Artifact Server
| Flag | Type | Default | Description |
|---|---|---|---|
--artifact-server-path |
string | (disabled) | Storage directory. Server only starts if set |
--artifact-server-addr |
string | outbound IP | Bind address |
--artifact-server-port |
string | 34567 |
Listen port |
Cache Server
| Flag | Type | Default | Description |
|---|---|---|---|
--no-cache-server |
bool | false |
Disable the built-in cache server |
--cache-server-path |
string | ~/.cache/actcache |
Cache storage directory |
--cache-server-addr |
string | outbound IP | Bind address |
--cache-server-port |
uint16 | 0 (random) |
Listen port |
--cache-server-external-url |
string | External URL if behind a proxy |
Action Cache
| Flag | Type | Default | Description |
|---|---|---|---|
--action-cache-path |
string | ~/.cache/act |
Directory for cached actions and host workspaces |
--action-offline-mode |
bool | false |
Use cached actions without fetching. Disables --pull |
--use-new-action-cache |
bool | false |
Enable Go-git-based action cache |
--local-repository |
string[] | Map remote action refs to local paths |
Output and Logging
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--verbose |
-v |
bool | false |
Debug-level output |
--quiet |
-q |
bool | false |
Suppress step output |
--json |
bool | false |
JSON-formatted log output | |
--log-prefix-job-id |
bool | false |
Use job ID instead of full name in log prefix |
Execution Control
| Flag | Type | Default | Description |
|---|---|---|---|
--matrix |
string[] | Filter matrix combinations (e.g., --matrix os:ubuntu-latest) |
|
--no-skip-checkout |
bool | false |
Run actions/checkout instead of local file copy |
--use-gitignore |
bool | true |
Exclude .gitignore paths from container copy |
--concurrent-jobs |
int | num CPUs | Maximum concurrent jobs |
Utility Flags
| Flag | Type | Description |
|---|---|---|
--bug-report |
bool | Print system info for bug reports |
--man-page |
bool | Print generated man page to stdout |
--list-options |
bool | Print flags as JSON |
--version |
bool | Print version |
--help |
bool | Print help |
Environment Variables
| Variable | Purpose |
|---|---|
DOCKER_HOST |
Docker daemon URI (overridden by --container-daemon-socket) |
ACT_DISABLE_VERSION_CHECK |
Set to 1 to suppress startup version notices |
XDG_CACHE_HOME |
Override cache base directory (default: ~/.cache) |
CLICOLOR |
Controls colored output in graph drawing |
NORAW |
Disables raw terminal mode for Docker attach |
Tip
Variables
ACT_ACTOR,ACT_REPOSITORY, andACT_OWNERare used in tests to override the actor, repository, and owner. They are not part of the normal user-facing API.
Configuration File Format
The .actrc file is a line-per-flag format with environment variable expansion:
# Comments are not supported — every non-empty line is treated as an argument
-P ubuntu-latest=catthehacker/ubuntu:act-latest
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
--container-architecture linux/amd64
--artifact-server-path ${HOME}/.act-artifacts
--action-offline-mode
Lines starting with - are split at the first whitespace to separate flag from value. Lines without - prefix are ignored when split=true (the default for config loading).
4. Deployment & Scaling
Building from Source
git clone https://github.com/nektos/act.git
cd act
go build -o act .
# Verify
./act --version
The VERSION file at the repository root is embedded at compile time via //go:embed.
GoReleaser Configuration
act uses GoReleaser (.goreleaser.yml) for cross-platform release builds. Release artifacts include binaries for Linux, macOS, and Windows across amd64/arm64, plus a Chocolatey NuGet package (act-cli.nuspec).
Docker Socket Discovery
act searches for the Docker socket in a platform-specific order:
DOCKER_HOSTenvironment variable (if set)--container-daemon-socketflag (if provided)- Common socket locations checked in order:
/var/run/docker.sock/run/podman/podman.sock$HOME/.docker/run/docker.sock$HOME/.colima/default/docker.sock$XDG_RUNTIME_DIR/docker.sock$XDG_RUNTIME_DIR/podman/podman.sock\\.\pipe\docker_engine(Windows)
Note
If
--container-daemon-socketis set to-, act will not bind-mount the Docker socket into job containers. This is useful for security-sensitive environments where container-in-container access should be prevented.
Scaling Considerations
- Concurrent jobs: Controlled by
--concurrent-jobs. Defaults to CPU count. Each job creates its own container. - Disk usage: Each job copies the workspace into the container (unless
--bindis used). Large repos multiply disk usage by job count. - Network: The
hostnetwork mode (default) shares the host network stack. Named networks provide isolation but require address configuration for artifact/cache servers. - Cache server: Uses BoltDB (
bbolt) for metadata and filesystem for blobs. Not designed for multi-process concurrent access.
5. Operations
Cache Management
Action cache (~/.cache/act/ by default):
- Stores cloned action repositories (bare git repos when using
--use-new-action-cache) - Host workspaces for jobs
- Clean with:
rm -rf ~/.cache/act/
Artifact cache (~/.cache/actcache/ by default):
- BoltDB database + artifact blob files
- Stores GitHub Actions cache entries (keys, versions, content)
- Clean with:
rm -rf ~/.cache/actcache/
Tip
The cache server supports garbage collection internally. Caches that haven't been accessed are candidates for removal based on the BoltDB
CompleteandUsedAttimestamps.
Monitoring Execution
Use --verbose (-v) for debug-level logs showing:
- Docker API calls (pull, create, start, exec)
- Expression evaluation results
- Action resolution and caching decisions
- Environment variable loading
Use --json for structured log output parseable by tools like jq:
act --json 2>&1 | jq 'select(.msg | contains("Run Main"))'
Note
Log output goes to stderr. Step output (the actual command outputs) goes to stdout. Use
--quietto suppress step output.
Version Notices
act checks https://api.nektosact.com/notices on each run (unless ACT_DISABLE_VERSION_CHECK=1). The check uses HTTP conditional GET (ETag caching) to minimize bandwidth. Notices are displayed after the workflow run with a 1-second timeout — they never block execution.
6. Performance
Image Pull Optimization
- Default:
--pull=true— always pulls images (ensures latest version) - Fast iteration:
--pull=false— uses local image if available - Offline:
--action-offline-mode— no network at all
Workspace Copy vs Bind
| Mode | Flag | Speed | Safety |
|---|---|---|---|
| Copy (default) | none | Slower start, full isolation | Workflows cannot modify host files |
| Bind mount | --bind / -b |
Instant start | Workflows can modify host files |
Action Caching
Two cache implementations:
| Cache | Flag | Method | Best for |
|---|---|---|---|
| Default | none | git clone via shell |
Reliability |
| Go-git | --use-new-action-cache |
In-process bare git + go-git | Speed, offline mode |
The Go-git cache resolves refs to SHAs, stores bare repos, and extracts files via tree walking — avoiding shell subprocess overhead.
Offline Mode
--action-offline-mode skips all network fetches:
- Docker pull is forced off
- Action cache uses existing clones without fetch
- Requires prior run to populate caches
7. Security
Secret Handling
- Secrets are masked in output by default using pattern matching against the
*[]stringmasks context --insecure-secretsdisables masking (logs will show secret values)- Secrets are case-insensitive (stored as uppercase keys internally)
- Secret values can be provided interactively (terminal password prompt) if not set inline or via environment
Caution
--insecure-secretsshould never be used in CI pipelines or shared terminal sessions. Secret values will be visible in all log output.
Docker Socket Access
By default, act bind-mounts the Docker socket into job containers. This provides Docker-in-Docker capability but gives containers full Docker API access.
- Use
--container-daemon-socket -to disable socket mounting - Use
--container-cap-dropor--container-options --cap-drop=ALLto restrict capabilities
GitHub Token
If no GITHUB_TOKEN secret is provided, act attempts to retrieve one from the gh CLI by running gh auth token. This uses the logged-in user's token.
Warning
Be aware that the auto-detected token may be scoped to a different GitHub instance (e.g., GHE) than expected. Pass tokens explicitly in multi-instance environments. (See GitHub issue #6077)
Authorization Tokens (Internal)
The artifact cache server uses JWT tokens for request authorization:
- Tokens are signed with HMAC-SHA256 using a randomly generated key per session
- Token claims include
TaskID,RunID,JobID, andac(action cache scope) - Tokens are validated on every cache API request
Path Traversal Protection
The artifact server uses safeResolve() to prevent path traversal attacks:
func safeResolve(baseDir string, relPath string) string {
return filepath.Join(baseDir, filepath.Clean(filepath.Join(string(os.PathSeparator), relPath)))
}
Symlink resolution in action steps enforces depth limits (max depth %d of symlinks exceeded) and boundary checks (symlink tries to access file outside of).
Input Validation
- Workflow YAML is validated against JSON Schema (
pkg/schema/workflow_schema.json,pkg/schema/action_schema.json) - Expression parser validates function calls against known function signatures with arity checks
- Job names are validated against the regex pattern
[a-zA-Z_][a-zA-Z0-9_-]*
8. Developer Internals
Repository Structure
act/
├── main.go — entry point, embeds VERSION
├── main_test.go — smoke test
├── Makefile — build targets
├── go.mod / go.sum — Go module definition
├── VERSION — version string (embedded at compile)
├── install.sh — shell installer script
├── .goreleaser.yml — release automation config
├── .golangci.yml — linter configuration
├── .actrc — default config (project-level)
├── act-cli.nuspec — Chocolatey package spec
│
├── cmd/ — CLI layer
│ ├── root.go — Cobra root command, all flags, run logic
│ ├── input.go — Input struct (all CLI options)
│ ├── secrets.go — Secret parsing with interactive prompt
│ ├── platforms.go — Default platform mapping
│ ├── list.go — `--list` tabular output
│ ├── graph.go — `--graph` ASCII art
│ ├── notices.go — Version notice fetching (nektosact.com)
│ └── dir.go — Home/cache directory resolution
│
├── pkg/model/ — Workflow data model
│ ├── workflow.go — Workflow, Job, Step, Strategy, Defaults types
│ ├── action.go — Action definition (runs.using, inputs, outputs)
│ ├── planner.go — WorkflowPlanner: read YAML → Plan/Stages/Runs
│ ├── github_context.go — GitHub context (ref, sha, repository, owner)
│ ├── job_context.go — Job context type
│ ├── step_result.go — Step result status enum
│ └── anchors.go — YAML anchor/alias cycle detection
│
├── pkg/runner/ — Execution engine
│ ├── runner.go — Runner interface, Config struct, plan executor
│ ├── job_executor.go — Job lifecycle (container → steps → cleanup)
│ ├── run_context.go — RunContext: state, env, binds, GitHub context
│ ├── step.go — Step interface, conditional evaluation
│ ├── step_run.go — Shell command execution
│ ├── step_action_local.go — Local action dispatch
│ ├── step_action_remote.go— Remote action fetch + dispatch
│ ├── step_docker.go — docker:// URL action dispatch
│ ├── step_factory.go — Step type routing
│ ├── action.go — Action reading (action.yml/yaml/Dockerfile)
│ ├── action_composite.go — Composite action step expansion
│ ├── action_cache.go — GoGitActionCache (bare git repos)
│ ├── action_cache_offline_mode.go — Offline cache wrapper
│ ├── local_repository_cache.go — Local repo override cache
│ ├── expression.go — Expression evaluator wiring
│ ├── reusable_workflow.go — workflow_call support
│ ├── command.go — Workflow command processing (set-env, add-path, etc.)
│ └── logger.go — Job/step logging with secret masking
│
├── pkg/container/ — Docker abstraction layer
│ ├── container_types.go — NewContainerInput, FileEntry, build/pull inputs
│ ├── docker_run.go — Container lifecycle (create/start/exec/copy/remove)
│ ├── docker_pull.go — Image pull with auth
│ ├── docker_build.go — Dockerfile build executor
│ ├── docker_images.go — Image existence/removal
│ ├── docker_network.go — Network create/remove
│ ├── docker_volume.go — Volume removal
│ ├── docker_auth.go — Docker registry auth from config
│ ├── docker_cli.go — Container option parsing (1,135 lines)
│ ├── docker_logger.go — Docker log message parsing
│ ├── docker_socket.go — Socket discovery + SocketAndHost
│ ├── docker_stub.go — Stub implementations (unsupported platforms)
│ ├── host_environment.go — Host execution mode (no Docker)
│ ├── executions_environment.go — ExecutionsEnvironment interface
│ ├── linux_container_environment_extensions.go — Container path helpers
│ ├── parse_env_file.go — Environment file parser (GITHUB_ENV format)
│ ├── util.go — UID/GID detection (Linux)
│ ├── util_windows.go — Windows UID stub
│ ├── util_openbsd_mips64.go — OpenBSD stub
│ └── util_plan9.go — Plan9 stub
│
├── pkg/artifacts/ — Artifact server (v1 + v4)
│ ├── server.go — HTTP routes for upload/download (v1 API)
│ ├── artifacts_v4.go — Protobuf-based v4 API routes
│ └── artifact.pb.go — Generated protobuf types
│
├── pkg/artifactcache/ — Cache server
│ ├── handler.go — HTTP handler, BoltDB storage, JWT auth
│ ├── model.go — Cache request/response types
│ ├── storage.go — Blob storage (filesystem)
│ └── doc.go — Package documentation
│
├── pkg/exprparser/ — Expression language interpreter
│ ├── interpreter.go — ${{ }} evaluation engine
│ └── functions.go — Built-in functions (contains, startsWith, hashFiles, etc.)
│
├── pkg/schema/ — Workflow validation
│ ├── schema.go — JSON Schema validator
│ ├── workflow_schema.json — GitHub Actions workflow schema
│ └── action_schema.json — GitHub Actions action.yml schema
│
├── pkg/filecollector/ — File collection for container copy
│ └── file_collector.go — TarCollector, CopyCollector with gitignore
│
├── pkg/workflowpattern/ — Path filter patterns
│ └── workflow_pattern.go — Glob-style pattern matching for triggers
│
├── pkg/common/ — Shared utilities
│ ├── executor.go — Executor functional pattern (Pipeline, Parallel, Conditional)
│ ├── context.go — Graceful cancellation (SIGINT/SIGTERM)
│ ├── logger.go — Context-scoped logging
│ ├── auth.go — JWT token generation/validation
│ ├── draw.go — ASCII box/arrow drawing
│ ├── dryrun.go — Dry-run context flag
│ ├── file.go — CopyFile/CopyDir helpers
│ ├── job_error.go — Job error context
│ ├── line_writer.go — Line-buffered writer
│ ├── outbound_ip.go — Outbound IP detection
│ └── cartesian.go — Cartesian product (matrix expansion)
│
├── pkg/lookpath/ — Cross-platform executable lookup
│ ├── lp_unix.go — Unix PATH lookup
│ ├── lp_windows.go — Windows PATH + PATHEXT lookup
│ ├── lp_plan9.go — Plan9 stub
│ └── lp_js.go — JS/WASM stub
│
└── pkg/gh/ — GitHub CLI integration
└── gh.go — Token retrieval via `gh auth token`
Architecture Deep Dive
Executor Pattern
The core abstraction is common.Executor — a function func(context.Context) error. All operations (container create, step run, file copy) are executors composed with combinators:
NewPipelineExecutor(a, b, c)— sequentialNewParallelExecutor(n, a, b, c)— concurrent with limitnNewConditionalExecutor(cond, ifTrue, ifFalse)— branching.Then(next)— chaining.If(cond)/.IfNot(cond)— conditional execution.Finally(cleanup)— guaranteed cleanup.OnError(handler)— error handling
This makes the execution pipeline declarative and composable while keeping error propagation clean.
Workflow Planning
WorkflowPlanner (pkg/model/planner.go) reads all YAML files, builds a dependency graph based on needs: declarations, and produces a Plan:
type Plan struct {
Stages []*Stage // Stages run sequentially
}
type Stage struct {
Runs []*Run // Runs within a stage run concurrently
}
type Run struct {
Workflow *Workflow
JobID string
}
Planning supports filtering by event name (PlanEvent), job ID (PlanJob), or all jobs (PlanAll).
Expression Evaluation
The pkg/exprparser/ package implements the full GitHub Actions expression language:
- Literals:
null,true,false, numbers, strings - Operators:
==,!=,&&,||,!,<,>,<=,>= - Contexts:
github,env,secrets,steps,matrix,needs,inputs,vars,runner,job - Functions:
contains(),startsWith(),endsWith(),format(),join(),toJSON(),fromJSON(),hashFiles(),success(),failure(),cancelled(),always()
The interpreter uses rhysd/actionlint for parsing and implements its own evaluation with type coercion matching GitHub's behavior.
Container Options Parsing
pkg/container/docker_cli.go (1,135 lines) implements a comprehensive Docker CLI option parser that accepts all standard docker run flags. This is used when --container-options is passed to transform CLI-style flags into Docker API container.Config and container.HostConfig structs. Supported flags include volumes, ports, devices, memory limits, healthchecks, network config, security options, and more.
Action Resolution
Actions are resolved in order:
- Local actions (
uses: ./path): Read from workspace, follow symlinks (depth-limited) - Remote actions (
uses: owner/repo@ref): Clone from GitHub (or cache), extract action.yml - Docker URL (
uses: docker://image): Pull and run directly - Composite actions: Expand steps recursively with isolated run contexts
The action reader looks for action.yml → action.yaml → Dockerfile (synthetic action) → trampoline.js (synthetic node action with args).
Service Containers
Service containers (job.services) are started before the job and connected to the job's Docker network. Health checks are supported — the job waits for services to become healthy before proceeding with steps.
Module Reference
cmd/root.go — CLI Entry Point (776 lines)
The central orchestration file. createRootCommand() defines all 60+ flags. newRunCommand() is the main run handler that:
- Discovers Docker socket
- Loads envs, secrets, vars, inputs from files
- Auto-retrieves
GITHUB_TOKENfromghCLI - Plans workflow execution
- Configures runner with
Configstruct - Starts artifact/cache servers
- Dispatches execution (or watch mode)
pkg/runner/runner.go — Runner Engine (226 lines)
Defines Runner interface with NewPlanExecutor() method. The runnerImpl dispatches stages sequentially and jobs within each stage concurrently (up to ConcurrentJobs). Failed jobs are reported with Job '%s' failed errors.
pkg/runner/run_context.go — Job State (1,153 lines)
The RunContext struct holds all job-level state:
- Container reference and environment
- Step results and outputs
- GitHub context (event, ref, sha, etc.)
- Expression evaluator factory
- Service container management
- Binds and mounts resolution
Key method Executor() builds the job lifecycle executor: container setup → step execution → cleanup.
pkg/runner/expression.go — Expression Wiring (401 lines)
Bridges the runner context with the expression parser. Populates the EvaluationEnvironment with all GitHub Actions contexts (github.*, env.*, secrets.*, steps.*, matrix.*, needs.*, runner.*, job.*, inputs.*, vars.*).
pkg/container/docker_run.go — Container Lifecycle (913 lines)
Implements the full Docker container lifecycle:
NewContainer()— creates container referenceCreate()— Docker API container creation with configStart()— starts container with optional attachExec()— runs commands inside containerCopy()/CopyDir()— file transfer to containerRemove()— cleanupGetDockerClient()— creates Docker API client fromDOCKER_HOST
pkg/artifactcache/handler.go — Cache Server (569 lines)
Implements the GitHub Actions cache API:
POST /_apis/artifactcache/cache— reserve cache entryPATCH /_apis/artifactcache/caches/:id— upload cache blobPOST /_apis/artifactcache/caches/:id— commit cacheGET /_apis/artifactcache/cache— lookup cache by key/version
Uses BoltDB for metadata storage and filesystem for blob storage. JWT authorization protects all endpoints.
Code Patterns and Conventions
| Pattern | Usage |
|---|---|
| Executor composition | All side effects wrapped in common.Executor functions |
| Context-scoped state | Logger, dry-run flag, job errors stored in context.Context |
| Interface-based DI | ExecutionsEnvironment interface enables Docker/host/stub swapping |
| Functional options | Executor combinators (.Then(), .If(), .Finally()) |
| Embed directives | VERSION file, trampoline.js, schema JSON embedded at compile |
| Table-driven tests | All test files use t.Run() with test case tables |
| Platform build tags | util_windows.go, util_openbsd_mips64.go, lp_unix.go etc. |
Testing Strategy
- Unit tests: Extensive in every package. Table-driven with
testify/assert. - Integration tests:
pkg/runner/runner_test.go(782 lines) runs real workflows against Docker. - Test fixtures:
pkg/runner/testdata/contains 100+ workflow scenarios covering:- Matrix strategies, composite actions, reusable workflows
- Service containers, environment files, secret handling
- Docker actions (local, remote, URL), Node.js actions (12/16/20)
- Shell variants (bash, sh, python, pwsh, cmd)
- Edge cases (spaces in paths, nested composites, symlinks)
- Mock containers:
container_mock_test.goprovides test doubles for the container interface. - Linting: golangci-lint (
.golangci.yml), codespell (.codespellrc), markdownlint, megalinter, prettierrc.
Potential Improvements
Based on code analysis and community issues:
- OIDC token support: Currently unsupported — actions needing
ACTIONS_ID_TOKEN_REQUEST_URLfail. Would require a local OIDC provider or mock. - Job-level
strategy.job-index/strategy.job-total: Missing from strategy context (GitHub issue #5976). - Git worktree support:
.gitfile reconstitution for container mounts (GitHub issue #6074). - Concurrent instance locking: No coordination between multiple act processes (GitHub issue #6061).
- Network-scoped artifact/cache servers: When
--networkis nothost, servers need explicit address configuration (GitHub issue #5996). docker_cli.gocomplexity: At 1,135 lines, the container options parser is a large single file that could benefit from decomposition.
Contributing
- Code: Go 1.25+, run
makefor build,make testfor tests - Linting:
golangci-lint run(configuration in.golangci.yml) - Tests: Add test workflows in
pkg/runner/testdata/for new features - Schema: Update
pkg/schema/workflow_schema.json/action_schema.jsonfor new workflow syntax
9. Integration Points
Docker Engine API
act communicates directly with the Docker Engine API via moby/moby/client. Required API operations:
- Container: Create, Start, Remove, Exec, Inspect, CopyToContainer
- Image: Pull, Build, List, Remove
- Network: Create, Remove
- Volume: Remove
- System: Info (for bug reports)
GitHub CLI (gh)
act calls gh auth token to retrieve the user's GitHub token when no GITHUB_TOKEN secret is provided. This is a one-time call wrapped in common.EarlyCancelContext with cancellation support.
Artifact Server Endpoints (Internal)
| Method | Path | Protocol | Purpose |
|---|---|---|---|
POST |
/_apis/pipelines/workflows/:runId/artifacts |
v1 | Create artifact container |
PUT |
/upload/:runId |
v1 | Upload artifact blob |
PATCH |
/_apis/pipelines/workflows/:runId/artifacts |
v1 | Finalize artifact |
GET |
/_apis/pipelines/workflows/:runId/artifacts |
v1 | List artifacts |
GET |
/download/:container |
v1 | Download artifact |
GET |
/artifact/*path |
v1 | Serve artifact file |
v4 endpoints use protobuf-encoded requests at /twirp/github.actions.results.api.v1.ArtifactService/*.
Version Notice API
GET https://api.nektosact.com/notices?os={os}&arch={arch}&version={version}
Returns JSON array of {"level": "info|warning", "message": "..."}. Supports ETag-based conditional GET.
10. Troubleshooting
Diagnostic Checklist
- [ ] Docker daemon is running (
docker info) - [ ] Docker socket is accessible (
ls -la /var/run/docker.sock) - [ ] Workflow files exist at the expected path (
act -l) - [ ] Required secrets are set (
-sor--secret-file) - [ ] Platform mapping matches
runs-onlabels (-P) - [ ] Network connectivity for action downloads (or
--action-offline-mode) - [ ] Sufficient disk space for runner images
Error: cache %d: not reserved
Cause: The cache server received a commit request for a cache ID that was never reserved.
Solution: This is typically a race condition in the action. Retry the workflow. If persistent, check that ACTIONS_CACHE_URL is accessible from within the container.
Warning
When using custom Docker networks (
--network), the cache server's auto-detected address may not be reachable from containers. Use--cache-server-addrwith an address that is routable from the container network.
Error: workflow is not valid. '%s': Job name '%s' is invalid
Cause: Job names must start with a letter or _ and contain only [a-zA-Z0-9_-].
Solution: Rename the job in your workflow YAML to use valid characters.
Error: Actions YAML Schema Validation Error detected
Cause: Workflow YAML doesn't conform to the GitHub Actions schema.
Solution: Visit https://nektosact.com/usage/schema.html for details. Use --validate --strict for detailed error output.
Note
Schema validation errors include context paths showing exactly which YAML key failed validation. The
--strictflag enables additional checks that GitHub might not enforce but represent best practices.
Error: unable to determine outbound IP address
Cause: The cache/artifact server couldn't detect the host's outbound IP for binding.
Solution: Specify addresses explicitly:
act --artifact-server-addr 0.0.0.0 --cache-server-addr 0.0.0.0
11. Reference
Supported Action Types
runs.using value |
Description |
|---|---|
node12 |
Node.js 12 action |
node16 |
Node.js 16 action |
node20 |
Node.js 20 action |
docker |
Docker container action |
composite |
Composite action (nested steps) |
Supported Event Types
act supports all GitHub Actions event types. The event name is passed as the first positional argument. Default is push. If a workflow handles only one event, that event becomes the default.
Expression Functions
| Function | Signature | Description |
|---|---|---|
contains |
contains(search, item) |
Case-insensitive string/array search |
startsWith |
startsWith(str, prefix) |
String prefix check |
endsWith |
endsWith(str, suffix) |
String suffix check |
format |
format(fmt, args...) |
String formatting with {0} placeholders |
join |
join(array, separator) |
Array to string |
toJSON |
toJSON(value) |
Value to JSON string |
fromJSON |
fromJSON(str) |
JSON string to value |
hashFiles |
hashFiles(patterns...) |
SHA-256 hash of matching files |
success |
success() |
Previous steps all succeeded |
failure |
failure() |
Any previous step failed |
cancelled |
cancelled() |
Workflow was cancelled |
always |
always() |
Always true |
Workflow Commands Supported
| Command | Syntax | Description |
|---|---|---|
set-env |
::set-env name=NAME::VALUE |
Set environment variable |
set-output |
::set-output name=NAME::VALUE |
Set step output |
add-path |
::add-path::PATH |
Prepend to PATH |
add-mask |
::add-mask::VALUE |
Mask a value in logs |
save-state |
::save-state name=NAME::VALUE |
Save state for post steps |
stop-commands |
::stop-commands::TOKEN |
Pause command processing |
debug |
::debug::MESSAGE |
Debug message |
warning |
::warning::MESSAGE |
Warning annotation |
error |
::error::MESSAGE |
Error annotation |
group |
::group::TITLE |
Begin log group |
endgroup |
::endgroup:: |
End log group |