Why CI/CD inefficiencies hurt time to market?

Explained how ephemeral CI runners, cache compression tax, and shared compute silently drain engineering hours and how a persistent runner architecture fixes the issues.

Why CI/CD inefficiencies hurt time to market?

Modern CI/CD pipelines are supposed to accelerate delivery.

Yet many teams experience the opposite.

A one-line pull request triggers a workflow that spends more time preparing the environment than running the actual build or test suite. Engineers merge a trivial dependency bump, wait several minutes for CI to finish, and move on. Individually, the delay feels insignificant. Across hundreds of pull requests, it becomes a measurable drag on engineering throughput.

The frustrating part is that the slowdown often isn't caused by your code.

It's caused by the architecture underneath your CI runner.

The Hidden Waiting Tax

Consider a typical GitHub Actions workflow for a Node.js application.

A small pull request modifies a single API endpoint and triggers the following pipeline:

▶ Checkout repository          8s
▶ Setup Node.js               12s
▶ Restore package cache       34s
▶ pnpm install                51s
▶ Restore Docker cache        42s
▶ Docker build               118s
▶ Run tests                   19s
▶ Upload artifacts            16s

Total runtime:               300s
Actual test execution:        19s

The tests themselves complete in under 20 seconds.

Everything else is environment reconstruction.

Why This Happens

Most CI systems rely on ephemeral runners.

Each workflow starts on a fresh virtual machine with an empty filesystem. No package cache. No Docker layers. No dependency tree. No build artifacts.

Every run must rebuild state from scratch.

The workflow becomes a cycle of:

  1. 1Download state
  2. 2Reconstruct state
  3. 3Execute workload
  4. 4Compress state
  5. 5Upload state

Repeat for every commit.

For organizations running hundreds or thousands of builds daily, this hidden waiting tax compounds into thousands of lost engineering hours annually.

Package Manager Cache Mechanics and the Compression Tax

Most engineers think of caching as a local disk operation.

In CI, it is usually a distributed storage operation.

When GitHub Actions restores a cache, it is not reading directly from local storage. Instead, it downloads a compressed archive from remote object storage, extracts it, and recreates the original directory structure.

The workflow looks roughly like this:

node_modules/
      |
      ▼
  tar.gz archive
      |
      ▼
Object Storage
      |
      ▼
Download
      |
      ▼
Extract
      |
      ▼
Restored Cache

A typical setup looks like:

name: CI
on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - run: pnpm install --frozen-lockfile
      - run: pnpm test

The configuration looks simple.

The underlying operations are not.

A large dependency cache frequently generates logs resembling:

Run actions/cache@v4

Cache Size: ~2.1 GB

Downloading cache archive...

315 MiB / 2.1 GiB
812 MiB / 2.1 GiB
1.4 GiB / 2.1 GiB
2.1 GiB / 2.1 GiB

Average Speed: 145 MiB/s

Extracting cache archive...

tar -xzf cache.tgz

real    0m43.2s
user    0m36.4s
sys     0m11.8s

The workflow spends nearly a minute doing nothing except reconstructing files that already existed during the previous run.

This phenomenon is often called the compression tax.

The cache hit exists.

But the cost of accessing the cache remains high because the runner cannot access it locally.

Teams trying to optimize GitHub Actions cache performance often discover that cache transfer time eventually dominates pipeline duration.

The cache system works exactly as designed.

The architecture underneath it becomes the bottleneck.

Why Docker Builds Feel So Slow in CI

Docker introduces a second bottleneck.

Locally, Docker builds are fast because layer caches live permanently on disk.

When BuildKit encounters an unchanged layer:

Layer already exists
→ Reuse layer
→ Continue build

No network operations occur. No downloads occur. No decompression occurs.

CI runners behave differently.

Since runners disappear after execution, Docker layers disappear too.

To compensate, teams use registry-based cache storage.

A common Buildx configuration looks like:

- name: Build Docker Image
  uses: docker/build-push-action@v5
  with:
    context: .
    push: false
    cache-from: type=registry,ref=myapp:buildcache
    cache-to: type=registry,ref=myapp:buildcache,mode=max

This improves cache hit rates but introduces a new dependency: the network.

The build now follows:

Runner
    |
    ▼
Registry
    |
    ▼
Download Layers
    |
    ▼
Extract Layers
    |
    ▼
Resume Build

Typical logs reveal the hidden overhead:

#4 importing cache manifest

#4 resolve registry cache
#4 DONE 3.1s

#5 downloading cache layers

sha256:ab91...
Downloading 182 MB

sha256:f872...
Downloading 96 MB

sha256:dd12...
Downloading 241 MB

#5 DONE 31.4s

#6 extracting layers

Extracting layer 1/6
Extracting layer 2/6
Extracting layer 3/6

#6 DONE 17.6s

The cache hit technically succeeded.

Yet nearly 50 seconds were spent simply retrieving and extracting cached data.

This is why docker build caching in CI often feels much slower than developers expect after experiencing local Docker builds.

Local cache hits are disk reads. CI cache hits are distributed storage operations.

Shared Compute Makes Everything Worse

Caching is only part of the problem.

Shared compute introduces additional variance.

Most hosted CI platforms schedule workloads across large pools of shared virtual machines.

  • CPU allocation may vary
  • Disk performance may vary
  • Network throughput may vary

Two identical workflows can produce dramatically different runtimes depending on neighboring workloads.

Engineers frequently encounter situations like:

Build #821  → 7m 12s
Build #822  → 4m 03s
Build #823  → 6m 48s

No code changed. The infrastructure conditions changed.

This unpredictability becomes particularly noticeable during:

  • Large dependency installations
  • Multi-stage Docker builds
  • Monorepo workflows
  • Integration test suites
  • Artifact-heavy pipelines

The result is slower feedback loops and reduced developer confidence in build time estimates.

The Economics of Network-Backed State

Traditional CI architectures effectively turn local disk operations into network operations.

Every cache interaction becomes:

Disk Write
    ↓
Compression
    ↓
Upload
    ↓
Storage
    ↓
Download
    ↓
Extraction
    ↓
Disk Read

The workflow repeatedly pays for:

  • CPU cycles spent compressing data
  • Network bandwidth
  • Object storage transactions
  • Archive extraction
  • Workflow runtime

At small scale this overhead is tolerable. At organizational scale it becomes expensive.

A team running:

  • 500 builds/day
  • 4 minutes of cache overhead/build

Consumes:

500 × 4 minutes = 2,000 minutes/day
                = 33 hours/day
                = 1,000 hours/month

Those hours represent infrastructure spend and delayed developer feedback.

This is one reason many organizations begin searching for a faster GitHub Actions runner or a GitHub Actions runner replacement as repositories grow.

The Real Bottleneck Lives Below the Build System

Package managers are not the bottleneck.

Dockerfiles are not the bottleneck.

BuildKit is not the bottleneck.

The root issue is an infrastructure model that treats storage as disposable.

When runner state disappears after every workflow:

  • Dependency caches must be downloaded
  • Docker layers must be restored
  • Artifacts must be reconstructed
  • Entire environments must be recreated

The result is a system optimized around moving data rather than executing code.

The fastest cache is not a faster tarball.

The Final Problem Solver

The teams looking to reduce such build time issues can switch to Monk CI. Monk CI approaches the problem differently by providing a GitHub Actions runner replacement with persistent SSD-backed infrastructure, faster Docker layer caching, and significantly accelerated Actions cache performance. While GitHub-hosted runners frequently spend time reconstructing build state from remote storage, Monk CI focuses on reducing cache and Docker build overhead at the runner layer, allowing teams to achieve substantially faster workflow execution with minimal workflow changes.

Written by

shankar narayanan

Last updated June 1, 2026