Skip to content

Latest commit

 

History

History
169 lines (129 loc) · 6.57 KB

File metadata and controls

169 lines (129 loc) · 6.57 KB

Modal GitHub Runner

CI License: MIT Modal GitHub Actions

Star History Chart

An ephemeral, self-hosted GitHub Actions runner built on Modal. Each job runs in a fresh sandbox, you pay only while jobs execute, and credentials are scoped to a single use.

30-second quickstart

# 1. Create a Modal secret with your GitHub PAT and webhook secret
modal secret create github-secret \
  GITHUB_TOKEN=ghp_xxx \
  WEBHOOK_SECRET=$(openssl rand -hex 32)

# 2. Deploy the runner
modal deploy app.py

# 3. Add a webhook in your GitHub repo settings
#    Point it at the URL modal deploy prints, content type JSON,
#    secret = the WEBHOOK_SECRET from step 1, events = "Workflow jobs"

Then update your workflow file:

runs-on: [self-hosted, modal]

Full deployment walkthrough: DEPLOY.md.

Features

Feature Description
Ephemeral Fresh Modal Sandbox per job. No state leaks between runs.
Zero idle cost No long-running servers. You pay for the seconds a job runs, nothing else.
JIT security Single-use runner tokens via GitHub's generate-jitconfig API.
GPU support T4, L4, A100, A100-80GB, H100 through workflow labels.
Network isolation Outbound CIDR allowlists or full network blocking.
Cache volumes Persistent /cache mount across jobs via Modal Volumes.
Auto-retry Exponential backoff (1s, 2s, 4s) on transient GitHub API failures.
Structured logging JSON logs with job ID, repo, duration, and error context.

Comparison

Modal Runner ARC (K8s) GitHub-hosted
Infrastructure None (serverless) Kubernetes cluster None (managed)
Idle cost Zero Node compute when idle N/A (billed per minute)
Startup time Sub-second sandbox Pod scheduling (seconds to minutes) ~10-20 seconds
GPU support T4, L4, A100, A100-80GB, H100 Requires GPU nodes Limited, macOS only
Horizontal scaling Automatic, no config Requires HPA/cluster autoscaler Automatic
Isolation MicroVM sandbox Container (shared kernel) VM
Kubernetes required No Yes No

ARC is the better choice if you already run a Kubernetes cluster and need deep integration with your existing infrastructure. Modal Runner is simpler if you want something that deploys in under a minute with no ops overhead.

GPU configuration

Add a gpu: label to your workflow's runs-on to request a specific GPU:

jobs:
  train:
    runs-on: [self-hosted, modal, gpu:a100]
    steps:
      - run: python train.py

Supported labels:

Label Hardware
gpu:t4 NVIDIA T4 (16 GB)
gpu:l4 NVIDIA L4 (24 GB)
gpu:a100 NVIDIA A100 (40 GB)
gpu:a100-80gb NVIDIA A100 (80 GB)
gpu:h100 NVIDIA H100 (80 GB)

Network and security

Control outbound network access from runner sandboxes with environment variables:

modal secret create github-secret \
  GITHUB_TOKEN=ghp_xxx \
  WEBHOOK_SECRET=xxx \
  BLOCK_NETWORK=true                       # block all outbound
  # or
  ALLOWED_CIDRS="10.0.0.0/8,192.168.0.0/16"  # allow specific ranges
  • BLOCK_NETWORK=true drops all outbound connections from the sandbox.
  • ALLOWED_CIDRS takes a comma-separated list of CIDR ranges. When set, only those ranges are reachable.

Additional security controls:

  • ALLOWED_REPOS restricts which repositories can trigger runner creation.
  • HMAC-SHA256 signature verification on every webhook request.
  • Delivery ID deduplication prevents replay attacks.
  • Per-repo concurrency limits via MAX_CONCURRENT_PER_REPO.

Architecture

sequenceDiagram
    participant GH as GitHub Actions
    participant WE as Modal Web Endpoint
    participant GA as GitHub API
    participant MS as Modal Sandbox
    
    GH->>WE: 1. workflow_job (queued) Webhook
    Note over WE: Verify Signature (HMAC-SHA256)
    WE->>GA: 2. Request JIT Config (generate-jitconfig)
    GA-->>WE: 3. Return JIT Config String
    WE->>MS: 4. modal.Sandbox.create(image, JIT_CONFIG)
    Note over MS: 5. Execute run.sh (as root in /tmp)
    MS->>GH: 6. Connect & Execute Job
    GH-->>MS: 7. Job Finished
    MS->>MS: 8. Exit & Terminate Sandbox
Loading
  1. A workflow triggers and a job enters queued.
  2. GitHub sends a workflow_job webhook to the Modal endpoint.
  3. The endpoint verifies the HMAC-SHA256 signature, then calls GitHub's generate-jitconfig API.
  4. A Modal Sandbox is provisioned with the runner image and JIT config.
  5. The runner connects to GitHub, executes the job, and the sandbox terminates on completion.

Environment variables

Variable Required Default Description
GITHUB_TOKEN Yes GitHub PAT for runner registration
WEBHOOK_SECRET Yes Secret for webhook signature validation
ALLOWED_REPOS No (all) Comma-separated allowlist of owner/repo
RUNNER_VERSION No 2.333.1 GitHub Actions runner version
RUNNER_GROUP_ID No 1 Runner group ID
MAX_CONCURRENT_PER_REPO No (unlimited) Max concurrent sandboxes per repo
ALLOWED_CIDRS No (allow all) Comma-separated CIDR ranges for outbound
BLOCK_NETWORK No false Fully isolate sandbox network
CACHE_VOLUME_NAME No Modal Volume name for persistent /cache
MODAL_REGION No us-east-1 Modal region for sandbox deployment
SANDBOX_EXTRA_ENV No JSON string of extra env vars for sandboxes
GITHUB_ENTERPRISE_DOMAIN No Custom domain for GitHub Enterprise

Limitations

  • Docker-in-Docker support uses Modal's alpha Docker-in-Sandbox feature. GitHub Actions services: and container actions generally work but may have edge cases.
  • Every job runs in a fresh sandbox. Files saved outside the repository workspace are lost after the job completes.

License

MIT


If this project helps you, giving it a star helps others discover it.

Author

Manas C. Bavaskar