We were spending approximately $1500/month on CircleCI for ~45k build minutes across a monorepo with 4 main services and frequent integration tests. The primary cost drivers were macOS builds for iOS and resource-intensive Docker layer caching.
After a two-month evaluation, we moved to a self-hosted solution built on:
- AWS EC2 `c6i.4xlarge` spot instances (Linux runners)
- AWS Mac instances (`mac1.metal` via EC2 Fleet for macOS)
- GitHub Actions Runner
The total monthly cost is now ~$300. This includes compute, a 500GB EBS volume for shared cache, and data transfer. The 80% cost reduction came from eliminating the managed service premium and optimizing instance utilization.
Key configuration for the Linux runner scaling:
```yaml
# .github/workflows/scale-runners.yaml
name: Scale EC2 Runners
on:
workflow_call:
inputs:
os:
required: true
type: string
count:
required: true
type: number
jobs:
scale:
runs-on: ubuntu-latest
steps:
- name: Start EC2 Instances
run: |
aws ec2 run-instances
--instance-type c6i.4xlarge
--image-id ami-12345678
--tag-specifications 'ResourceType=instance,Tags=[{Key=gh-runner,Value=linux}]'
--instance-market-options 'MarketType=spot,SpotOptions={MaxPrice=0.5,SpotInstanceType=persistent,InstanceInterruptionBehavior=stop}'
--count ${{ inputs.count }}
```
The cache strategy was critical. We use a dedicated Redis instance for job coordination and S3 + `s3fs` for persistent layer cache, which reduced average Linux build time from 12 to 7 minutes.
The break-even point for this investment (engineering time, setup, monitoring) was under three months. The main trade-off is operational overhead: we now manage runner updates, security patches, and instance health. For teams with consistent, high build volume, the financial case is clear.
Solid breakdown. Did you factor in the engineering hours to set up and maintain the EC2 Fleet config, or was that a one-off cost that's already paid for? That's the hidden tax on these self-hosted setups I always worry about.
That's a great point about the engineering hours. For us, the initial setup was about two engineer-weeks, but honestly the ongoing maintenance has been almost zero for the Linux runners. The EC2 spot instances just run the GitHub Actions runner binary, and the scaling is triggered by workflow calls. The real time sink is the macOS side - the `mac1.metal` fleet requires more babysitting because spot isn't an option, and we occasionally have to manually intervene when an instance gets into a weird state.
The hidden tax isn't zero, but it's become a predictable, small fraction of one engineer's time. Maybe two or three hours a month? The financial saving far outweighs that for our team size. If you're a tiny team where every hour counts, that calculus changes.
Prod is the only environment that matters.