Add comprehensive GitHub Actions self-hosted runners deployment plan
This commit is contained in:
685
GITHUB_RUNNERS_PLAN.md
Normal file
685
GITHUB_RUNNERS_PLAN.md
Normal file
@@ -0,0 +1,685 @@
|
||||
# GitHub Actions Self-Hosted Runners Deployment Plan
|
||||
|
||||
## Overview
|
||||
|
||||
**Goal:** Deploy GitHub Actions self-hosted runners on your Docker Swarm cluster to run CI/CD workflows with unlimited minutes, custom environments, and access to your homelab resources.
|
||||
|
||||
**Architecture:** Docker-based runners deployed as a Swarm service with auto-scaling capabilities.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Decision
|
||||
|
||||
### Option 1: Docker Container Runners (Recommended for your setup)
|
||||
- ✅ Runs in Docker containers on your existing cluster
|
||||
- ✅ Scales horizontally by adding/removing containers
|
||||
- ✅ Uses your existing infrastructure (tpi-n1, tpi-n2, node-nas)
|
||||
- ✅ Easy to manage through Docker Swarm
|
||||
- ✅ ARM64 and x86_64 support for multi-arch builds
|
||||
|
||||
### Option 2: VM/Physical Runners (Alternative)
|
||||
- Runners installed directly on VMs or bare metal
|
||||
- More isolated but harder to manage
|
||||
- Not recommended for your containerized setup
|
||||
|
||||
**Decision:** Use Docker Container Runners (Option 1) with multi-arch support.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
```
|
||||
GitHub Repository
|
||||
│
|
||||
│ Webhook/REST API
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ GitHub Actions Service │
|
||||
└─────────────────────────────┘
|
||||
│
|
||||
│ Job Request
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Your Docker Swarm Cluster │
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ Runner Service │ │
|
||||
│ │ (Multiple Replicas)│ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────┐ ┌─────┐ │ │
|
||||
│ │ │ ARM │ │x86_64│ │ │
|
||||
│ │ │64 │ │ │ │ │
|
||||
│ │ └─────┘ └─────┘ │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ Docker-in-Docker │ │
|
||||
│ │ (for Docker builds)│ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Planning & Preparation
|
||||
|
||||
### Step 1: Determine Requirements
|
||||
|
||||
**Use Cases:**
|
||||
- [ ] Build and test applications
|
||||
- [ ] Deploy to your homelab (Kubernetes/Docker Swarm)
|
||||
- [ ] Run ARM64 builds (for Raspberry Pi/ARM apps)
|
||||
- [ ] Run x86_64 builds (standard applications)
|
||||
- [ ] Access private network resources (databases, internal APIs)
|
||||
- [ ] Build Docker images and push to your Gitea registry
|
||||
|
||||
**Resource Requirements per Runner:**
|
||||
- CPU: 2+ cores recommended
|
||||
- Memory: 4GB+ RAM per runner
|
||||
- Disk: 20GB+ for workspace and Docker layers
|
||||
- Network: Outbound HTTPS to GitHub
|
||||
|
||||
**Current Cluster Capacity:**
|
||||
- tpi-n1: 8 cores ARM64, 8GB RAM (Manager)
|
||||
- tpi-n2: 8 cores ARM64, 8GB RAM (Worker)
|
||||
- node-nas: 2 cores x86_64, 8GB RAM (Storage)
|
||||
|
||||
**Recommended Allocation:**
|
||||
- 2 runners on tpi-n1 (ARM64)
|
||||
- 2 runners on tpi-n2 (ARM64)
|
||||
- 1 runner on node-nas (x86_64)
|
||||
|
||||
### Step 2: GitHub Configuration
|
||||
|
||||
**Choose Runner Level:**
|
||||
- [ ] **Repository-level** - Dedicated to specific repo (recommended to start)
|
||||
- [ ] **Organization-level** - Shared across org repos
|
||||
- [ ] **Enterprise-level** - Shared across enterprise
|
||||
|
||||
**For your use case:** Start with **repository-level** runners, then expand to organization-level if needed.
|
||||
|
||||
**Required GitHub Settings:**
|
||||
1. Go to: `Settings > Actions > Runners > New self-hosted runner`
|
||||
2. Note the **Registration Token** (expires after 1 hour)
|
||||
3. Note the **Runner Group** (default: "Default")
|
||||
4. Configure labels (e.g., `homelab`, `arm64`, `x86_64`, `self-hosted`)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Infrastructure Setup
|
||||
|
||||
### Step 3: Create Docker Network
|
||||
|
||||
```bash
|
||||
# On controller (tpi-n1)
|
||||
ssh ubuntu@192.168.2.130
|
||||
|
||||
# Create overlay network for runners
|
||||
docker network create --driver overlay --attachable github-runners-network
|
||||
|
||||
# Verify
|
||||
docker network ls | grep github
|
||||
```
|
||||
|
||||
### Step 4: Create Persistent Storage
|
||||
|
||||
```bash
|
||||
# Create volume for runner cache (shared across runners)
|
||||
docker volume create github-runner-cache
|
||||
|
||||
# Create volume for Docker build cache
|
||||
docker volume create github-runner-docker-cache
|
||||
```
|
||||
|
||||
### Step 5: Prepare Node Labels
|
||||
|
||||
```bash
|
||||
# Verify node labels
|
||||
ssh ubuntu@192.168.2.130
|
||||
docker node ls --format '{{.Hostname}} {{.Labels}}'
|
||||
|
||||
# Expected output:
|
||||
# tpi-n1 map[infra:true role:storage storage:high]
|
||||
# tpi-n2 map[role:compute]
|
||||
# node-nas map[type:nas]
|
||||
|
||||
# Add architecture labels if missing:
|
||||
docker node update --label-add arch=arm64 tpi-n1
|
||||
docker node update --label-add arch=arm64 tpi-n2
|
||||
docker node update --label-add arch=x86_64 node-nas
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Runner Deployment
|
||||
|
||||
### Step 6: Create Environment File
|
||||
|
||||
Create `.env` file:
|
||||
```bash
|
||||
# GitHub Configuration
|
||||
GITHUB_TOKEN=your_github_personal_access_token
|
||||
GITHUB_OWNER=your-github-username-or-org
|
||||
GITHUB_REPO=your-repository-name # Leave empty for org-level
|
||||
|
||||
# Runner Configuration
|
||||
RUNNER_NAME_PREFIX=homelab
|
||||
RUNNER_LABELS=self-hosted,homelab,linux
|
||||
RUNNER_GROUP=Default
|
||||
|
||||
# Docker Configuration
|
||||
DOCKER_TLS_CERTDIR=/certs
|
||||
|
||||
# Optional: Pre-installed tools
|
||||
PRE_INSTALL_TOOLS="docker-compose,nodejs,npm,yarn,python3,pip,git"
|
||||
```
|
||||
|
||||
### Step 7: Create Docker Compose Stack
|
||||
|
||||
Create `github-runners-stack.yml`:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
# ARM64 Runners
|
||||
runner-arm64:
|
||||
image: myoung34/github-runner:latest
|
||||
environment:
|
||||
- ACCESS_TOKEN=${GITHUB_TOKEN}
|
||||
- REPO_URL=https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}
|
||||
- RUNNER_NAME=${RUNNER_NAME_PREFIX}-arm64-{{.Task.Slot}}
|
||||
- RUNNER_WORKDIR=/tmp/runner-work
|
||||
- RUNNER_GROUP=${RUNNER_GROUP:-Default}
|
||||
- RUNNER_SCOPE=repo
|
||||
- LABELS=${RUNNER_LABELS},arm64
|
||||
- DISABLE_AUTO_UPDATE=true
|
||||
- EPHEMERAL=true # One job per container
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- github-runner-cache:/home/runner/cache
|
||||
- github-runner-docker-cache:/var/lib/docker
|
||||
networks:
|
||||
- github-runners-network
|
||||
- dokploy-network
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 2
|
||||
placement:
|
||||
constraints:
|
||||
- node.labels.arch == arm64
|
||||
restart_policy:
|
||||
condition: any
|
||||
delay: 5s
|
||||
max_attempts: 3
|
||||
privileged: true # Required for Docker-in-Docker
|
||||
|
||||
# x86_64 Runners
|
||||
runner-x86_64:
|
||||
image: myoung34/github-runner:latest
|
||||
environment:
|
||||
- ACCESS_TOKEN=${GITHUB_TOKEN}
|
||||
- REPO_URL=https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}
|
||||
- RUNNER_NAME=${RUNNER_NAME_PREFIX}-x86_64-{{.Task.Slot}}
|
||||
- RUNNER_WORKDIR=/tmp/runner-work
|
||||
- RUNNER_GROUP=${RUNNER_GROUP:-Default}
|
||||
- RUNNER_SCOPE=repo
|
||||
- LABELS=${RUNNER_LABELS},x86_64
|
||||
- DISABLE_AUTO_UPDATE=true
|
||||
- EPHEMERAL=true
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- github-runner-cache:/home/runner/cache
|
||||
- github-runner-docker-cache:/var/lib/docker
|
||||
networks:
|
||||
- github-runners-network
|
||||
- dokploy-network
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.labels.arch == x86_64
|
||||
restart_policy:
|
||||
condition: any
|
||||
delay: 5s
|
||||
max_attempts: 3
|
||||
privileged: true
|
||||
|
||||
# Optional: Runner Autoscaler
|
||||
autoscaler:
|
||||
image: ghcr.io/actions-runner-controller/actions-runner-controller:latest
|
||||
environment:
|
||||
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||
- RUNNER_SCOPE=repo
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- github-runners-network
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
|
||||
volumes:
|
||||
github-runner-cache:
|
||||
github-runner-docker-cache:
|
||||
|
||||
networks:
|
||||
github-runners-network:
|
||||
driver: overlay
|
||||
dokploy-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
### Step 8: Deploy Runners
|
||||
|
||||
```bash
|
||||
# Copy files to controller
|
||||
scp github-runners-stack.yml ubuntu@192.168.2.130:~/
|
||||
scp .env ubuntu@192.168.2.130:~/
|
||||
|
||||
# SSH to controller
|
||||
ssh ubuntu@192.168.2.130
|
||||
|
||||
# Load environment
|
||||
set -a && source .env && set +a
|
||||
|
||||
# Deploy stack
|
||||
docker stack deploy -c github-runners-stack.yml github-runners
|
||||
|
||||
# Verify deployment
|
||||
docker stack ps github-runners
|
||||
docker service ls | grep github
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: GitHub Integration
|
||||
|
||||
### Step 9: Verify Runners in GitHub
|
||||
|
||||
1. Go to: `https://github.com/[OWNER]/[REPO]/settings/actions/runners`
|
||||
2. You should see your runners listed as "Idle"
|
||||
3. Labels should show: `self-hosted`, `homelab`, `linux`, `arm64` or `x86_64`
|
||||
|
||||
### Step 10: Test with Sample Workflow
|
||||
|
||||
Create `.github/workflows/test-self-hosted.yml`:
|
||||
|
||||
```yaml
|
||||
name: Test Self-Hosted Runners
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-arm64:
|
||||
runs-on: [self-hosted, homelab, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Show runner info
|
||||
run: |
|
||||
echo "Architecture: $(uname -m)"
|
||||
echo "OS: $(uname -s)"
|
||||
echo "Node: $(hostname)"
|
||||
echo "CPU: $(nproc)"
|
||||
echo "Memory: $(free -h | grep Mem)"
|
||||
|
||||
- name: Test Docker
|
||||
run: |
|
||||
docker --version
|
||||
docker info
|
||||
docker run --rm hello-world
|
||||
|
||||
test-x86_64:
|
||||
runs-on: [self-hosted, homelab, x86_64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Show runner info
|
||||
run: |
|
||||
echo "Architecture: $(uname -m)"
|
||||
echo "OS: $(uname -s)"
|
||||
echo "Node: $(hostname)"
|
||||
|
||||
- name: Test access to homelab
|
||||
run: |
|
||||
# Test connectivity to your services
|
||||
curl -s http://gitea.bendtstudio.com:3000 || echo "Gitea not accessible"
|
||||
curl -s http://192.168.2.130:3000 || echo "Dokploy not accessible"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Security Hardening
|
||||
|
||||
### Step 11: Implement Security Best Practices
|
||||
|
||||
**1. Use Short-Lived Tokens:**
|
||||
```bash
|
||||
# Generate a GitHub App instead of PAT for better security
|
||||
# Or use OpenID Connect (OIDC) for authentication
|
||||
```
|
||||
|
||||
**2. Restrict Runner Permissions:**
|
||||
```yaml
|
||||
# Add to workflow
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, homelab]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # Only if pushing to registry
|
||||
```
|
||||
|
||||
**3. Network Isolation:**
|
||||
```yaml
|
||||
# Modify stack to use isolated network
|
||||
networks:
|
||||
github-runners-network:
|
||||
driver: overlay
|
||||
internal: true # No external access except through proxy
|
||||
```
|
||||
|
||||
**4. Resource Limits:**
|
||||
```yaml
|
||||
# Add to service definition in stack
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 4G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 2G
|
||||
```
|
||||
|
||||
### Step 12: Enable Ephemeral Mode
|
||||
|
||||
Ephemeral runners (already configured with `EPHEMERAL=true`) provide better security:
|
||||
- Each runner handles only one job
|
||||
- Container is destroyed after job completion
|
||||
- Fresh environment for every build
|
||||
- Prevents credential leakage between jobs
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Monitoring & Maintenance
|
||||
|
||||
### Step 13: Set Up Monitoring
|
||||
|
||||
**Create monitoring script** (`monitor-runners.sh`):
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Check runner status
|
||||
echo "=== Docker Service Status ==="
|
||||
docker service ls | grep github-runner
|
||||
|
||||
echo -e "\n=== Runner Containers ==="
|
||||
docker ps --filter name=github-runner --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
echo -e "\n=== Recent Logs ==="
|
||||
docker service logs github-runners_runner-arm64 --tail 50
|
||||
docker service logs github-runners_runner-x86_64 --tail 50
|
||||
|
||||
echo -e "\n=== Resource Usage ==="
|
||||
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" | grep github-runner
|
||||
```
|
||||
|
||||
**Create cron job for monitoring:**
|
||||
```bash
|
||||
# Add to crontab
|
||||
crontab -e
|
||||
|
||||
# Check runner health every 5 minutes
|
||||
*/5 * * * * /home/ubuntu/github-runners/monitor-runners.sh >> /var/log/github-runners.log 2>&1
|
||||
```
|
||||
|
||||
### Step 14: Set Up Log Rotation
|
||||
|
||||
```bash
|
||||
# Create logrotate config
|
||||
sudo tee /etc/logrotate.d/github-runners << EOF
|
||||
/var/log/github-runners.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 644 ubuntu ubuntu
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Step 15: Backup Strategy
|
||||
|
||||
```bash
|
||||
# Create backup script
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/backup/github-runners/$(date +%Y%m%d)"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Backup configuration
|
||||
cp ~/github-runners-stack.yml "$BACKUP_DIR/"
|
||||
cp ~/.env "$BACKUP_DIR/"
|
||||
|
||||
# Backup volumes
|
||||
docker run --rm -v github-runner-cache:/data -v "$BACKUP_DIR":/backup alpine tar czf /backup/runner-cache.tar.gz -C /data .
|
||||
docker run --rm -v github-runner-docker-cache:/data -v "$BACKUP_DIR":/backup alpine tar czf /backup/docker-cache.tar.gz -C /data .
|
||||
|
||||
echo "Backup completed: $BACKUP_DIR"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Advanced Configuration
|
||||
|
||||
### Step 16: Cache Optimization
|
||||
|
||||
**Mount host cache directories:**
|
||||
```yaml
|
||||
volumes:
|
||||
- /home/ubuntu/.cache/npm:/root/.npm
|
||||
- /home/ubuntu/.cache/pip:/root/.cache/pip
|
||||
- /home/ubuntu/.cache/go-build:/root/.cache/go-build
|
||||
- /home/ubuntu/.cargo:/root/.cargo
|
||||
```
|
||||
|
||||
**Pre-install common tools in custom image** (`Dockerfile.runner`):
|
||||
```dockerfile
|
||||
FROM myoung34/github-runner:latest
|
||||
|
||||
# Install common build tools
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
nodejs \
|
||||
npm \
|
||||
python3 \
|
||||
python3-pip \
|
||||
golang-go \
|
||||
openjdk-17-jdk \
|
||||
maven \
|
||||
gradle \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Docker Compose
|
||||
RUN curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
|
||||
-o /usr/local/bin/docker-compose && \
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
# Pre-pull common images
|
||||
RUN docker pull node:lts-alpine
|
||||
RUN docker pull python:3.11-slim
|
||||
```
|
||||
|
||||
Build and use custom image:
|
||||
```bash
|
||||
docker build -t your-registry/github-runner:custom -f Dockerfile.runner .
|
||||
docker push your-registry/github-runner:custom
|
||||
|
||||
# Update stack to use custom image
|
||||
```
|
||||
|
||||
### Step 17: Autoscaling Configuration
|
||||
|
||||
**Use Actions Runner Controller (ARC) for Kubernetes-style autoscaling:**
|
||||
|
||||
```yaml
|
||||
# Add to stack
|
||||
autoscaler:
|
||||
image: ghcr.io/actions-runner-controller/actions-runner-controller:latest
|
||||
environment:
|
||||
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||
- GITHUB_APP_ID=${GITHUB_APP_ID}
|
||||
- GITHUB_APP_INSTALLATION_ID=${GITHUB_APP_INSTALLATION_ID}
|
||||
- GITHUB_APP_PRIVATE_KEY=/etc/gh-app-key/private-key.pem
|
||||
volumes:
|
||||
- /path/to/private-key.pem:/etc/gh-app-key/private-key.pem:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
```
|
||||
|
||||
### Step 18: Multi-Repository Setup
|
||||
|
||||
For organization-level runners, update environment:
|
||||
```bash
|
||||
# For org-level
|
||||
RUNNER_SCOPE=org
|
||||
ORG_NAME=your-organization
|
||||
|
||||
# Remove REPO_URL, use:
|
||||
ORG_URL=https://github.com/${ORG_NAME}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Troubleshooting Guide
|
||||
|
||||
### Common Issues & Solutions
|
||||
|
||||
**1. Runner shows "Offline" in GitHub:**
|
||||
```bash
|
||||
# Check logs
|
||||
docker service logs github-runners_runner-arm64
|
||||
|
||||
# Common causes:
|
||||
# - Expired token (regenerate in GitHub settings)
|
||||
# - Network connectivity issue
|
||||
docker exec <container> curl -I https://github.com
|
||||
|
||||
# Restart service
|
||||
docker service update --force github-runners_runner-arm64
|
||||
```
|
||||
|
||||
**2. Docker-in-Docker not working:**
|
||||
```bash
|
||||
# Ensure privileged mode is enabled
|
||||
# Check Docker socket is mounted
|
||||
docker exec <container> docker ps
|
||||
|
||||
# If failing, check AppArmor/SELinux
|
||||
sudo aa-status | grep docker
|
||||
```
|
||||
|
||||
**3. Jobs stuck in "Queued":**
|
||||
```bash
|
||||
# Check if runners are picking up jobs
|
||||
docker service ps github-runners_runner-arm64
|
||||
|
||||
# Verify labels match
|
||||
docker exec <container> cat /home/runner/.runner | jq '.labels'
|
||||
```
|
||||
|
||||
**4. Out of disk space:**
|
||||
```bash
|
||||
# Clean up Docker system
|
||||
docker system prune -a --volumes
|
||||
|
||||
# Clean runner cache
|
||||
docker volume rm github-runner-docker-cache
|
||||
docker volume create github-runner-docker-cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Planning
|
||||
- [ ] Determine which repositories need self-hosted runners
|
||||
- [ ] Decide on runner count per architecture
|
||||
- [ ] Generate GitHub Personal Access Token
|
||||
|
||||
### Phase 2: Infrastructure
|
||||
- [ ] Create Docker network
|
||||
- [ ] Create persistent volumes
|
||||
- [ ] Verify node labels
|
||||
|
||||
### Phase 3: Deployment
|
||||
- [ ] Create `.env` file with GitHub token
|
||||
- [ ] Create `github-runners-stack.yml`
|
||||
- [ ] Deploy stack to Docker Swarm
|
||||
- [ ] Verify runners appear in GitHub UI
|
||||
|
||||
### Phase 4: Testing
|
||||
- [ ] Create test workflow
|
||||
- [ ] Run test on ARM64 runner
|
||||
- [ ] Run test on x86_64 runner
|
||||
- [ ] Verify Docker builds work
|
||||
- [ ] Test access to homelab services
|
||||
|
||||
### Phase 5: Security
|
||||
- [ ] Enable ephemeral mode
|
||||
- [ ] Set resource limits
|
||||
- [ ] Review and restrict permissions
|
||||
- [ ] Set up network isolation
|
||||
|
||||
### Phase 6: Operations
|
||||
- [ ] Create monitoring script
|
||||
- [ ] Set up log rotation
|
||||
- [ ] Create backup script
|
||||
- [ ] Document maintenance procedures
|
||||
|
||||
---
|
||||
|
||||
## Cost & Resource Analysis
|
||||
|
||||
**Compared to GitHub-hosted runners:**
|
||||
|
||||
| Feature | GitHub Hosted | Your Self-Hosted |
|
||||
|---------|---------------|------------------|
|
||||
| Cost | $0.008/minute Linux | Free (electricity) |
|
||||
| Minutes | 2,000/month free | Unlimited |
|
||||
| ARM64 | Limited | Full control |
|
||||
| Concurrency | 20 jobs | Unlimited |
|
||||
| Network | Internet only | Your homelab access |
|
||||
|
||||
**Your Infrastructure Cost:**
|
||||
- Existing hardware: $0 (already running)
|
||||
- Electricity: ~$10-20/month additional load
|
||||
- Time: Initial setup ~2-4 hours
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review this plan** and decide on your specific use cases
|
||||
2. **Generate GitHub PAT** with `repo` and `admin:org` scopes
|
||||
3. **Start with Phase 1** - Planning
|
||||
4. **Deploy a single runner first** to test before scaling
|
||||
5. **Iterate** based on your workflow needs
|
||||
|
||||
Would you like me to help you start with any specific phase, or do you have questions about the architecture? 🚀
|
||||
Reference in New Issue
Block a user