Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Filter by Categories
About Article
Analyze Data
Archive
Best Practices
Better Outputs
Blog
Code Optimization
Code Quality
Command Line
Course
Daily tips
Dashboard
Data Analysis & Manipulation
Data Engineer
Data Visualization
DataFrame
Delta Lake
DevOps
DuckDB
Environment Management
Feature Engineer
Git
Jupyter Notebook
LLM
LLM Tools
Machine Learning
Machine Learning & AI
Machine Learning Tools
Manage Data
MLOps
Natural Language Processing
Newsletter Archive
NumPy
Pandas
Polars
PySpark
Python Helpers
Python Tips
Python Utilities
Scrape Data
SQL
Testing
Time Series
Tools
Visualization
Visualization & Reporting
Workflow & Automation
Workflow Automation

How to Test GitHub Actions Locally with act

How to Test GitHub Actions Locally with act

Table of Contents

The Slow Feedback Loop

If you have used GitHub Actions, you have probably gone through something like this:

  1. Edit .github/workflows/test.yml
  2. Commit, push, and wait 2-5 minutes for the runner to start
  3. Discover a typo or missing dependency
  4. Fix it, commit again, push again, wait again

After a few iterations, you can easily spend 15 to 30 minutes debugging what turns out to be a small YAML mistake.

The root issue is that there is no way to run GitHub Actions locally. Every test requires a push, a wait, and a review of logs.

For data scientists iterating on CI workflows for training or validation, this feedback loop slows everything down.

In this article, you will learn how to use act to run GitHub Actions locally and catch errors in seconds instead of minutes.

Stay Current with CodeCut

Actionable Python tips, curated for busy data pros. Skim in under 2 minutes, three times a week.

What Is act?

act is an open-source CLI tool that runs GitHub Actions workflows on your local machine. It uses Docker containers to simulate GitHub’s runner environment, so the same workflow YAML that runs on GitHub runs locally with no changes.

The key difference is speed. Instead of waiting for GitHub to provision a hosted VM, act uses a pre-pulled Docker image and starts execution in seconds.

Setup

Installing act

act requires Docker to run workflows in containers. Install Docker Desktop if you don’t have it already.

Install act with Homebrew:

brew install act

For other package managers, see the act installation guide.

Verify the installation:

act --version
act version 0.2.86

Creating a Sample ML Project

To demonstrate act, let’s create a small ML project with data validation and model tests.

Project structure:

ml-project/
├── .github/
│   └── workflows/
│       └── test.yml
├── src/
│   ├── train.py
│   └── validate.py
├── tests/
│   ├── test_data.py
│   └── test_model.py
└── requirements.txt

The project includes:

  • src/train.py: Trains a RandomForest on Iris and saves the model
  • src/validate.py: Asserts data has the expected shape and value ranges
  • tests/test_data.py: Validates data schema and distribution
  • tests/test_model.py: Checks model accuracy and reproducibility
  • requirements.txt: Lists dependencies (scikit-learn, pytest)

Here is the GitHub Actions workflow:

# .github/workflows/test.yml
name: ML Tests
on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: python src/validate.py

  test:
    runs-on: ubuntu-latest
    needs: validate
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: pytest tests/ -v

This workflow defines two jobs that run in sequence:

  • validate: Runs data checks first
  • test: Runs the pytest suite only if validation passes

On GitHub, you’d wait several minutes to see whether these jobs succeed. With act, you can test them in seconds.

Running Workflows Locally

Listing Available Workflows

Before running anything, you can inspect what workflows and jobs are available:

act -l
Stage  Job ID    Job name  Workflow name  Workflow file  Events
0      validate  validate  ML Tests       test.yml       push,pull_request
1      test      test      ML Tests       test.yml       push,pull_request

This shows both jobs, their execution order (stage 0 runs first), and the events that trigger them.

To list only jobs triggered by a specific event such as a pull request, run:

act -l pull_request

Running a Workflow

To run all workflows triggered by a push event, run:

act push

The first time you run this command, act prompts you to select a default image size (Large, Medium, or Micro). Pick Medium for a good balance between compatibility and download size.

You’ll see familiar GitHub Actions log output in your terminal:

[ML Tests/validate] 🚀  Start image=catthehacker/ubuntu:act-latest
[ML Tests/validate]   ✅  Success - Main actions/checkout@v4
[ML Tests/validate]   ✅  Success - Main actions/setup-python@v5
[ML Tests/validate]   ✅  Success - Main pip install -r requirements.txt
[ML Tests/validate]   ✅  Success - Main python src/validate.py
[ML Tests/validate] 🏁  Job succeeded
[ML Tests/test    ] 🚀  Start image=catthehacker/ubuntu:act-latest
[ML Tests/test    ]   ✅  Success - Main actions/checkout@v4
[ML Tests/test    ]   ✅  Success - Main actions/setup-python@v5
[ML Tests/test    ]   ✅  Success - Main pip install -r requirements.txt
[ML Tests/test    ]   ✅  Success - Main pytest tests/ -v
| ============================== 5 passed in 1.21s ===============================
[ML Tests/test    ] 🏁  Job succeeded

Like GitHub Actions, act resolves job dependencies and runs each step in order, but in local Docker containers instead of hosted VMs. If a step fails, you see the error immediately without pushing to GitHub.

The same approach works for other events like act pull_request, act schedule, or act workflow_dispatch.

Running a Specific Job

If you are debugging a failing test, it is time-consuming to run the entire workflow. Instead, target a specific job by name by using the -j flag:

act -j test

Or run a specific workflow file by using the -W flag:

act -W .github/workflows/test.yml

Dry Run

Downloading Docker images and executing steps takes time. If you only want to check your workflow YAML for syntax errors, use a dry run:

act -n
*DRYRUN* [ML Tests/validate]   ✅  Success - Main actions/checkout@v4
*DRYRUN* [ML Tests/validate]   ✅  Success - Main actions/setup-python@v5
*DRYRUN* [ML Tests/validate]   ✅  Success - Main pip install -r requirements.txt
*DRYRUN* [ML Tests/validate]   ✅  Success - Main python src/validate.py
*DRYRUN* [ML Tests/validate] 🏁  Job succeeded
*DRYRUN* [ML Tests/test    ]   ✅  Success - Main pytest tests/ -v
*DRYRUN* [ML Tests/test    ] 🏁  Job succeeded

The *DRYRUN* prefix indicates that no containers were created. This is useful for verifying that your workflow file is valid and that jobs run in the expected order before committing to a full execution.

Managing Secrets

Many workflows need API keys or credentials. For example, you might add a Slack notification step to the existing workflow:

# .github/workflows/test.yml
name: ML Tests
on: [push, pull_request]

jobs:
  validate:
    ...
  test:
    ...
  notify:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - name: Notify
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK }}
          webhook-type: incoming-webhook
          payload: |
            text: "Tests passed"

The notify job references secrets.SLACK_WEBHOOK. Configuring secrets through GitHub’s repository settings UI is slow and requires multiple clicks. With act, you can provide them locally with a single flag.

Passing Secrets Directly

Pass secrets using the -s flag:

act -s SLACK_WEBHOOK=https://hooks.slack.com/test

For sensitive values, omit the value and act will prompt you securely (input is not echoed):

act -s SLACK_WEBHOOK

Loading Secrets from a File

For projects with multiple secrets, store them in a .env file:

# .env (add this to .gitignore!)
SLACK_WEBHOOK=https://hooks.slack.com/services/T00/B00/xxx
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE

Then load all secrets at once:

act --secret-file .env

This is easier than repeating -s for every secret, especially as the number of secrets grows.

Summary

act removes the commit-push-wait loop from GitHub Actions development. Here are the key commands:

CommandDescription
act pushRun all push-triggered workflows
act -lList all available workflows and jobs
act -j testRun a specific job by name
act -W path/to/workflow.ymlRun a specific workflow file
act -nDry run without executing
act -s KEY=VALUEPass a secret
act --secret-file .envLoad secrets from file

For a complete reference, see the act documentation.

Related Tutorials


📚 Want to go deeper? Learning new techniques is the easy part. Knowing how to structure, test, and deploy them is what separates side projects from real work. My book shows you how to build data science projects that actually make it to production. Get the book →

Stay Current with CodeCut

Actionable Python tips, curated for busy data pros. Skim in under 2 minutes, three times a week.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

Work with Khuyen Tran

Work with Khuyen Tran