A high-performance, webhook-driven auto-scaler for GitHub Actions self-hosted runners written in Go. This system provides instant scaling by responding to GitHub webhooks in real-time.
- Pure Webhook-Driven Scaling: Instant response to GitHub
workflow_run.queuedevents - Multi-Owner Support: Manage runners for multiple GitHub users/organizations
- Multi-Runtime Support: Docker and Kubernetes container runtimes
- Multi-Architecture: Supports AMD64 and ARM64 with intelligent scheduling (Kubernetes)
- Webhook Management API: Complete REST API for webhook lifecycle management
- Token Validation: Intelligent GitHub token scope detection and validation
- Security: HMAC signature verification and secure container management
- Monitoring: Built-in health checks, metrics, and status endpoints
- Docker Runtime: Docker and Docker Compose
- Kubernetes Runtime: Kubernetes cluster with kubectl access
- GitHub Personal Access Token with
reposcope - GitHub repository with Actions enabled
# Latest stable release
docker pull ghcr.io/cecil-the-coder/gha-webhook-scaler:latest
# Specific version
docker pull ghcr.io/cecil-the-coder/gha-webhook-scaler:v1.0.0Supported Architectures:
linux/amd64(Intel/AMD 64-bit)linux/arm64(ARM 64-bit, Apple Silicon, AWS Graviton)
# Create .env file
cat > .env << EOF
GITHUB_TOKEN=your_github_personal_access_token
REPO_OWNER=your_github_username
REPO_NAME=your_repository_name
WEBHOOK_SECRET=your_webhook_secret_optional
MAX_RUNNERS=5
PORT=8080
DEBUG=false
EOF
# Start the auto-scaler
docker compose up -d# Create .env file for all repositories under owner
cat > .env << EOF
GITHUB_TOKEN=your_github_personal_access_token
REPO_OWNER=your_github_username
ALL_REPOS=true
WEBHOOK_SECRET=your_webhook_secret_optional
MAX_RUNNERS=10
PORT=8080
DEBUG=false
EOF
# Start the auto-scaler
docker compose up -dThe auto-scaler can automatically create and manage webhooks for your repositories:
For Single Repository:
# The webhook will be automatically created when the auto-scaler starts
# No manual webhook configuration needed!For All Repositories:
# Set ALL_REPOS=true in your configuration
# The auto-scaler will automatically create webhooks for all accessible repositories
ALL_REPOS=trueHow it works:
- Auto-scaler automatically creates webhooks pointing to
http://your-server:8080/webhook - Validates GitHub token permissions before webhook creation
- Skips repositories where you don't have admin/maintain permissions
- Updates existing webhooks if configuration changes
- Works with both single repositories and organization-wide setups
Note: Webhooks are created automatically when the auto-scaler starts. Most users don't need to do anything beyond setting up their environment configuration.
If you prefer to create webhooks manually or the automatic setup doesn't work:
For Single Repository:
- Go to Repository Settings → Webhooks → Add webhook
- URL:
http://your-server:8080/webhook - Content type:
application/json - Secret: (optional, recommended)
- Events: Select "Workflow runs"
For All Repositories (Organization):
- Go to Organization Settings → Webhooks → Add webhook
- Same configuration as above
| Variable | Description | Default | Required |
|---|---|---|---|
GITHUB_TOKEN |
GitHub Personal Access Token | - | Yes |
REPO_OWNER |
Repository owner/organization | - | Yes |
REPO_NAME |
Repository name | - | Yes (unless ALL_REPOS=true) |
ALL_REPOS |
Enable all repositories under owner | false |
No |
WEBHOOK_SECRET |
GitHub webhook secret | - | No |
MAX_RUNNERS |
Maximum concurrent runners | 5 |
No |
RUNNER_TIMEOUT |
Maximum runner lifetime (seconds) | 3600 |
No |
PORT |
HTTP server port | 8080 |
No |
DEBUG |
Enable debug logging | false |
No |
RUNTIME |
Container runtime (docker or kubernetes) |
docker |
No |
RUNNER_IMAGE |
Docker image for runners | myoung34/github-runner:ubuntu-noble |
No |
CACHE_VOLUMES |
Enable caching volumes | true |
No |
KUBERNETES_PVCS |
Use PersistentVolumeClaims for Kubernetes | false |
No |
LABELS |
Additional runner labels (comma-separated) | - | No |
Support for one or more GitHub owners/organizations:
# Specify one or more owners (comma-separated)
OWNERS=mycompany,personal,client-org
# Company organization
OWNER_MYCOMPANY_GITHUB_TOKEN=ghp_company_token
OWNER_MYCOMPANY_ALL_REPOS=true
OWNER_MYCOMPANY_WEBHOOK_SECRET=company_secret
OWNER_MYCOMPANY_MAX_RUNNERS=15
OWNER_MYCOMPANY_LABELS=company,production
# Personal account
OWNER_PERSONAL_GITHUB_TOKEN=ghp_personal_token
OWNER_PERSONAL_REPO_NAME=my-project
OWNER_PERSONAL_WEBHOOK_SECRET=personal_secret
OWNER_PERSONAL_MAX_RUNNERS=5
# Client organization
OWNER_CLIENT_ORG_GITHUB_TOKEN=ghp_client_token
OWNER_CLIENT_ORG_ALL_REPOS=true
OWNER_CLIENT_ORG_WEBHOOK_SECRET=client_secretNotes:
- For a single owner, just list one owner:
OWNERS=mycompany - Owner names with hyphens are converted to underscores in environment variables (e.g.,
my-orgbecomesOWNER_MY_ORG_GITHUB_TOKEN) - Legacy configuration using
GITHUB_TOKENandREPO_OWNERis still supported for backward compatibility
GET /health- Health checkGET /version- Version and build informationPOST /webhook- GitHub webhook receiverGET /status- Auto-scaler status and configurationGET /metrics- Operational metrics
Repository Webhooks:
GET /api/v1/repos/{owner}/{repo}/hooks- List webhooksPOST /api/v1/repos/{owner}/{repo}/hooks- Create webhookGET /api/v1/repos/{owner}/{repo}/hooks/{hook_id}- Get webhookDELETE /api/v1/repos/{owner}/{repo}/hooks/{hook_id}- Delete webhookPOST /api/v1/repos/{owner}/{repo}/hooks/{hook_id}/test- Test webhookPUT /api/v1/repos/{owner}/{repo}/hooks/ensure- Create or update webhook
Token Validation:
GET /api/v1/owners/{owner}/token/validate- Validate token permissionsGET /api/v1/owners/{owner}/token/scopes- Check token scopesGET /api/v1/repos/{owner}/{repo}/token/validate- Repository-specific validation
Repository Management:
GET /api/v1/owners/{owner}/repositories- List accessible repositoriesPOST /api/v1/owners/{owner}/webhooks/preview- Preview webhook setupPOST /api/v1/owners/{owner}/webhooks/setup- Auto-setup webhooks
Use these labels in your GitHub Actions workflows:
# For any architecture
runs-on: [self-hosted, linux, docker]
# Architecture-specific
runs-on: [self-hosted, linux, x64, docker] # AMD64
runs-on: [self-hosted, linux, arm64, docker] # ARM64# Update configuration in k8s-manifest.yaml
# - Update image registry
# - Set GitHub tokens and secrets
# - Configure domain for Ingress
kubectl apply -f k8s-manifest.yaml- Multi-Architecture Scheduling: Automatically detects job requirements
- Persistent Caching: Uses PVCs for build cache persistence
- Resource Management: Configurable CPU/memory limits
- RBAC Security: Proper service account permissions
# Kubernetes-specific configuration
RUNTIME=kubernetes
CACHE_VOLUMES=true
KUBERNETES_PVCS=true # Required for persistent cachingClassic Personal Access Tokens:
reposcope - Full repository access (recommended)write:repo_hookscope - Webhook management only
Fine-Grained Personal Access Tokens:
- "Repository webhooks" permission (read/write)
- "Actions" permission (write)
The system automatically validates token permissions:
# Validate token for owner
curl http://localhost:8080/api/v1/owners/myorg/token/validate
# Check specific repository permissions
curl http://localhost:8080/api/v1/repos/myorg/myrepo/token/validate- HMAC-SHA256 signature verification
- Owner-specific webhook secrets
- Repository authorization validation
- Request source validation
- Webhook Reception: Receives
workflow_run.queuedevents from GitHub - Repository Validation: Ensures webhook is from authorized repository
- Owner Identification: Determines which owner configuration to use
- Label Detection: Analyzes queued jobs to determine required runner labels
- Capacity Check: Ensures scaling won't exceed owner's max runner limit
- Runner Creation: Starts new ephemeral runner with appropriate labels
- Automatic Cleanup: Runners self-terminate after job completion
Docker Runtime:
- Runs on host architecture (AMD64 or ARM64)
- Uses Docker volumes for caching
- Direct Docker socket access
Kubernetes Runtime:
- Multi-architecture scheduling based on workflow labels
- PersistentVolumeClaims for persistent caching
- Architecture-specific node selection
# Install dependencies
go mod download
# Run locally
export GITHUB_TOKEN=your_token
export REPO_OWNER=your_owner
export REPO_NAME=your_repo
go run *.go# Build binary
go build -o autoscaler *.go
# Build Docker image
docker build -t github-actions-autoscaler:latest .# Health check
curl http://localhost:8080/health
# Status and configuration
curl http://localhost:8080/status
# Operational metrics
curl http://localhost:8080/metrics{
"status": "healthy",
"config": {
"repository": "user/repo",
"all_repos": false,
"architecture": "amd64",
"max_runners": 5
},
"runners": {
"current": 2,
"max": 5,
"list": [...]
}
}Webhooks not received:
- Check firewall/port forwarding for port 8080
- Verify webhook URL is accessible from GitHub
- Check webhook secret configuration
Runners not starting:
- Verify GitHub token permissions using
/api/v1/owners/{owner}/token/validate - Check Docker socket access
- Review auto-scaler logs
Rate limiting issues:
- Use multiple tokens for different owners
- Monitor rate limits via token validation API
# Docker Compose
docker logs -f github-runner-autoscaler-go
# Kubernetes
kubectl logs -f deployment/github-runner-autoscaler -n github-runnersThis version is webhook-driven only and removes:
- Minimum runner guarantees
- Periodic queue monitoring
- Activity-based scoring
- Complex distribution algorithms
- Update configuration - Remove
MIN_RUNNERSreferences - Configure webhooks - Set up webhook delivery (replaces polling)
- Verify operation - Test with webhook delivery logs
- Update monitoring - Scaling is now event-driven only
This project is licensed under the MIT License.
🎯 Key Design Principle: Simple, reliable, webhook-driven scaling. One webhook event = one runner (up to configured limits).