Skip to content

bfulton/localmost

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

localmost

localmost icon

Run most of your builds locally
Self-hosted GitHub Actions runners for your Mac

Save time and money on macOS builds

GitHub charges $0.062/minute for their cheapest macos runners. A 20-minute build costs $1.24. Push twice a day and you're spending over $50/month on CI. Run the same jobs on your MacBook and they finish in less than half the time for $0.

Here's what some open source projects would save:

Project Builds/mo Runners p90 Cost/mo
Alamofire ~9 macos-15 8m $14
mattermost-mobile ~225 macos-15-large 25m $321
SwiftFormat ~72 macos-15 4m $22

Pricing as of January 2026. Costs calculated from jobs via generate-benchmarks.sh.

Local builds are also faster. Based on XcodeBenchmark:

Runner Time
GitHub macos-latest M1 x3 ($0.06/m) 838s
GitHub macos-15-large Intel x12 ($0.08/m) 955s
GitHub macos-15-xlarge M2 Pro x5 ($0.10/m) 339s
MacBook Air M2 x8 (2022) 202s
MacBook Pro M4 Max x16 (2024) 77s

Why else use localmost?

Features:

  • Automatic fallback — workflows detect when your Mac is available; fall back to hosted runners when it's not
  • One-click setup — no terminal commands, no manually generating registration tokens
  • Lid-close protection — close your laptop without killing in-progress jobs
  • Multi-runner parallelism — run 1-8 concurrent jobs
  • Network isolation — runner traffic is proxied through an allowlist (GitHub, npm, PyPI, etc.)
  • Filesystem sandboxing — runner processes can only write to their working directory
  • Resource-aware scheduling — automatically pause runners when on battery or during video calls

What It Is

localmost is a macOS app that manages GitHub's official actions-runner binary. It handles authentication, registration, runner process lifecycle, and automatic fallback — the tedious parts of self-hosted runners.

Security note: Running CI jobs on your local machine has inherent risks—especially for public repos that accept external contributions. localmost sandboxes runner processes and restricts network access, but these are not VM-level isolation. See SECURITY.md for details on the threat model and recommendations.

Architecture

localmost architecture diagram

  • Runner proxy — maintains long-poll sessions with GitHub's broker to receive job assignments
  • Runner pool — 1-8 worker instances that execute jobs in sandboxed environments
  • HTTP proxy — allowlist-based network isolation for runner traffic (GitHub, npm, PyPI, etc.)
  • Build cache — persistent tool cache shared across job runs (Node.js, Python, etc.)

Workflow Integration

Add to your GitHub Actions workflow to automatically use localmost when available:

permissions:
  actions: read
  contents: read

jobs:
  check:
    uses: bfulton/localmost/.github/workflows/check.yaml@main

  build:
    needs: check
    runs-on: ${{ needs.check.outputs.runner }}
    steps:
      - uses: actions/checkout@v4
      # ... your steps
Prefer not to reference an external workflow? Copy the check inline:
jobs:
  check:
    runs-on: ubuntu-latest
    outputs:
      runner: ${{ steps.check.outputs.runner }}
    steps:
      - id: check
        run: |
          HEARTBEAT="${{ vars.LOCALMOST_HEARTBEAT }}"
          if [ -n "$HEARTBEAT" ]; then
            HEARTBEAT_TIME=$(date -d "$HEARTBEAT" +%s 2>/dev/null || echo "0")
            AGE=$(($(date +%s) - HEARTBEAT_TIME))
            if [ "$AGE" -lt 90 ]; then
              echo "runner=self-hosted" >> $GITHUB_OUTPUT
              exit 0
            fi
          fi
          echo "runner=macos-latest" >> $GITHUB_OUTPUT

How It Works

The check workflow uses a simple heartbeat mechanism:

  • localmost automatically updates a LOCALMOST_HEARTBEAT variable in your repo/org every 60 seconds
  • The workflow reads this variable and checks the timestamp
  • If the timestamp is less than 90 seconds old → use self-hosted
  • Otherwise → fall back to macos-latest (or your configured fallback)
  • On clean exit, localmost immediately marks the heartbeat stale so workflows fall back without waiting

This fallback-to-cloud design is intentional: if your Mac is asleep, offline, or the heartbeat is stale for any reason, workflows continue running on GitHub-hosted runners rather than waiting or failing.

GitHub App Permissions

localmost uses a GitHub App for authentication. During installation, you'll be asked to grant the following permissions:

Required Permissions

Permission Level Purpose
Administration Read & Write Register and remove self-hosted runners on repositories
Actions Read & Write Check workflow status and cancel running jobs
Metadata Read Access basic repository information (required by GitHub for all apps)
Self-hosted runners (org) Read & Write Register and remove self-hosted runners at the organization level

Why Administration Permission?

GitHub's permission model requires Administration: Read & Write for managing self-hosted runners at the repository level. This is the same permission scope needed by the official actions/runner registration process.

While this permission could theoretically allow other administrative actions, localmost only uses it for:

  • Generating runner registration tokens (POST /repos/{owner}/{repo}/actions/runners/registration-token)
  • Removing runners when you stop them (DELETE /repos/{owner}/{repo}/actions/runners/{runner_id})

localmost is open source — you can verify this by searching for actions/runners in the codebase.

For organization-level runners, the narrower Self-hosted runners: Read & Write permission is used instead of Administration.

Repository Access

During GitHub App installation, you choose which repositories to grant access to:

  • All repositories - localmost can register runners for any repo in your account/org
  • Only select repositories - limit access to specific repos you want to run locally

You can change this at any time in your GitHub settings under Applications > Installed GitHub Apps > localmost > Configure.

Token Security

localmost uses OAuth device flow authentication. Your access token is:

  • Encrypted with macOS Keychain and stored locally
  • Scoped only to the repositories you explicitly grant access to
  • Revocable at any time from your GitHub settings

CLI Companion

localmost includes a command-line interface for controlling the app from your terminal:

# Start/stop the app
localmost start
localmost stop

# Check runner status
localmost status

# Pause the runner (stops accepting new jobs)
localmost pause

# Resume the runner
localmost resume

# View recent job history
localmost jobs

Installing the CLI

From the app menu: localmost → Install Command Line Tool...

This creates a symlink in /usr/local/bin so you can use localmost from any terminal. You'll be prompted for your administrator password.

For development builds, use npm link instead.

The CLI communicates with the running app via a Unix socket. Most commands require the app to be running - use localmost start to launch it first.

Development

Built with Electron + React/TypeScript. Requires Node.js 18+.

# Clone and install dependencies
git clone https://github.com/bfulton/localmost.git
cd localmost
npm install

# Start the app in development mode
npm start

# Run tests
npm test

# Build for macOS (creates .dmg)
npm run make

Roadmap

Current release: 0.3.0 — Test Locally, Secure by Default

  • Run workflows locally before pushing with localmost test
  • Declarative sandbox policies with .localmostrc
  • Secure secrets management in macOS Keychain
  • Environment comparison with GitHub runners

Future feature ideas:

  • Trusted contributors for public repos - Control which repos can run on your machine based on their contributor list. Options: never build public repos, only build repos where all contributors are trusted (default: you + known bots, customizable), or always build (with high-friction confirmation). Repos with untrusted contributors fail with a clear error.
  • Quick actions - Re-run failed job, cancel all jobs.
  • Spotlight integration - Check status or pause builds from Spotlight.
  • Artifact inspector - Browse uploaded artifacts without leaving the app.
  • Disk space monitoring - Warn or pause when disk is low, auto-clean old work dirs.
  • Linux and Windows host support - Run self-hosted runners on non-Mac machines for projects that need them.
  • Higher parallelism cap - Parallelize proxy registration to support 16+ concurrent runners (currently capped at 8 due to serial registration time).
  • Ephemeral VM isolation - Run each job in a fresh lightweight VM for stronger isolation between jobs.

Bugs and quick improvements:

  • Fix "build on unknown" race where jobs don't get links

About

Run most of your builds locally — self-hosted GitHub Actions runners for your Mac

Resources

License

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •