Skip to main content

Render Cut Docker Builds From 87s to 32s: Where the Seconds Went and How to Match It on Hardware You Own

10 min readDora NodaDora Noda
Share
On this page

Fifty-five seconds. That's the whole story: in June 2026, Render reported that the median Docker build on its platform had fallen from 87 seconds to 32 — a 60% cut achieved not with faster chips, but with upload tuning, scheduling fixes, and cache plumbing. Then in August, it came back for the rest, moving builds onto faster CPU-and-disk nodes for another 40% off median build times across every runtime.

If you run a platform — or you're deciding whether to keep paying for one — those two changelog entries are worth more than most performance guides. They itemize, with production medians attached, exactly where time goes in a git-push container build. And every item on the list is reproducible on hardware you own.

The 55 seconds, itemized up front

Render's June 11, 2026 changelog names four optimizations behind the 87-to-32 drop:

#LeverWhat it fixes
1Tuned chunk size and parallelism for build image uploadsLayer-push throughput: many small layers saturate a connection better in parallel with right-sized chunks
2Faster build scheduling on Render infrastructureQueue time: how long a build waits for a builder before doing anything at all
3Parallelized image upload with cache exportPipeline tail: pushing the final image and writing the build cache used to run serially
4Build cache stored in Render's own registry, with automatic pruningCold cache: every build reusing prior layers instead of rebuilding the world, without unbounded cache growth

No new CPUs. No exotic builder fleet. The median build was spending most of its life waiting — waiting for a builder, waiting on uploads, waiting on a cache that wasn't there. That is the single most important sentence in this post: a typical PaaS build is a waiting problem, not a compute problem. Keep it in mind, because it dictates the entire self-hosted recipe.

Where the 55 seconds lived

Uploads: the quiet majority. A Docker build on a hosted platform moves bytes twice — the build context goes up, and the resulting layers come back down into a registry. For a typical web app image (a few hundred megabytes across a dozen-plus layers), layer-push round trips dominate wall-clock time once compilation itself is cached. Tuning chunk size and request parallelism is unglamorous work, but on high-latency paths between builder and registry it is the difference between a connection that idles and one that stays saturated. Render listed it first for a reason.

Scheduling: the invisible tax. Before a single Dockerfile instruction runs, a build waits for a free builder with capacity. On a shared multi-tenant fleet, that queue is where p50 goes to die: a few seconds of scheduling delay on every build adds up to a large slice of an 87-second median. Speeding up scheduling — placing builds faster, keeping warm capacity — buys wall-clock time with zero change to the build itself. This is pure platform engineering, and it's the lever most self-hosted setups accidentally leave on the table by provisioning builders on demand instead of keeping a warm pool.

The serial tail. Traditionally, a build exports the final image, pushes it, and then writes its layer cache — three steps in a trench coat walking single file. Parallelizing the image push with the cache export collapses the tail of every build. It changes nothing about what gets computed; it just stops doing two network-bound jobs one after another.

The cache, made durable. This is the big one. A build cache that evaporates between builds — because the builder was ephemeral, or the cache lived on a disk that got reclaimed — turns every deploy into a near-cold build: dependencies re-downloaded, compilation units rebuilt, base layers re-pulled. Storing the cache in a registry makes it survive builder churn, and automatic pruning keeps it from growing into a liability (stale cache entries slow down cache resolution and eat storage budget). Render's phrasing matters here: registry-backed plus pruned. A cache without eviction policy is a future incident.

Then August finished the job from the other direction: faster CPU and disk on the build nodes, worth another 40% median reduction across all runtimes. Note the ordering. Render squeezed the waiting out first and only then paid for faster hardware. That sequence is the correct one, and it tells you what to copy.

The self-hosted recipe, lever for lever

Every one of Render's four levers has a direct owned-hardware equivalent. Here they are in the same order.

1. Uploads: keep the builder close to the registry. On your own fleet, builder-to-registry traffic rides your own network — private networking between nodes rather than trips across the public internet. Run the registry in the same region (or the same cluster) as the builders, enable registry-backed layer caching, and you get Render's upload tuning essentially for free:

bash
docker buildx build \
  --cache-to type=registry,ref=registry.internal/app:buildcache,mode=max \
  --cache-from type=registry,ref=registry.internal/app:buildcache \
  --push -t registry.internal/app:latest .

mode=max caches every stage, not just the final one — the difference between "rebuilds are fast when only the last layer changed" and "rebuilds are fast, period."

2. Scheduling: run warm, persistent builders. This is where self-hosted setups most often lose to managed platforms — not on compute, but by building on cold, ephemeral runners. The fix is a long-lived BuildKit daemon with a persistent cache volume, addressed remotely:

bash
docker buildx create --driver remote --name fleet-builder \
  tcp://builder-01.internal:1234

A persistent buildkitd keeps its local layer cache hot between builds, so repeat deploys for the same service skip straight to the changed layers. Operators who have compared approaches converge on the same conclusion: a persistent BuildKit daemon with a cache volume beats spinning up a fresh builder per build for exactly the "watch a repo, rebuild often" loop a PaaS runs all day. (Note that Kaniko — the classic daemonless in-cluster builder — is now archived upstream, so BuildKit is also simply the maintained choice.)

3. The tail: BuildKit already pipelines export and push. Modern BuildKit parallelizes pushing the result image with exporting cache metadata when both target registries — you get Render's third lever by using a current builder and pointing --cache-to at the registry rather than a local directory that then needs a separate upload step.

4. The cache: registry-backed, with a pruning policy you actually enforce. Two mechanisms stack here. Registry cache (--cache-to/--cache-from above) survives builder replacement. BuildKit cache mounts handle the inside of a build step — dependency directories that should persist across builds without becoming image layers:

dockerfile
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline
dockerfile
RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/usr/local/cargo/git \
    --mount=type=cache,target=/build/target \
    cargo build --release

Real-world reports put the payoff at an order of magnitude or more on the common case: one Rust project measured an incremental rebuild after a one-line change at 6 seconds against a full-tree rebuild before — roughly 16 to 50 times faster. The gotcha to respect: cache mounts are not image layers, so compiled artifacts must be copied out to the build context before the runtime stage can use them. And mirror Render's discipline on pruning — schedule docker buildx prune (or registry garbage collection) so stale cache doesn't accumulate silently.

The August lever: fast local disk on the builder. NVMe-backed builders are the cheapest hardware upgrade in this whole recipe, and it lands exactly where Render's follow-up landed: dependency unpacking, layer compression, and local cache I/O are all disk-bound long before they're CPU-bound.

What a budget builder box realistically delivers

So what does this recipe produce on modest owned hardware — say, a single-digit-euro-per-month cloud builder in the Hetzner CX class? Set expectations by build temperature, because temperature is the whole game:

  • Cold build (nothing cached: fresh builder, new service): minutes, dominated by dependency downloads and full compilation. No platform on earth avoids this; Render's 87-second median was never measuring cold builds either — medians over a fleet with cache hits baked in are mostly warm.
  • Warm rebuild (dependency change only): tens of seconds. Base layers and the build cache hit; only the dependency layer and above rebuild.
  • Warm rebuild (app-code change only): seconds to tens of seconds. Everything below the final COPY hits cache; the platform pushes a few changed layers.

That warm-code-change number is your Render-32-seconds comparator, and it is entirely reachable: warm buildkitd plus registry cache plus cache mounts puts the typical Node or Python service rebuild in the same tens-of-seconds band, on hardware that costs less per month than a single seat of most hosted platforms.

And here is the part the TODO item insisted on, because it's the part everyone gets backwards: the biggest wins are cache locality and scheduling, not raw CPU. Doubling builder cores does almost nothing for a warm rebuild that spends 80% of its time pushing layers and resolving cache. A small builder with a hot cache beats a large builder with a cold one every time. Spend your budget in this order: persistent builders first (kill cold starts), registry-backed cache with pruning second (survive builder churn), builder-registry network proximity third (kill upload waits), and only then bigger CPUs or faster disks.

The honest limits, stated plainly:

  • Someone has to run the pruning, rotate the registry credentials, and keep buildkitd healthy. Managed platforms fold that labor into the bill; self-hosting itemizes it as your time.
  • Cache mounts and registry caches are accelerants, not magic: a monorepo with poor layer ordering (source copied before dependencies installed) defeats every cache on this page. Dockerfile hygiene — dependencies first, source last, .dockerignore kept tight — is a prerequisite, not an optimization.
  • Multi-architecture builds (amd64 + arm64) roughly double build work unless you provision native builders per arch or accept emulation overhead. Depot's observation holds generally: native CPUs are a large share of remote-builder speed, so match builder arch to deploy arch.

Builds are a cache-locality problem

Render's two changelog entries tell a clean story when read together: first they eliminated the waiting (uploads, scheduling, serial tails, cold caches) for a 60% cut, then they bought faster hardware for another 40%. The sequence is the lesson. Teams that start by renting bigger builders are optimizing the smallest slice of the pie; teams that start with warm builders, registry-backed caches, and sane layer ordering get the hosted numbers on owned metal.

That ordering is also why build performance belongs in the self-hosting decision at all. Slow builds are routinely cited as a reason to stay on a managed platform — but the mechanism behind fast managed builds isn't proprietary. It's BuildKit, a warm cache, and a short network path to a registry: all open, all operable, all cheap to run.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Sources

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex