Self-Hosted GitHub Actions Runner - Server Setup¶
Complete guide to setting up and managing self-hosted GitHub Actions runners on Linux servers.
Architecture Overview¶
┌────────────────────────────────────────────────────────────────────────┐
│ Self-Hosted Runner Server Architecture │
└────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ GitHub │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Repository │─>│ Workflow │─>│ Job Queue │ │
│ └──────────────┘ └──────────────┘ └──────┬───────┘ │
└─────────────────────────────────────────────┼───────────────┘
│
│ HTTPS
│
┌─────────────────────────────────────────────┼───────────────┐
│ Linux Server │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ systemd Service │ │
│ │ │ │
│ │ actions.runner.ORG-REPO.RUNNER-NAME.service │ │
│ └────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Runner Process (github-runner user) │ │
│ │ │ │
│ │ • Polls GitHub for jobs │ │
│ │ • Downloads workflow code │ │
│ │ • Executes steps │ │
│ │ • Reports status │ │
│ └────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Work │ │ Docker │ │ Build │ │
│ │ Directory │ │ Engine │ │ Tools │ │
│ │ │ │ │ │ │ │
│ │ _work/ │ │ • Build │ │ • Node.js │ │
│ │ • Jobs │ │ • Run │ │ • Python │ │
│ │ • Logs │ │ • Push │ │ • Go │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────┘
│ │ │
└─────────────────┴─────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Monitoring │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Health Checks │ │ Logs │ │ Metrics │ │
│ │ │ │ │ │ │ │
│ │ • Service │ │ • journalctl│ │ • CPU │ │
│ │ • Disk │ │ • _diag/ │ │ • Memory │ │
│ │ • Network │ │ • Rotation │ │ • Disk │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────┘
Runner Lifecycle¶
GitHub Runner Service Job Execution
│ │ │
│ 1. Job Available │ │
├───────────────────────────>│ │
│ │ │
│ 2. Request Job │ │
│<───────────────────────────┤ │
│ │ │
│ 3. Assign Job │ │
├───────────────────────────>│ │
│ │ │
│ │ 4. Start Execution │
│ ├───────────────────────────>│
│ │ │
│ │ 5. Checkout Code │
│<───────────────────────────┼────────────────────────────┤
│ │ │
│ │ 6. Run Steps │
│ │ │
│ │ 7. Build/Test │
│ │ │
│ │ 8. Docker Build │
│ │ │
│ │ 9. Job Complete │
│ │<───────────────────────────┤
│ │ │
│ 10. Report Status │ │
│<───────────────────────────┤ │
│ │ │
│ 11. Acknowledge │ │
├───────────────────────────>│ │
│ │ │
│ │ 12. Cleanup │
│ ├───────────────────────────>│
│ │ │
System Architecture¶
┌────────────────────────────────────────────────────────────────────────┐
│ System Components │
└────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Linux Server │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ systemd Service │ │
│ │ │ │
│ │ • Auto-start on boot │ │
│ │ • Restart on failure │ │
│ │ • Resource limits (CPU, Memory) │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Runner User (github-runner) │ │
│ │ │ │
│ │ • Isolated environment │ │
│ │ • Limited permissions │ │
│ │ • Docker group membership │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Work │ │ Docker │ │ Security │ │
│ │ Directory │ │ Socket │ │ │ │
│ │ │ │ │ │ • Firewall │ │
│ │ /tmp/ │ │ /var/run/ │ │ • Audit │ │
│ │ runner/ │ │ docker.sock│ │ • Limits │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────────┘
│ │ │
└─────────────────┴─────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Monitoring & Logging │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Health Check │ │ Prometheus │ │ Log Rotation │ │
│ │ Script │ │ Exporter │ │ │ │
│ │ │ │ │ │ • Daily │ │
│ │ • Cron Job │ │ • Metrics │ │ • Compress │ │
│ │ • Every 5m │ │ • Alerts │ │ • Cleanup │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────┘
Table of Contents¶
- Prerequisites
- Installation on Linux Server
- Configuration
- Running as a Service
- Security Best Practices
- Monitoring and Maintenance
- Troubleshooting
Prerequisites¶
System Requirements¶
- Linux server (Ubuntu 20.04+ recommended)
- Minimum 2 CPU cores
- Minimum 4GB RAM
- 20GB+ disk space
- Root or sudo access
Required Software¶
# Update system
sudo apt update && sudo apt upgrade -y
# Install dependencies
sudo apt install -y curl wget git jq build-essential libssl-dev libffi-dev python3 python3-venv python3-dev
Installation on Linux Server¶
Step 1: Create a Dedicated User¶
# Create runner user
sudo useradd -m -s /bin/bash github-runner
# Add to docker group (if using Docker)
sudo usermod -aG docker github-runner
# Switch to runner user
sudo su - github-runner
Step 2: Download and Extract Runner¶
# Create a folder
mkdir actions-runner && cd actions-runner
# Download the latest runner package
curl -o actions-runner-linux-x64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz
# Optional: Validate the hash
echo "29fc8cf2dab4c195bb147384e7e2c94cfd4d4022c793b346a6175435265aa278 actions-runner-linux-x64-2.311.0.tar.gz" | shasum -a 256 -c
# Extract the installer
tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz
Step 3: Configure the Runner¶
For Repository-Level Runner¶
# Get token from: https://github.com/YOUR_ORG/YOUR_REPO/settings/actions/runners/new
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token YOUR_TOKEN
# Configure with labels
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token YOUR_TOKEN --labels self-hosted,linux,x64,production
For Organization-Level Runner¶
# Get token from: https://github.com/organizations/YOUR_ORG/settings/actions/runners/new
./config.sh --url https://github.com/YOUR_ORG --token YOUR_TOKEN --labels self-hosted,linux,x64,production
Step 4: Test the Runner¶
Running as a Service¶
Install as systemd Service¶
# Exit from github-runner user
exit
# Install the service (as root/sudo)
cd /home/github-runner/actions-runner
sudo ./svc.sh install github-runner
# Start the service
sudo ./svc.sh start
# Check status
sudo ./svc.sh status
# Enable auto-start on boot
sudo systemctl enable actions.runner.YOUR_ORG-YOUR_REPO.YOUR_RUNNER_NAME.service
Service Management Commands¶
# Start service
sudo ./svc.sh start
# Stop service
sudo ./svc.sh stop
# Check status
sudo ./svc.sh status
# View logs
sudo journalctl -u actions.runner.* -f
# Restart service
sudo ./svc.sh stop && sudo ./svc.sh start
Configuration¶
Environment Variables¶
Create /home/github-runner/actions-runner/.env:
# Custom environment variables
NODE_ENV=production
DOCKER_HOST=unix:///var/run/docker.sock
PATH=/usr/local/bin:/usr/bin:/bin
# Proxy settings (if needed)
HTTP_PROXY=http://proxy.example.com:8080
HTTPS_PROXY=http://proxy.example.com:8080
NO_PROXY=localhost,127.0.0.1
Runner Configuration File¶
Edit /home/github-runner/actions-runner/.runner:
{
"agentId": 1,
"agentName": "production-runner-01",
"poolId": 1,
"poolName": "Default",
"serverUrl": "https://github.com/YOUR_ORG",
"gitHubUrl": "https://github.com/YOUR_ORG/YOUR_REPO",
"workFolder": "_work"
}
Custom Labels¶
# Add labels during configuration
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
--token YOUR_TOKEN \
--labels self-hosted,linux,x64,production,gpu,high-memory
# Or reconfigure existing runner
./config.sh remove --token YOUR_TOKEN
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
--token YOUR_TOKEN \
--labels self-hosted,linux,x64,production,gpu
Security Best Practices¶
1. Isolate Runner User¶
# Limit sudo access
sudo visudo
# Add this line (only if necessary)
github-runner ALL=(ALL) NOPASSWD: /usr/bin/docker
# Better: Don't give sudo access at all
2. Use Dedicated Network¶
# Configure firewall
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow from 192.168.1.0/24 to any port 443
sudo ufw deny incoming
sudo ufw allow outgoing
3. Secure Docker Access¶
# Create docker group with limited permissions
sudo groupadd docker-runners
sudo usermod -aG docker-runners github-runner
# Configure Docker daemon
sudo nano /etc/docker/daemon.json
{
"userns-remap": "github-runner",
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
4. Implement Resource Limits¶
Create /etc/systemd/system/actions.runner.*.service.d/override.conf:
[Service]
# CPU limit (50% of one core)
CPUQuota=50%
# Memory limit
MemoryLimit=2G
MemoryMax=2G
# Process limit
TasksMax=100
# Restart policy
Restart=always
RestartSec=10
Apply changes:
5. Enable Audit Logging¶
# Install auditd
sudo apt install auditd
# Add audit rules
sudo nano /etc/audit/rules.d/github-runner.rules
# Monitor runner directory
-w /home/github-runner/actions-runner/ -p wa -k github_runner
-w /home/github-runner/actions-runner/_work/ -p wa -k github_runner_work
# Monitor service
-w /etc/systemd/system/actions.runner.* -p wa -k github_runner_service
Monitoring and Maintenance¶
Health Check Script¶
Create /home/github-runner/health-check.sh:
#!/bin/bash
RUNNER_NAME="production-runner-01"
LOG_FILE="/var/log/github-runner-health.log"
# Check if service is running
if ! systemctl is-active --quiet actions.runner.*; then
echo "$(date): Runner service is not running" >> $LOG_FILE
sudo systemctl start actions.runner.*
fi
# Check disk space
DISK_USAGE=$(df -h /home/github-runner | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 80 ]; then
echo "$(date): Disk usage is at ${DISK_USAGE}%" >> $LOG_FILE
# Clean old work directories
find /home/github-runner/actions-runner/_work -type d -mtime +7 -exec rm -rf {} +
fi
# Check memory usage
MEM_USAGE=$(free | grep Mem | awk '{print ($3/$2) * 100.0}' | cut -d. -f1)
if [ $MEM_USAGE -gt 90 ]; then
echo "$(date): Memory usage is at ${MEM_USAGE}%" >> $LOG_FILE
fi
# Check runner connectivity
if ! curl -s https://github.com > /dev/null; then
echo "$(date): Cannot reach GitHub" >> $LOG_FILE
fi
Monitoring with Prometheus¶
Create /home/github-runner/runner-exporter.sh:
#!/bin/bash
# Export runner metrics for Prometheus
cat << EOF > /var/lib/node_exporter/textfile_collector/github_runner.prom
# HELP github_runner_status Runner status (1=running, 0=stopped)
# TYPE github_runner_status gauge
github_runner_status{runner="production-runner-01"} $(systemctl is-active --quiet actions.runner.* && echo 1 || echo 0)
# HELP github_runner_jobs_total Total jobs executed
# TYPE github_runner_jobs_total counter
github_runner_jobs_total{runner="production-runner-01"} $(grep -c "Running job" /home/github-runner/actions-runner/_diag/*.log 2>/dev/null || echo 0)
EOF
Log Rotation¶
Create /etc/logrotate.d/github-runner:
/home/github-runner/actions-runner/_diag/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 github-runner github-runner
}
Cleanup Script¶
Create /home/github-runner/cleanup.sh:
#!/bin/bash
# Clean old work directories
find /home/github-runner/actions-runner/_work -type d -mtime +7 -exec rm -rf {} + 2>/dev/null
# Clean Docker resources
docker system prune -af --volumes --filter "until=168h"
# Clean old logs
find /home/github-runner/actions-runner/_diag -name "*.log" -mtime +7 -delete
# Clean package caches
sudo apt-get clean
sudo apt-get autoclean
echo "$(date): Cleanup completed" >> /var/log/github-runner-cleanup.log
Using the Self-Hosted Runner¶
In Workflow File¶
name: Use Self-Hosted Runner
on: [push]
jobs:
build:
runs-on: self-hosted # Use any self-hosted runner
steps:
- uses: actions/checkout@v4
- run: echo "Running on self-hosted runner"
build-with-labels:
runs-on: [self-hosted, linux, x64, production] # Use specific runner
steps:
- uses: actions/checkout@v4
- run: echo "Running on production runner"
Troubleshooting¶
Runner Not Connecting¶
# Check service status
sudo systemctl status actions.runner.*
# View logs
sudo journalctl -u actions.runner.* -n 100 --no-pager
# Check network connectivity
curl -v https://github.com
# Verify token
./config.sh --check
High Resource Usage¶
# Check running processes
ps aux | grep Runner.Listener
# Check Docker containers
docker ps
docker stats
# Check disk usage
du -sh /home/github-runner/actions-runner/_work/*
# Clean up
./run.sh --once # Run one job and exit
Permission Issues¶
# Fix ownership
sudo chown -R github-runner:github-runner /home/github-runner/actions-runner
# Fix permissions
chmod +x /home/github-runner/actions-runner/*.sh
# Check Docker permissions
sudo usermod -aG docker github-runner
newgrp docker
Service Won't Start¶
# Remove and reconfigure
cd /home/github-runner/actions-runner
sudo ./svc.sh uninstall
./config.sh remove --token YOUR_TOKEN
# Reconfigure
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token YOUR_TOKEN
# Reinstall service
sudo ./svc.sh install github-runner
sudo ./svc.sh start
Updating the Runner¶
# Stop the service
sudo ./svc.sh stop
# Download new version
cd /home/github-runner
wget https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz
# Backup current installation
cp -r actions-runner actions-runner.backup
# Extract new version
cd actions-runner
tar xzf ../actions-runner-linux-x64-2.311.0.tar.gz
# Start the service
sudo ./svc.sh start
Removing the Runner¶
# Stop the service
sudo ./svc.sh stop
# Uninstall the service
sudo ./svc.sh uninstall
# Remove runner from GitHub
./config.sh remove --token YOUR_TOKEN
# Delete runner directory
cd ~
rm -rf actions-runner
# Remove user (optional)
sudo userdel -r github-runner
Multiple Runners on Same Server¶
# Create separate directories
mkdir -p ~/actions-runner-1 ~/actions-runner-2
# Configure each runner separately
cd ~/actions-runner-1
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token TOKEN1 --name runner-1
cd ~/actions-runner-2
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token TOKEN2 --name runner-2
# Install both as services
cd ~/actions-runner-1 && sudo ./svc.sh install github-runner
cd ~/actions-runner-2 && sudo ./svc.sh install github-runner
# Start both
sudo systemctl start actions.runner.*
Best Practices Summary¶
- Isolation: Run runners in isolated environments
- Security: Limit permissions, use firewalls, enable audit logging
- Monitoring: Implement health checks and monitoring
- Maintenance: Regular cleanup and updates
- Scaling: Use multiple runners for high workload
- Backup: Keep configuration backups
- Documentation: Document custom configurations
- Testing: Test runner setup before production use