Skip to main content

Running GitHub Actions Locally with act: 5x Faster Development

· 12 min read
Thanh-Giang Tan Nguyen
Founder at RIVER

GitHub Actions are powerful for automating bioinformatics pipelines, but waiting 5-10 minutes for each cloud run is painful during development. act lets you run GitHub Actions workflows locally on your machine in seconds, slashing feedback time by 5x.

In this post, we'll explore act, a command-line tool that runs GitHub Actions locally using Docker. Perfect for testing ML pipelines, gene expression analysis, and CI/CD workflows before pushing to GitHub.

Why Test GitHub Actions Locally?

Traditional GitHub Actions workflow:

  1. Write workflow → Push to GitHub
  2. Wait 5-10 minutes for cloud runner
  3. Workflow fails → Fix locally → Push again
  4. Repeat steps 2-3 (multiple times!)

Total feedback cycle: 30+ minutes for a simple fix

With act:

  1. Write workflow
  2. Run locally with act → Instant feedback (10-30 seconds)
  3. Debug and iterate locally
  4. Push confident code to GitHub

Total feedback cycle: 5 minutes


Part 1: Installation and Setup

The easiest way to install act globally is using Pixi, a fast package manager for Python and system tools:

Install act globally with Pixi:

pixi global install act

That's it! Pixi handles downloading the correct binary for your OS (macOS, Linux, or Windows).

Verify Installation

act --version
# Output: act version 0.2.X

Why use Pixi?

  • ✅ Cross-platform (macOS, Linux, Windows)
  • ✅ No system dependencies needed
  • ✅ One command: pixi global install act
  • ✅ Automatic updates: pixi global upgrade act
  • ✅ Isolated from system Python/packages
  • ✅ Same tool you use for project management

Alternative Installation Methods

If you prefer not to use Pixi, here are other options:

macOS (Homebrew)

brew install act

Linux

# Debian/Ubuntu
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | bash

# Or with pacman (Arch)
pacman -S act

Windows (PowerShell)

choco install act
# Or if using Scoop:
scoop install act

Manual Installation

Step 2: Install Docker

act requires Docker to run workflows in containers (just like GitHub's cloud runners).

Option 1: Docker Desktop (Easiest)

  • Download Docker Desktop for macOS or Windows
  • Install and start the application
  • Docker will automatically be available in your terminal

Option 2: Install Docker with Pixi (if you prefer package managers)

# Install Docker tools in your Pixi environment
pixi global install docker

Option 3: Linux (System Package)

# Debian/Ubuntu
sudo apt-get install docker.io docker-compose
sudo usermod -aG docker $USER # Add current user to docker group
newgrp docker # Activate group changes

Verify Docker Installation

docker --version
docker run hello-world # Should complete successfully

Troubleshooting Docker:

  • macOS/Windows: Ensure Docker Desktop is running (check system tray icon)
  • Linux: Check if docker daemon is running: sudo systemctl start docker
  • Permission denied: Add user to docker group: sudo usermod -aG docker $USER

Step 3: Verify Setup

Test that act can find your GitHub workflows:

cd /path/to/your/repo
act --list

Expected output:

Stage  Job ID  Job Name  Workflow Name     Workflow File           Events
0 test test test-workflow .github/workflows/test.yml push

If no workflows appear, ensure .github/workflows/ exists with .yml files.

If your project uses Pixi, add act as a task for easy execution:

Add to pixi.toml:

[tasks]
ci = "act push"
ci-test = "act pull_request"
ci-debug = "act -v push"

Then run with Pixi:

pixi run ci           # Run GitHub Actions locally
pixi run ci-debug # With verbose output

Check your Pixi setup:

pixi info
# Should show: act is available globally

Part 2: Basic Usage - Running Workflows Locally

Simple Example: Python Test Workflow

Let's create a minimal GitHub Actions workflow and test it with act.

Create .github/workflows/test.yml:

name: Python Tests
on:
push:
branches: [main, develop]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install dependencies
run: |
pip install pytest numpy pandas

- name: Run tests
run: |
pytest tests/ -v

Run locally with act:

act push

Expected output:

[Python Tests/test] 🚀 Start image pull...
[Python Tests/test] 🐳 Docker pull requested ghcr.io/catthehacker/ubuntu:act-latest
[Python Tests/test] ✓ Image pull complete
[Python Tests/test] 🚀 Start container...
[Python Tests/test] ⭐ Run Main actions/checkout@v3
[Python Tests/test] ✓ Complete job

Running Specific Workflows

List all available workflows:

act --list

Run a specific job:

act --job test

Run a specific workflow:

act --workflow test.yml

Simulate a different event (e.g., pull_request):

act pull_request

Part 3: Environment Variables and Secrets

Passing Environment Variables

act provides several ways to pass variables:

Method 1: Command-line flag

act -e event.json

Where event.json contains:

{
"repository": {
"name": "my-repo",
"owner": {
"login": "myusername"
}
}
}

Method 2: .actrc file (in repo root)

# .actrc
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest
-l

Method 3: Shell environment variables

export MY_VAR="value"
export ANOTHER_VAR="another"
act push

Working with Secrets

GitHub Actions use secrets for sensitive data. With act, you can pass secrets locally:

Method 1: .secrets file (in repo root)

# .secrets
MY_SECRET=super_secret_value
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
API_KEY=your_api_key_here

Important: Add .secrets to .gitignore to prevent committing secrets!

echo ".secrets" >> .gitignore

Method 2: Command-line secret flag

act -s MY_SECRET=value -s GITHUB_TOKEN=token

Workflow using secrets:

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Use secret
run: |
echo "API Key: ${{ secrets.API_KEY }}"
echo "Token: ${{ secrets.GITHUB_TOKEN }}"

Run with secrets:

act --secret-file .secrets

Part 4: Docker and Container Management

Understanding act Container Images

act uses pre-built Docker images that mimic GitHub's cloud runners. Default images are large (~15GB) but highly compatible.

Available Docker images:

# Ubuntu (recommended for bioinformatics)
ghcr.io/catthehacker/ubuntu:act-latest

# Debian (smaller, faster)
ghcr.io/catthehacker/ubuntu:full-latest

# Minimal (smallest, fast)
ubuntu:latest # Docker Hub

Specifying Docker Images

Method 1: -P flag in command

act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:full-latest

Method 2: .actrc file

# .actrc
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:full-latest
-P windows-latest=ghcr.io/catthehacker/windows:full-latest

Method 3: Command-line shorthand

# Use minimal image
act --container-architecture linux/amd64

Pre-pulling Docker Images

Large images take time on first run. Pre-pull them:

# Pull the image once
docker pull ghcr.io/catthehacker/ubuntu:act-latest

# Now act will use cached image (much faster)
act push

Managing Disk Space

Docker images consume significant space. Clean up unused images:

# Remove unused images
docker image prune -a

# Remove all containers
docker container prune -a

# Check disk usage
docker system df

Part 5: Performance Optimization Tips

1. Use Smaller Docker Images

Instead of full Ubuntu, use minimal images:

Before (slow - ~15GB):

act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest

After (fast - ~2GB):

act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:full-latest

Performance impact: ~2-3x faster

2. Cache Dependencies

GitHub Actions support caching. Leverage it in act:

Workflow with caching:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Cache pip dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install Python dependencies
run: pip install -r requirements.txt

- name: Run tests
run: pytest tests/

Performance impact: First run ~30s, subsequent runs ~5s (cache hit)

3. Run Jobs in Parallel

By default, act runs jobs sequentially. Enable parallel execution:

# Run all jobs in parallel
act --parallel 4

Workflow with multiple jobs:

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- run: pytest tests/unit/

integration-tests:
runs-on: ubuntu-latest
steps:
- run: pytest tests/integration/

lint:
runs-on: ubuntu-latest
steps:
- run: pylint src/

Sequential time: 30s + 40s + 10s = 80s Parallel time (--parallel 3): ~40s

4. Use act -l (List) Mode

For quick workflow checks without running:

act --list

Shows all jobs without executing them.

5. Rebuild Docker Image

Cache can become stale. Rebuild:

act --reuse-containers  # Reuse running containers
act --rebuild # Rebuild image from scratch

Real Example: Bioinformatics Gene Expression Pipeline

Let's build a complete ML pipeline workflow and test it locally with act.

Workflow File: .github/workflows/ml-pipeline.yml

name: ML Pipeline - Gene Expression Analysis
on:
push:
branches: [main, develop]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Cache pip packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt') }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-xdist

- name: Lint with pylint
run: pylint src/ --exit-zero
continue-on-error: true

- name: Run unit tests
run: pytest tests/unit/ -v --cov=src --cov-report=xml

- name: Run integration tests
run: pytest tests/integration/ -v

- name: Upload coverage reports
uses: codecov/codecov-action@v3
if: matrix.python-version == '3.12'
with:
files: ./coverage.xml
flags: unittests

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Run Bandit security check
run: |
pip install bandit
bandit -r src/ -v --exit-code 0
continue-on-error: true

Running Locally with act

Run all jobs:

act push

Expected output:

[ML Pipeline - Gene Expression Analysis/test] 🚀 Start image pull...
[ML Pipeline - Gene Expression Analysis/test] ⭐ Run actions/checkout@v3
[ML Pipeline - Gene Expression Analysis/test] ✓ Step 'Checkout code' completed
[ML Pipeline - Gene Expression Analysis/test] ⭐ Run Set up Python 3.10
[ML Pipeline - Gene Expression Analysis/test] ✓ Step 'Set up Python 3.10' completed
[ML Pipeline - Gene Expression Analysis/test] ⭐ Run Install dependencies
[ML Pipeline - Gene Expression Analysis/test] ✓ Step 'Install dependencies' completed
[ML Pipeline - Gene Expression Analysis/test] ⭐ Run Run unit tests
...
[ML Pipeline - Gene Expression Analysis/test] ✓ Complete job
[ML Pipeline - Gene Expression Analysis/security] ⭐ Run Bandit security check
[ML Pipeline - Gene Expression Analysis/security] ✓ Complete job

Run specific Python version:

act push --job test --matrix python-version=3.12

Run only security tests:

act push --job security

Debugging Workflows with act

Enable Verbose Logging

act -v push

Shows detailed output including Docker commands and environment variables.

Debug with Interactive Shell

If a step fails, enter the container:

# Create a failing step
steps:
- run: |
echo "Debug info:"
env | sort
ls -la

Or use act with shell access:

act -b  # Use local container (not rebuilding)

Inspect Container After Failure

Keep container running after failure:

# View logs
docker ps -a

# Enter the container
docker exec -it <container_id> /bin/bash

Common Issues and Solutions

Issue: "Cannot connect to Docker daemon"

# Solution: Start Docker
sudo systemctl start docker # Linux
open /Applications/Docker.app # macOS

Issue: "Not enough space for Docker images"

# Solution: Clean up
docker system prune -a --volumes

Issue: "Workflow runs but tests fail locally but pass on GitHub"

# Solution: Check Python versions match
python --version
act --list # Verify Python version in workflow

Performance Comparison: Local vs Cloud

TaskLocal (act)GitHub CloudSpeedup
First run45s300s+6-7x
Subsequent runs (cached)8s250s+30x
Development iteration2 min (10 runs)50 min25x
Cost$0$0.008/minN/A

Key insight: For a typical development session with 10 iterations, act saves 48 minutes of waiting time!


Best Practices for CI/CD with act

1. Test Locally Before Pushing

# Before git push
act push
# If all pass, then:
git push origin main

2. Match GitHub Runner Environment

# Use same image as GitHub
act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest

3. Commit .actrc to Repository

# .actrc (can be committed)
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:full-latest
-l

But keep .secrets in .gitignore:

# .gitignore
.secrets

4. Use Matrix Strategy for Multiple Versions

strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, macos-latest]

Test all combinations locally before push.

5. Document Dependencies

# requirements.txt
pytest>=7.0.0
numpy>=1.24.0
pandas>=1.5.0
scipy>=1.10.0

Keep updated so act can replicate exact environment.

If you use Pixi for project management, keep your global tools there too:

# Install act globally with Pixi
pixi global install act

# Update act easily
pixi global upgrade act

# See all globally installed tools
pixi global list

Benefits for bioinformatics workflows:

  • Single package manager for all tools
  • Easy version control (pixi.lock)
  • Reproducible environments across team
  • No system package conflicts

Create a .pixi.toml for your project:

[dependencies]
python = "3.12"
pytest = ">=7.0.0"
numpy = ">=1.24.0"
pandas = ">=1.5.0"

[tasks]
test = "pytest tests/ -v"
lint = "pylint src/"
ci = "act push" # Run GitHub Actions locally

Then your team can just run:

pixi run test      # Run tests
pixi run lint # Run linter
pixi run ci # Run GitHub Actions locally

Key Takeaways

  1. act runs GitHub Actions locally → test before pushing to GitHub
  2. 5-30x faster feedback → iterate quickly during development
  3. Docker-based → identical environment to GitHub Cloud runners
  4. Supports secrets and environment variables → test real workflows
  5. Performance optimization → use smaller images, caching, parallel jobs
  6. Free and open-source → no additional costs beyond your machine

What's Next?

Now that you can test workflows locally with act, consider:

  • Setting up pre-commit hooks to run act automatically
  • Creating reusable workflow templates for bioinformatics pipelines
  • Integrating act into your team's development process
  • Exploring GitHub Actions Marketplace for bioinformatics tools

References

Happy local testing! 🚀