Specifying Custom Platforms for Action Runners in act Using `--platform`

Use act -P <platform>=<image> to override the default Docker image for any GitHub Actions runner label, storing persistent mappings in .actrc to avoid repeating flags on every run.

The nektos/act tool executes GitHub Actions workflows locally by spinning up Docker containers that emulate GitHub-hosted runner environments. When you need to test workflows against custom container images or specific operating system versions, specifying custom platforms for action runners becomes essential. This guide explains how the --platform flag works under the hood, from CLI parsing in cmd/root.go to image resolution in pkg/runner/run_context.go.

How the --platform Flag Works Internally

The -P flag accepts key-value pairs in the format platform-name=docker-image, allowing you to override act's built-in defaults through a multi-stage resolution process that merges user input with predefined mappings.

Flag Registration and Input Collection

In cmd/root.go (lines 82-84), the flag is registered as a string array that collects raw user input before any processing occurs:

rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform")

These values populate the platforms []string field in the Input struct defined in cmd/input.go (lines 22-23). This struct merely stores the raw slice for later use; no validation happens at this stage.

Merging Defaults with User Overrides

The newPlatforms() function in cmd/platforms.go (lines 7-21) initializes a map with four Ubuntu LTS defaults, then applies your overrides with case-insensitive key handling:

func (i *Input) newPlatforms() map[string]string {
    platforms := map[string]string{
        "ubuntu-latest": "node:16-buster-slim",
        "ubuntu-22.04":  "node:16-bullseye-slim",
        "ubuntu-20.04":  "node:16-buster-slim",
        "ubuntu-18.04":  "node:16-buster-slim",
    }

    for _, p := range i.platforms {
        parts := strings.Split(p, "=")
        if len(parts) == 2 {
            platforms[strings.ToLower(parts[0])] = parts[1]
        }
    }
    return platforms
}

Runtime Image Resolution

When the CLI creates the runner configuration in cmd/root.go (lines 626-629), it passes the merged platform map via Platforms: input.newPlatforms(). During workflow execution, the runsOnImage() method in pkg/runner/run_context.go (lines 49-56) selects the appropriate container:

func (rc *RunContext) runsOnImage(ctx context.Context) string {
    for _, platformName := range rc.runsOnPlatformNames(ctx) {
        image := rc.Config.Platforms[strings.ToLower(platformName)]
        if image != "" {
            return image
        }
    }
    return ""
}

If no custom image is found, the runner falls back to the built-in defaults defined in newPlatforms(). If even those are missing for the requested platform, the job is skipped with a warning about an unsupported platform (lines 17-24).

Default Runner Images in act

By default, act maps common runs-on labels to lightweight Node.js images to minimize download times and disk usage. The ubuntu-latest, ubuntu-20.04, and ubuntu-18.04 labels all resolve to node:16-buster-slim, while ubuntu-22.04 uses node:16-bullseye-slim. These images provide a minimal environment sufficient for basic JavaScript and shell-based workflows, though they lack many tools present in actual GitHub-hosted runners.

Specifying Custom Platforms via Command Line

Override defaults temporarily by passing one or more -P flags when invoking act:


# Use a custom image for a specific Ubuntu version

act -P ubuntu-20.04=myorg/custom-act-image:latest

# Override multiple platforms simultaneously

act \
  -P ubuntu-18.04=myorg/act-ubuntu18:latest \
  -P ubuntu-22.04=myorg/act-ubuntu22:latest

Persistent Configuration with .actrc

Store platform mappings in a configuration file to avoid repeating flags on every invocation. Act searches for .actrc in three locations (in order of precedence): ./.actrc, ~/.actrc, and ~/.config/act/actrc.

Add your custom platform lines to the file:

-P ubuntu-20.04=myorg/custom-act-image:latest
-P ubuntu-22.04=myorg/act-ubuntu22:latest

On first run, if no configuration file exists, act executes the defaultImageSurvey function in cmd/root.go to interactively prompt for preferred images, then writes the resulting -P lines to the first available config file location. This survey only appears once per machine.

Summary

  • The --platform flag (-P) accepts platform-name=docker-image pairs to override act's built-in defaults for any runner label.
  • Internal resolution flows from cmd/root.go (flag parsing) → cmd/platforms.go (map merging with lowercase normalization) → pkg/runner/run_context.go (runtime lookup via runsOnImage()).
  • Default images map Ubuntu LTS releases to lightweight Node.js containers (node:16-buster-slim or node:16-bullseye-slim).
  • Persistent configuration is stored in .actrc files, automatically loaded from the current directory or home directory.
  • Fallback behavior uses built-in defaults if no custom mapping matches, or skips the job if the platform is entirely unrecognized.

Frequently Asked Questions

Can I use --platform to run macOS or Windows containers?

No. The --platform flag only affects Linux-based Docker images. Act cannot execute macOS or Windows containers because Docker requires a Linux host kernel. While you can map macos-latest to a Linux image (e.g., -P macos-latest=ubuntu:22.04), this executes a Linux container that merely pretends to be macOS for workflow parsing purposes, not actual macOS.

Why does act use Node.js images as defaults instead of full GitHub-hosted runner images?

The default node:16-buster-slim images prioritize fast download speeds and minimal disk usage for local development. Full GitHub-hosted runner images exceed 10GB and contain hundreds of pre-installed tools that most local workflows do not require. You can override these with full images using -P ubuntu-latest=nektos/act-environments-ubuntu:18.04 when your workflow depends on specific pre-installed software.

How does act handle case sensitivity in platform names?

Platform names are normalized to lowercase before lookup. In cmd/platforms.go, the newPlatforms() function converts both the default keys and user-supplied keys to lowercase using strings.ToLower(). Similarly, runsOnImage() in pkg/runner/run_context.go lowercases the workflow's runs-on value before checking against the configuration map, ensuring Ubuntu-20.04 and ubuntu-20.04 resolve identically.

What happens if I specify a platform mapping that isn't used in my workflow?

Act ignores unused platform mappings without error. The platform map is built globally at startup in newPlatforms(), but runsOnImage() only queries the map when processing a job's actual runs-on field. Unused mappings consume negligible memory and do not affect execution speed or container startup time.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →