🚀 GitHub Actions Self-Hosted Runners on ECS EC2
Architecture Overview
GitHub Actions → ECS EC2 Cluster → Multiple Runner Containers → RDS
↓
Auto Scaling Group (2-10 instances)
Multiple AZs for HA
~$30-40/month vs $60+ for FargateStep 1: Same Dockerfile and Startup Script
Use the same Dockerfile and start.sh from before - they work for both Fargate and EC2.---
Step 2: CloudFormation Alternative (EC2 Launch Type)---
Step 3: Deployment Guide
Prerequisites
# 1. Build and push Docker image (same as before)
cd github-runner-ecs
chmod +x build-push.sh
./build-push.sh
# 2. Create/select EC2 key pair
aws ec2 create-key-pair --key-name github-runner-key --query 'KeyMaterial' --output text > github-runner-key.pem
chmod 400 github-runner-key.pemDeploy with Terraform
Create terraform.tfvars:
aws_region = "us-east-1"
github_repo = "myorg/myrepo"
github_token = "ghp_xxxxxxxxxxxx"
vpc_id = "vpc-xxxxx"
private_subnet_ids = ["subnet-xxxxx", "subnet-yyyyy"]
rds_security_group_id = "sg-xxxxx"
key_name = "github-runner-key"Deploy:
cd terraform
terraform init
terraform plan
terraform applyDeploy with CloudFormation
aws cloudformation create-stack \
--stack-name github-actions-runners-ec2 \
--template-body file://cloudformation-ec2.yaml \
--parameters \
ParameterKey=VpcId,ParameterValue=vpc-xxxxx \
ParameterKey=PrivateSubnetIds,ParameterValue="subnet-xxxxx\,subnet-yyyyy" \
ParameterKey=RDSSecurityGroupId,ParameterValue=sg-xxxxx \
ParameterKey=GitHubRepo,ParameterValue=myorg/myrepo \
ParameterKey=GitHubToken,ParameterValue=ghp_xxxxxxxxxxxx \
ParameterKey=KeyName,ParameterValue=github-runner-key \
ParameterKey=InstanceType,ParameterValue=t3.medium \
ParameterKey=MinInstances,ParameterValue=2 \
ParameterKey=MaxInstances,ParameterValue=6 \
ParameterKey=DesiredRunnerCount,ParameterValue=4 \
--capabilities CAPABILITY_NAMED_IAM
# Monitor stack creation
aws cloudformation wait stack-create-complete --stack-name github-actions-runners-ec2Step 4: Verify Deployment
# Check ECS cluster
aws ecs list-clusters
# Check EC2 instances in cluster
aws ecs list-container-instances --cluster github-actions-runners
# Check running tasks
aws ecs list-tasks --cluster github-actions-runners
# Check Auto Scaling Group
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names github-runner-ecs-asg
# View logs
aws logs tail /ecs/github-actions-runner --followSSH into EC2 Instance (for debugging)
# Get instance IP
INSTANCE_ID=$(aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names github-runner-ecs-asg \
--query 'AutoScalingGroups[0].Instances[0].InstanceId' \
--output text)
INSTANCE_IP=$(aws ec2 describe-instances \
--instance-ids $INSTANCE_ID \
--query 'Reservations[0].Instances[0].PrivateIpAddress' \
--output text)
# SSH (if in same VPC or via bastion)
ssh -i github-runner-key.pem ec2-user@$INSTANCE_IP
# Check ECS agent
sudo systemctl status ecs
# Check running containers
docker ps
# View ECS config
cat /etc/ecs/ecs.configStep 5: Cost Comparison
EC2 vs Fargate Pricing (Monthly)
| Configuration | EC2 (t3.medium) | Fargate |
|---|---|---|
| 2 instances, 4 runners | ~$30/month | ~$60/month |
| 4 instances, 8 runners | ~$60/month | ~$120/month |
| 6 instances, 12 runners | ~$90/month | ~$180/month |
EC2 Breakdown (t3.medium in us-east-1):
- Instance cost: $0.0416/hour × 730 hours = $30.37/month per instance
- 2 instances = $60.74/month
- Each instance can run ~2-4 runner containers
Savings: ~50% cheaper than Fargate!
Step 6: Test Workflow---
Step 7: Scaling Configuration
How Scaling Works
Two-level scaling:
- ECS Service Scaling (Task count): 4 → 12 containers
- EC2 Auto Scaling (Instance count): 2 → 6 instances
# Manually scale tasks
aws ecs update-service \
--cluster github-actions-runners \
--service github-actions-runner \
--desired-count 8
# Manually scale instances
aws autoscaling set-desired-capacity \
--auto-scaling-group-name github-runner-ecs-asg \
--desired-capacity 4Step 8: Monitoring DashboardMake it executable and run:
chmod +x monitor-runners.sh
./monitor-runners.shStep 9: Cost Optimization Tips
- Use Spot Instances (70% cheaper):
# In Launch Template
instance_market_options {
market_type = "spot"
spot_options {
max_price = "0.03" # ~70% discount
spot_instance_type = "one-time"
}
}-
Use Smaller Instances for Low Traffic:
t3.small(2 vCPU, 2GB): $15/month - Run 1-2 runnerst3.medium(2 vCPU, 4GB): $30/month - Run 2-4 runners ✅ Recommendedt3.large(2 vCPU, 8GB): $60/month - Run 4-8 runners
-
Schedule Scaling:
# Scale down at night
aws autoscaling put-scheduled-action \
--auto-scaling-group-name github-runner-ecs-asg \
--scheduled-action-name scale-down-night \
--recurrence "0 22 * * *" \
--desired-capacity 1 \
--min-size 1
# Scale up in morning
aws autoscaling put-scheduled-action \
--auto-scaling-group-name github-runner-ecs-asg \
--scheduled-action-name scale-up-morning \
--recurrence "0 8 * * *" \
--desired-capacity 2 \
--min-size 2Production Checklist
- Docker image built and in ECR
- ECS cluster created with EC2 capacity provider
- Auto Scaling Group running (2-6 instances)
- ECS Service running (4-12 tasks)
- Security groups configured (ECS → RDS)
- Runners showing in GitHub (check Settings → Actions → Runners)
- Test workflow runs successfully
- CloudWatch logs accessible
- Auto-scaling tested
- SSH access configured for debugging
- Cost monitoring enabled
Quick Commands Reference
# View cluster status
aws ecs describe-clusters --clusters github-actions-runners
# List instances
aws ecs list-container-instances --cluster github-actions-runners
# View logs
aws logs tail /ecs/github-actions-runner --follow
# Scale service
aws ecs update-service --cluster github-actions-runners \
--service github-actions-runner --desired-count 8
# Scale instances
aws autoscaling set-desired-capacity \
--auto-scaling-group-name github-runner-ecs-asg \
--desired-capacity 4
# SSH to instance
aws ec2 describe-instances --filters "Name=tag:Name,Values=GitHub Runner ECS Instance" \
--query 'Reservations[*].Instances[*].[InstanceId,PrivateIpAddress]' \
--output tableThis setup gives you:
- ✅ High availability (multi-AZ)
- ✅ Auto-scaling (2-6 instances, 4-12 runners)
- ✅ 50% cost savings vs Fargate
- ✅ No single point of failure
- ✅ Direct RDS access from VPC