Why GitHub Actions Sucks When You Actually Need It

CI/CD Performance Bottleneck

GitHub Actions is fine for toy projects and weekend hacks. The second you have a real team with real deadlines, it falls apart. I started tracking our build times because my manager kept asking why deployments were taking so long. Turns out our "quick" afternoon pushes were sitting in queue for 15-20 minutes before they even started.

Last Tuesday we had users screaming about 500 errors. One fucking line in a config file was wrong. I fixed it, pushed, and then watched GitHub's queue for 18 minutes while customers churned. Eighteen minutes for a build that takes 3 minutes to run.

Queue Times and Resource Limitations

CircleCI Logo

GitHub says they handle thousands of concurrent jobs, but that's bullshit marketing speak. Reality hits different. I started keeping a spreadsheet because I got tired of guessing:

What actually happens:

  • 7 AM: Builds usually start in under 3 minutes (nobody's awake yet)
  • 10 AM - 4 PM: You're fucked. 10-25 minute waits are normal
  • Large runners: Still sit in the same damn queue, you just pay 4x more
  • CircleCI: Starts immediately or within 30 seconds, every single time

My "fuck this shit" moment was during a production fire. Database connection was failing because I typo'd POSTGRES_DB=myapp-prod as POSTGRES_DB=myapp-prd. One character. I fixed it, pushed, and then watched GitHub's queue for 19 minutes while customers got 503 errors and started tweeting angry shit at us.

Performance Beyond Queue Times

Even when your build finally starts, GitHub Actions runs like ass. I ran the exact same Jest test suite on both platforms. CircleCI: 9 minutes. GitHub Actions: 14 minutes. Same fucking container, same tests, same everything.

iOS builds are where GitHub really shits the bed. Their macOS runners are still using Intel chips from 2019 while everyone else moved to Apple Silicon years ago. My Xcode builds went from 45 minutes on GitHub to 18 minutes on Blaze. That's not optimization, that's just having hardware that wasn't obsolete when Obama was president.

Architectural Limitations

Why GitHub Actions is Fundamentally Broken

Their resource options are stupid: 2-core or 8-core, nothing else. My build needs 4 cores but I have to pay for 8. That's like buying a fucking truck because they don't sell sedans.

The scheduler is drunk: I've seen builds sit in queue while other runners are completely idle. It's like the scheduler randomly decided to take a coffee break.

Docker caching is a joke: GitHub's cache misses constantly. CircleCI's Docker Layer Caching actually works - layers get reused instead of rebuilt every goddamn time.

Matrix builds are broken: Set up a 3x3 matrix and watch half the jobs sit there doing nothing. I've seen Error: This job was not started because a job with the same key already exists when the scheduler gets confused. It's supposed to run in parallel but GitHub's scheduler apparently can't count to 9.

GitLab Logo

Impact on Development Flow

Slow CI affects more than just deployment times - it changes how you work. When builds take unpredictably long, you start adapting your workflow in ways that aren't always healthy:

Common adaptations I've noticed:

  • Context switching increases: Start other work while waiting, then lose track of the original task
  • Deployment hesitancy: Teams deploy less frequently when CI is unreliable
  • Larger batch changes: Developers combine multiple changes to avoid multiple queue waits
  • Local testing emphasis: "It works locally" becomes more important when CI feedback is delayed

How Teams Actually Adapt

Most teams follow a similar pattern: start with GitHub Actions for convenience, then gradually move critical workflows elsewhere as they hit performance limits.

Enterprise patterns I've observed: Companies often keep GitHub Actions for basic checks but move deployment pipelines to faster platforms. Stripe's written about their infrastructure choices (they don't use GitHub Actions for the important stuff).

iOS development teams: GitHub's Intel-based macOS runners are particularly challenging for iOS development. Many teams have moved to Blaze for Apple Silicon performance or invested in dedicated hardware. Even Apple's Xcode Cloud often provides better iOS build performance than GitHub's shared runners.

Scaling challenges: Teams with many active developers (50+) often find GitHub Actions becomes a bottleneck during peak development hours. Netflix uses GitHub Actions for open source shit but runs their real infrastructure on something that actually works.

The common theme is using GitHub Actions where convenience matters, but migrating performance-critical builds elsewhere. Buildkite's case studies demonstrate how teams handle CI/CD at scale when GitHub Actions doesn't meet their performance requirements.

Platform Comparison: What I've Actually Tested

Platform

Queue Experience

Performance Notes

Resource Options

Best Use Cases

GitHub Actions

Fast when nobody's awake,
fucked during business hours

Slow as shit when you need it

2-core or 8-core, nothing else

GitHub repos (if you're stuck)

CircleCI

Actually starts when you push

Actually works like CI should

Pick what makes sense

Docker builds, when speed matters

GitLab CI/CD

Starts when it should

Decent if you use their registry

Your hardware, your rules

When you hate jumping between tools

Blaze (GitHub Actions)

Still GitHub's shit queue

Finally, runners that aren't from 2019

M4 Pro instead of old Intel

iOS builds (worth the extra cost)

Buildkite

Your infrastructure, your speed

Fast as whatever you give it

Your money, your choice

When you know what you're doing

Azure DevOps

Works fine for Microsoft stuff

Better for Windows than others

Windows-focused options

.NET projects, enterprise Microsoft

What I've Learned Setting Up CI/CD Alternatives

High-Performance CI/CD Architecture

I've migrated several teams from GitHub Actions to other platforms over the past year. Each migration taught me something different about what actually makes builds faster. Here's what I've figured out through trial and error.

CircleCI: What Actually Made a Difference

The first thing I noticed about CircleCI was the predictable start times. Builds just... start. No sitting around wondering if you're stuck in a queue.

Docker Layer Caching (DLC): This was the game-changer for our Docker builds. CircleCI's Docker Layer Caching costs extra but actually fucking works. Instead of rebuilding the same FROM node:18.17-alpine layer every goddamn time, it just reuses it. Our Docker build went from 12 minutes to 5 minutes. Not because I'm smart, but because CircleCI isn't stupid.

Resource Classes: Unlike GitHub's "2-core or 8-core, take it or leave it" approach, CircleCI lets you pick what makes sense. I usually start with large (4 CPU, 15GB RAM) for most builds. If you have a lot of parallel tests, xlarge (8 CPU, 32GB RAM) is worth it. The 2xlarge option exists for really big builds, but I haven't needed it yet.

Test Splitting: This took me a few tries to set up correctly. CircleCI's test splitting looks at how long your tests took previously and distributes them across containers. When it works, it works well.

Basic setup that worked for me:

jobs:
  test:
    resource_class: large
    parallelism: 3
    docker:
      - image: cimg/node:18.17
    steps:
      - checkout
      - run: |
          # This splits tests based on previous timing data
          circleci tests glob \"test/**/*.js\" | \\
          circleci tests split --split-by=timings

The learning curve isn't terrible if you're coming from GitHub Actions. Check their case studies if you want to see who else got tired of GitHub's bullshit.

GitLab CI: Self-Hosted and Registry Benefits

GitLab Logo

GitLab CI was interesting to test because you can self-host runners pretty easily. The main advantage I found was having everything in one place - code, CI, and container registry.

Integrated Container Registry: When you use GitLab's own registry for your images, pulls are noticeably faster. Makes sense since there's no external network hop. I saw this especially with larger base images.

Self-Hosted Runner Flexibility: Setting up your own runners isn't too complicated. The auto-scaling documentation is actually readable, unlike some platforms. You can scale based on how busy your queue gets instead of waiting for someone else's scheduler.

Caching Across Runners: GitLab's distributed caching lets different runners share cached artifacts. This helps when you have multiple runners and don't want to rebuild everything from scratch on each one.

Configuration that worked well for me:

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_BUILDKIT: 1  # This speeds up Docker builds

build:
  stage: build
  image: docker:20.10
  services:
    - docker:20.10-dind
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - .cache/
      - node_modules/  # Cache dependencies too
  script:
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest .

I spent a week migrating one project to GitLab CI. The registry integration alone made Docker builds faster, and not having to jump between GitHub and other tools was nice. Their customer case studies show how other teams have done larger migrations.

Blaze + GitHub Actions: Apple Silicon for iOS

If you're stuck with GitHub Actions but do iOS development, Blaze runners might be worth checking out. They basically give you Apple Silicon runners instead of GitHub's Intel-based ones.

Apple Silicon Performance: The difference in compile times is real. Our iOS builds that took 40+ minutes on GitHub's Intel runners finish in maybe 15-20 minutes on Blaze's M4 Pro runners. Not exactly 3x faster like their marketing claims, but definitely noticeable.

Warm Environments: One nice thing about Blaze is the runners don't cold-start every time. Dependencies like Xcode and CocoaPods are already there, so you skip some setup time.

Cost Considerations: Blaze costs more per minute than GitHub's runners, but if your builds finish significantly faster, you might pay less overall. Do the math for your specific situation - don't just assume it's cheaper.

Simple setup:

jobs:
  ios-build:
    runs-on: blaze/macos-14  # Instead of macos-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build iOS App
        run: |
          # Same build commands as before
          xcodebuild -workspace App.xcworkspace \\
                     -scheme App \\
                     -archivePath App.xcarchive \\
                     archive

I tried this on a side project and it worked as advertised. I've seen 40-minute builds drop to 18 minutes on Apple Silicon. Not the 3x improvement the marketing claims, but definitely worth it if you're not paying for builds out of your own pocket.

Buildkite: When You Control the Infrastructure

Buildkite is different - you provide the runners, they provide the orchestration. I haven't used it extensively, but the concept makes sense if you have specific infrastructure requirements.

Your Hardware, Their Coordination: You can run builds on exactly the hardware that makes sense for your workload. Need NVMe storage for Docker builds? Set it up. Want high-memory instances for compilation? Your choice.

Network Proximity: Since you control the infrastructure, you can put runners close to your deployment targets and artifact storage. No external network hops slowing things down.

Custom Caching: You're not limited to whatever caching the platform provides. Design caching that makes sense for your tech stack.

Basic pipeline configuration:

steps:
  - label: \"Docker Build\"
    command: |
      # Use whatever caching strategy works for you
      docker build \\
        --cache-from $(docker images -q | head -1) \\
        --tag myapp:${BUILDKITE_COMMIT} .
    agents:
      queue: \"docker-builds\"  # Your custom queue

The trade-off is infrastructure management. You're responsible for keeping runners healthy, scaling them, and handling the operations side. Look at their case studies - it's mostly teams who got sick of waiting for builds. Their agent docs explain the setup process if you want to explore it.

Mistakes I Made (So You Don't Have To)

GitHub Actions mistakes that cost me money:

  • Used large runners for ESLint. $0.008/minute vs $0.032/minute to run 30 seconds of linting. Math is hard.
  • No dependency caching for the first month. 3 minutes to npm install the same fucking packages every time.
  • Matrix builds all waited on each other because I forgot needs: []. Sequential builds pretending to be parallel.

CircleCI fuck-ups that cost me money:

  • Picked xlarge for everything like an idiot. Burned through $200 in a week running linters on 8-core boxes.
  • Forgot to enable Docker Layer Caching and wondered why builds were still slow. Missed the whole fucking point.
  • Set up 3 parallel containers but fucked up the test splitting. Container 1 ran 500 tests, containers 2 and 3 sat there jerking off.

GitLab CI configuration disasters:

  • Used Docker Hub instead of GitLab's registry. Added 30 seconds of network overhead to every pull.
  • Fucked up the auto-scaling config and got ERROR: This job is stuck, because you don't have any active runners for 2 hours.
  • Set cache keys to ${CI_COMMIT_SHA} instead of ${CI_COMMIT_REF_SLUG}. Every build got a unique cache key so nothing ever got cached. Brilliant.

What to Actually Measure

I track a few simple metrics to see if changes actually help:

Build times I care about:

  • How long do typical builds take? (Aim for under 10 minutes for most stuff)
  • What's the worst-case build time? (Should be under 20 minutes for complex workflows)
  • How fast do tests fail when they're going to fail? (Want feedback in under 5 minutes)

Queue behavior:

  • How long do I wait during busy hours? (Goal: under 2 minutes most of the time)
  • Are builds actually running in parallel when they should be?

Team impact:

  • Are we deploying more or less often than before?
  • How long from commit to production?
  • Are people complaining about CI in standups?

The main thing is whether the CI system helps or hinders your development flow. If you're constantly waiting or working around CI limitations, it's time to try something else.

Frequently Asked Questions

Q

How do I know if GitHub Actions is actually slowing down my team?

A

Signs your CI is fucked:

  • People open Netflix while waiting for builds
  • "Is it still running?" gets asked in every standup
  • You're checking build status during bathroom breaks
  • Nobody wants to deploy on Fridays because CI takes too long
  • Junior developers think waiting 20 minutes for linting is normalNuclear option for measurement: Time how long it takes to fix a typo in your README and deploy it.

If that's more than 5 minutes, your CI is the problem.```bash# Check how fucked your build times aregh run list --limit 20 --json status,conclusion,created

At,updatedAt# Pro tip: pipe to jq if you want to do math on the times```Git

Hub Actions works fine if you like waiting. If you actually want to ship code instead of watching progress bars, try something else.

Q

Which platform works better for Docker builds?

A

![Buildkite Logo](https://buildkite.com/_astro/buildkite-logo-on-light.

CD-oo-SO.svg)CircleCI crushes GitHub Actions for Docker builds. Their layer caching costs extra money but actually fucking works.Real performance I've measured:

  • GitHub Actions: 12-15 minutes rebuilding the same Node.js base image every goddamn time
  • CircleCI with DLC: 5-7 minutes because it's not stupid enough to rebuild layers that haven't changed
  • GitLab CI: 8-10 minutes, better when using their registryCircleCI's caching isn't magic
  • it just works like Docker caching should.

Instead of being dumb as rocks like GitHub.Basic CircleCI Docker setup:```yamljobs: build: docker:

  • image: cimg/node:
    18.17 steps:

  • setup_remote_docker: docker_layer_caching: true # Costs extra but worth it for Docker-heavy workflows

  • checkout

  • run: docker build -t myapp .```Your results might vary depending on your Docker setup and base images, but CircleCI's caching has been more consistent in my testing.

Q

Is Blaze worth it for iOS development?

A

**Probably, if you do a lot of i

OS builds.** GitHub's macOS runners are Intel-based, which shows in compile times for Xcode projects.Cost comparison from my testing:

  • GitHub Actions: i

OS builds typically take 35-50 minutes, costs around $3-4 per build

  • Blaze:

Same builds finish in 15-25 minutes, costs maybe $3-5 per build depending on usage

Blaze is more expensive per minute, but builds finish faster, so total cost is often similar or lower. The real benefit is time saved.What you get with Blaze:

  • Apple Silicon (M4 Pro) instead of Intel runners
  • Faster compile times for Swift/Objective-C code
  • Build environments that don't cold-start every time
  • More predictable iOS build performanceConsiderations:
  • If you only build i

OS apps occasionally, the cost difference might not justify it

  • If your team builds multiple times daily, the time savings add up
  • Your specific build complexity affects the performance improvementYour mileage will vary depending on how much your Xcode project sucks, but the Apple Silicon difference is real.
Q

How much faster is CircleCI compared to GitHub Actions?

A

Performance differences I've observed:

  • React test suite:

Around 9-10 minutes on CircleCI vs 12-15 minutes on GitHub Actions

  • Queue times: Circle

CI typically starts within 1-2 minutes, GitHub can be 5-25 minutes during peak hours

  • Docker builds:

CircleCI with layer caching usually 30-50% faster than GitHub's basic cachingThe queue reliability is the bigger factor: Circle

CI starts builds more predictably.

GitHub's performance varies significantly based on when you're building.I haven't tested at massive scale, but CircleCI seems to handle concurrent builds more consistently than GitHub Actions. GitHub's performance degrades noticeably when many teams are building simultaneously.Bottom line: Our deploys went from "maybe 20 minutes, maybe 40" to "consistently 12-15 minutes" when we switched to Circle

CI. Honestly, knowing it'll take 15 minutes instead of 'maybe 20, maybe 40' was almost better than the speed.

Q

Can I optimize GitHub Actions instead of switching?

A

Optimizations that actually help:

  • Use larger runners (8-core) for CPU-intensive builds, but only where you can utilize the extra cores
  • Set up caching with actions/cache@v4 for dependencies and build outputs
  • Split test suites across multiple runners using matrix builds
  • Optimize Dockerfiles since GitHub's layer caching isn't as comprehensive as other platformsWhat I found less effective:
  • Large runners cost significantly more but don't always provide proportional speedup
  • Self-hosted runners solve some performance issues but create infrastructure management overhead
  • Many marketplace actions add complexity without meaningful performance improvementRealistic expectations: Good optimization might get you 20-40% improvement on GitHub Actions. Platform switches can provide 50-100% improvement in the right scenarios. The math depends on your specific workflows and performance requirements.
Q

What CI/CD works better for larger engineering teams?

A

CircleCI and GitLab CI tend to handle team scale better than GitHub Actions in my observation.Why CircleCI scales well:

  • Handles many concurrent jobs more reliably
  • Resource classes let you separate heavy builds from light ones
  • Pay-per-use pricing scales with actual usage rather than fixed capacityWhy GitLab CI scales well:
  • Self-hosted runners give you control over capacity
  • Auto-scaling configuration is more flexible
  • Integrated approach reduces dependencies between toolsGitHub Actions scaling challenges:
  • Queue performance becomes less predictable with many concurrent jobs
  • Shared runner pool means you compete with other teams for resources
  • Limited ability to prioritize different types of buildsCommon pattern I've seen: Many larger teams use a hybrid approach
  • keep Git

Hub Actions for basic checks and pull request validation, but move deployment and performance-critical builds to more specialized platforms. This gives you GitHub integration convenience while avoiding the bottlenecks.

Q

How do I convince my manager to try an alternative?

A

Focus on the business impact: Calculate roughly how much time your team spends waiting for builds.

Even conservative estimates add up to real costs.Simple business case:

  • Track how much time developers spend waiting for CI each week
  • Multiply by fully-loaded hourly costs (salary + benefits + overhead)
  • Compare that annual cost to alternative platform pricing
  • Include the impact on deployment frequency and developer satisfactionStart small: Test an alternative on 1-2 representative repositories for a month. Measure build times, queue times, and developer feedback. Let the data make the case.
Q

What's the simplest way to test alternatives?

A

**Parallel testing approach:**Set up one alternative platform (Circle

CI, GitLab CI, etc.) on a representative repository.

Run identical workflows on both GitHub Actions and the alternative for 2-3 weeks.Metrics to track:

  • Total time from commit to completed build
  • Queue wait times during different parts of the day
  • Build failure rates and debugging difficulty
  • Developer satisfaction and workflow changesQuestions for your team:
  • Which platform had more predictable performance?
  • How often did you have to wait for builds to start?
  • Did either platform change how you approach commits or deployments?If the alternative provides clear benefits in your actual usage patterns, you have data to support a broader migration. If not, you've validated that GitHub Actions meets your needs or identified specific optimization opportunities.

Performance Testing & Benchmarking Resources