Skip to content
Back to blog Test GitHub Actions Locally with Act

Test GitHub Actions Locally with Act

CICDDevOps

Test GitHub Actions Locally with Act

Push. Wait for runner. Fail on line 47. Fix typo. Push. Wait. Fail on line 52.

Sound familiar? Debugging GitHub Actions by pushing commits is painful. Each iteration takes minutes, clutters your git history, and burns CI minutes.

Act runs GitHub Actions locally on your machine. Change workflow, run act, see results in seconds. No push required.

TL;DR

Code Repository: All code from this post is available at github.com/moabukar/blog-code/act-locally-test-github-actions

  • Act runs GitHub Actions workflows locally using Docker
  • Instant feedback loop - seconds instead of minutes
  • Works with most GitHub Actions out of the box
  • Some features (secrets, artifacts, matrix) need configuration
  • Essential for workflow development and debugging

Code Repository: All code from this post is available at github.com/moabukar/blog-code/act-github-actions


Installing Act

# macOS
brew install act

# Linux (via GitHub releases)
curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash

# Windows (via Chocolatey)
choco install act-cli

# Verify
act --version

Act requires Docker to be installed and running.


Basic Usage

# Run the default event (push)
act

# Run a specific event
act pull_request
act workflow_dispatch
act schedule

# Run a specific job
act -j build
act -j test

# Run a specific workflow file
act -W .github/workflows/ci.yml

# Dry run (show what would happen)
act -n

Your First Run

Given this workflow:

# .github/workflows/ci.yml
name: CI
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: |
          echo "Running tests..."
          npm test

Run it locally:

$ act

[CI/test] 🚀  Start image=catthehacker/ubuntu:act-latest
[CI/test]   🐳  docker pull image=catthehacker/ubuntu:act-latest
[CI/test] 🧪  Run actions/checkout@v4
[CI/test]   ✅  Success - actions/checkout@v4
[CI/test] 🧪  Run Run tests
[CI/test]   💬  echo "Running tests..."
Running tests...
[CI/test]   💬  npm test
...
[CI/test]   ✅  Success - Run tests

Runner Images

Act uses Docker images to simulate GitHub-hosted runners. The default is a minimal image.

Image Sizes

# Micro (default) - minimal, fast to download
act -P ubuntu-latest=catthehacker/ubuntu:act-latest

# Medium - includes more common tools
act -P ubuntu-latest=catthehacker/ubuntu:act-22.04

# Large - closest to actual GitHub runners (~20GB)
act -P ubuntu-latest=catthehacker/ubuntu:full-22.04

Configure Default in .actrc

# ~/.actrc or .actrc in repo root
-P ubuntu-latest=catthehacker/ubuntu:act-22.04
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
-P ubuntu-20.04=catthehacker/ubuntu:act-20.04

Custom Runner Image

# Dockerfile.act
FROM catthehacker/ubuntu:act-22.04

# Add your specific tools
RUN apt-get update && apt-get install -y \
    postgresql-client \
    redis-tools

# Pre-install language runtimes
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs
docker build -f Dockerfile.act -t my-act-runner .
act -P ubuntu-latest=my-act-runner

Handling Secrets

From Environment Variables

# Pass individual secrets
act -s GITHUB_TOKEN=$GITHUB_TOKEN -s NPM_TOKEN=$NPM_TOKEN

# Or use a secrets file
act --secret-file .secrets
# .secrets (add to .gitignore!)
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxxxx
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=xxx

Using 1Password or Other Secret Managers

# Pipe from 1Password
act -s GITHUB_TOKEN=$(op read "op://Vault/GitHub Token/credential")

Environment Variables

# Pass env vars
act --env FOO=bar --env BAZ=qux

# From file
act --env-file .env

# GitHub-provided variables (automatically set)
# GITHUB_SHA, GITHUB_REF, GITHUB_REPOSITORY, etc.
# .env
NODE_ENV=test
DATABASE_URL=postgres://localhost:5432/test

Matrix Builds

Act supports matrix strategies:

jobs:
  test:
    strategy:
      matrix:
        node: [18, 20, 22]
        os: [ubuntu-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: node --version
# Run all matrix combinations
act

# Run specific matrix combination
act -j test --matrix node:20

Services (Docker Compose Style)

GitHub Actions supports service containers. Act handles them:

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7
        ports:
          - 6379:6379
    steps:
      - name: Test DB connection
        run: |
          pg_isready -h localhost -p 5432
          redis-cli -h localhost ping
# Services start automatically
act -j test

Artifacts

Act can handle artifacts with some configuration:

- uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: coverage/

- uses: actions/download-artifact@v4
  with:
    name: test-results
# Specify artifact server
act --artifact-server-path /tmp/artifacts

# Or use local directory
mkdir -p /tmp/act-artifacts
act --artifact-server-path /tmp/act-artifacts

Debugging Workflows

Verbose Output

# Show all output
act -v

# Even more verbose
act -vv

Interactive Shell

# Drop into shell on failure
act -j build --reuse

# Then manually debug
docker exec -it act-CI-build /bin/bash

Step Through

# Stop after each step
act --step-by-step

Common Gotchas

1. GITHUB_TOKEN Permissions

# Workflow uses github.token
- uses: actions/checkout@v4
  with:
    token: ${{ secrets.GITHUB_TOKEN }}
# Create a PAT and pass it
act -s GITHUB_TOKEN=ghp_xxxx

2. Actions That Require GitHub Context

Some actions only work on GitHub:

  • github-script (limited support)
  • create-release (needs GitHub API)
  • deploy-pages (GitHub-specific)

Workaround: Mock the behavior locally or skip:

- name: Create Release
  if: ${{ !env.ACT }}  # Skip when running in act
  uses: softprops/action-gh-release@v1

3. Docker-in-Docker

Actions that build Docker images need Docker socket access:

# Mount Docker socket
act -P ubuntu-latest=catthehacker/ubuntu:act-latest \
    --container-daemon-socket /var/run/docker.sock

4. Self-Hosted Runner Features

Features like caching to GitHub’s cache service won’t work locally:

- uses: actions/cache@v4  # Works, but uses local cache only
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('**/package-lock.json') }}

Practical Workflow

1. Create .actrc for Your Repo

# .actrc
-P ubuntu-latest=catthehacker/ubuntu:act-22.04
--secret-file .secrets
--env-file .env
--artifact-server-path /tmp/artifacts

2. Add .secrets to .gitignore

# .gitignore
.secrets
.env.local

3. Document Required Secrets

# .secrets.example (commit this)
GITHUB_TOKEN=your-github-token
NPM_TOKEN=your-npm-token
AWS_ACCESS_KEY_ID=your-aws-key
AWS_SECRET_ACCESS_KEY=your-aws-secret

4. Test Before Push

# Quick validation
act -n  # Dry run

# Full test
act

# Specific job
act -j deploy

Speed Tips

1. Use Smaller Images

# Instead of full (20GB)
act -P ubuntu-latest=catthehacker/ubuntu:act-latest  # ~500MB

2. Cache Docker Images

# Pre-pull images
docker pull catthehacker/ubuntu:act-22.04
docker pull node:20
docker pull postgres:15

3. Reuse Containers

# Don't recreate containers between runs
act --reuse

4. Skip Unnecessary Steps

- name: Deploy to Production
  if: github.ref == 'refs/heads/main' && !env.ACT
  run: ./deploy.sh

Integration with Make

# Makefile
.PHONY: ci ci-push ci-pr

# Run CI workflow locally
ci:
	act push

# Run PR workflow
ci-pr:
	act pull_request

# Run with verbose output
ci-debug:
	act -v

# Dry run
ci-dry:
	act -n

# Specific job
ci-test:
	act -j test

ci-build:
	act -j build

When Act Isn’t Enough

Act covers 90% of use cases. For the remaining 10%:

LimitationAlternative
GitHub-specific APIsTest against actual GitHub on a test repo
OIDC/Workload IdentityCan’t be simulated locally
Large runners (16+ CPU)Use actual GitHub runners
macOS/Windows runnersNot supported in act
GitHub Packages authUse real GitHub with PAT

Quick Reference

# Basic run
act

# Specific event
act pull_request

# Specific job
act -j build

# With secrets
act -s GITHUB_TOKEN=$TOKEN

# Secrets from file
act --secret-file .secrets

# Environment variables
act --env NODE_ENV=test

# Dry run
act -n

# Verbose
act -v

# Use specific image
act -P ubuntu-latest=catthehacker/ubuntu:act-22.04

# Reuse containers
act --reuse

# List available jobs
act -l

Conclusion

Act transforms GitHub Actions development from “push and pray” to instant local feedback. For workflow development:

  1. Write workflow
  2. Run act
  3. Fix issues
  4. Repeat until green
  5. Push once, confident it works

Your git history will thank you. Your CI minutes will thank you. Your sanity will thank you.


References

Found this helpful?

Comments