Render's median service build took 38 seconds in mid-July. Then Render moved builds onto nodes with faster CPUs and disks, and the median dropped to 21 seconds — a 40% cut, across every runtime, from hardware alone. No new cache layer, no build-pipeline rewrite. Just faster machines.
If you run your own build fleet, that number stings a little. Render bought its speedup with a fleet-wide hardware refresh — the one lever a team on owned machines cannot pull casually. Upgrading every node in a small Hetzner fleet to match it would mean replacing machines that are otherwise fine.
But here is the good news: you do not need to. The same 38-to-21-second gap can be closed from the other direction — not by making every second of compute faster, but by deleting the seconds that were never compute at all. This post shows the reproduction up front, verifies what Render actually shipped, and costs out the four levers that get an owned fleet to the same median on a single new machine.
The reproduction, up front: a 38-second build at 21 seconds on one machine
Below is a quantitative model — explicitly a model built from published figures, not a benchmark run on rented hardware — for a concrete typical app: a Node 22 service in a multi-stage Dockerfile (deps stage, Next.js build stage, slim runtime stage), the shape of build that dominates any git-push PaaS fleet. The baseline rows sum to Render's published 38-second median; the "after" column applies cache-first levers on one shared builder and lands at 21 seconds.
| Build phase | Baseline (~38s total) | After cache-first levers (~21s total) | What changed |
|---|---|---|---|
| Base image + layer restore | ~4s | ~1s | Warm BuildKit cache on a persistent builder volume; no re-pull |
| Dependency download + install | ~16s | ~3s | Package-manager cache mounts plus a warm store; downloads dominate this row (a typical project spends the majority of build time fetching packages) |
App compile (next build) | ~11s | ~11s | Unchanged: changed code must recompile, and cache cannot help that row |
| Layer export + registry push | ~7s | ~6s | Marginally faster local NVMe on the shared builder |
| Total | ~38s | ~21s | ~45% reduction, matching Render's 40% class |
Two things to notice before we go further. First, the compile row does not move. That is the honest core of this post: caching deletes download and rebuild seconds, and the ~11 seconds of real compilation is the floor both approaches share. Second, Render and this playbook attack different rows. Render's faster CPU and disk shrink the compile and export rows for every build; the cache-first playbook deletes the download and layer-restore rows for most builds. Both routes arrive near 21 seconds, but only one of them works without replacing your fleet.
The assumed build mix behind the "after" column: warm incremental builds where application code changed but the lockfile did not — the common case on a busy PaaS, where most pushes touch src/, not dependencies. Cold builds and lockfile changes behave differently, and the sensitivity table two sections down states exactly how.
What Render actually shipped (verified)
The numbers above are anchored to Render's August 7, 2026 changelog entry, "Reduced median service build time by 40% (all runtimes)". The entry is short enough to quote in full: Render service builds now run on nodes with faster CPU and disk; the median build time fell 40%, reflected across all runtimes.
Before the rollout (week of July 13), the median was 38 seconds, consistent with prior weeks. Since July 27 — the first full week post-rollout — the highest observed weekly median is 21 seconds, which Render calls "a conservative 40% reduction." (38 to 21 is actually ~45%; conservative indeed.)
The hardware cut did not land in isolation. It followed three single-runtime software wins earlier in the year:
| Improvement | Scope | Median cut |
|---|---|---|
| Docker image builds | Per-runtime pipeline work | 60% |
| Python service builds | Per-runtime pipeline work | 27% |
| Node.js service builds | Per-runtime pipeline work | 25% |
| August 2026 node refresh | Global: faster CPU + disk | 40% |
That sequence is the strategic read. Render pulled the software levers first — per-runtime pipeline optimizations, the kind of work that compounds — and hardware was the remaining lever: one change that speeds up every build in every runtime at once, with no per-app migration. For a vendor operating its own fleet, a node refresh is an operational project with a clean before/after chart. For a team that owns five Hetzner machines instead of a cloud account with elastic capacity, "replace the fleet's machines" is a much heavier project — which is exactly why the cache-first route deserves its own writeup.
Where the 38 seconds go
The model in the first section only convinces if its rows are real. So where does a typical ~38-second PaaS build actually spend its time?
Dependency download dominates. A typical Python project spends roughly 60% of its build time downloading packages — HTTP requests returning the same files every build. Node builds rhyme: npm ci or pnpm install against a registry is mostly network, and on a cold or cache-thin builder it is the single largest row. This is also the row most sensitive to builder placement: a builder with a warm package store skips it almost entirely, while an ephemeral runner pays it on every push.
Layer rebuild repeats solved work. Without a persistent layer cache, each build re-executes Dockerfile steps whose inputs did not change — base-image unpack, OS package installs, dependency stages — because the previous build's layers evaporated with its runner. Teams routinely report cutting image builds from 10+ minutes to 2–3 minutes by adding layer caching plus BuildKit cache mounts, with build-infrastructure costs falling 60–80% alongside. The mechanism generalizes down to the 38-second scale: the seconds are smaller, but the fraction that is repeated work is the same.
Compilation is the real floor. Framework builds (next build, tsc, esbuild bundling), Go/Rust compilation, asset pipelines — this row is genuine CPU work on changed inputs. Faster cores and faster disks shrink it roughly linearly, which is precisely the row Render's node refresh attacked. No cache helps the first compile of changed code; incremental compilers and remote execution can, but that is a deeper project than this post's four levers.
Export and push are disk-plus-network. Writing layers and pushing to a registry depends on local disk throughput and uplink. Faster NVMe trims it modestly — the one row where both approaches overlap, since the shared-builder playbook also lands builds on a machine with fast local NVMe.
The punchline for fleet owners: in the baseline, roughly two-thirds of the 38 seconds is downloads and repeated work. Hardware makes repeated work faster. Caching deletes it. Deletion wins per euro until the repeated-work rows are gone — which is why the playbook below spends money on exactly one machine and gets the rest from software.
The four levers, costed
The strategy is one shared remote builder plus cache everywhere, instead of N upgraded nodes. Builds are bursty — a small fleet's pushes queue behind each other in working hours and the builder idles at night — so one beefy machine absorbs a fleet's build load that would otherwise justify upgrading every node.
Lever 1: One shared BuildKit builder on a Hetzner AX42 (~€46/month). A dedicated AX42 (Ryzen 7 PRO 8700GE, 8 cores/16 threads, 64 GB DDR5, 2×512 GB NVMe) costs on the order of €46/month at list price; its predecessor class, the AX41-NVMe (Ryzen 5 3600, 6c/12t, 64 GB), sits near €37–43/month. Run BuildKit on it as a shared remote builder — every node in the fleet builds with BUILDKIT_HOST pointed at this machine (the docker/buildx remote driver exists precisely for this topology) — and every build in the fleet gets fast cores, fast local NVMe, and, crucially, a cache that survives between builds. Compare with upgrading five app nodes at ~€10–20/month each in step-up cost plus migration downtime: one machine, no fleet churn, and the builder's specs exceed what any single app node needs for its day job. This is the only hardware money the playbook spends.
Lever 2: A persistent BuildKit cache volume (free). Keep the builder's BuildKit state directory on its local NVMe and stop wiping it. That single operational choice — builds reuse the previous build's layers instead of starting cold — is the mechanism behind the largest published speedups in this space: remote-builder vendors attribute the bulk of their gains to persistent layer cache shared across builds, with native CPUs second. Expected contribution: deletes most of the layer-restore row and the unchanged-dependency row on warm builds.
Lever 3: Registry or S3 remote cache (pennies per GB-month). A persistent local volume covers the common case, but cache should survive builder maintenance. Export the cache to the fleet's own registry (--cache-to=type=registry,mode=max / --cache-from) or an S3-compatible backend (type=s3), which BuildKit supports natively. Registry cache adds a pull on cold starts but makes every builder interchangeable; S3 cache costs roughly object-storage rates with lifecycle expiry. Expected contribution: warm-start anywhere, plus cross-builder sharing if the fleet ever grows a second builder.
Lever 4: Package-manager cache mounts (free). RUN --mount=type=cache for npm/pnpm/pip/Go module caches keeps downloaded packages on the builder across builds even when layers invalidate — the cache-mount tier survives exactly the lockfile-adjacent churn that defeats naive layer caching. Expected contribution: shrinks the dependency row from ~16s toward ~3s on warm builds, since the registry round-trips disappear.
| Lever | Monthly cost | Row it attacks | Warm-build effect |
|---|---|---|---|
| Shared AX42 remote builder | ~€46 | All rows (fast CPU/disk baseline) | ~1.2–1.7× on the compute rows |
| Persistent BuildKit volume | €0 | Layer restore, unchanged stages | Deletes most repeat-work seconds |
| Registry/S3 remote cache | ~€1–5 | Cold-start portability | Warm cache on any builder |
| Cache mounts | €0 | Dependency download | ~16s → ~3s on warm builds |
Total new spend: one machine plus single-digit-euro storage. Total effect on the modeled warm build: ~38s → ~21s.
Cold vs warm: the sensitivity the title depends on
The model assumes warm incremental builds. Here is what happens off that assumption — the sensitivity table the title's promise depends on:
| Build type | Share of pushes (typical fleet) | Cache-first result | Hardware-only result |
|---|---|---|---|
| Warm, code-only change | Majority | ~21s: deps + layers cached, only compile runs | ~21s-class: faster CPU shrinks compile |
| Warm, lockfile changed | Minority | Mid-range (~28–32s): dependency row partially re-pays, cache mounts soften it | ~21s-class: hardware does not care what changed |
| Cold (first build, fresh builder) | Rare per app, common fleet-wide after maintenance | ~38s-class: cache helps zero; only the AX42's CPU/disk help (~1.2–1.7× over old nodes) | ~21s-class on refreshed nodes |
| No-op rebuild | Occasional (retries, re-deploys) | Seconds: everything cached | Still pays full compute |
Read it honestly: cache-first wins the common case (warm code-only pushes, and no-ops where it laps hardware entirely) and ties-or-loses the rare cases. Hardware wins uniformly, including cold builds — that is what Render bought, and it is the right purchase at fleet scale where cold builds are a constant background fraction. For a small owned fleet, the calculus flips: cold builds are rare enough per app that optimizing the warm median is the correct trade, and the remote-cache lever exists precisely to make "cold" rarer by surviving builder maintenance.
One more sensitivity worth stating: compiled languages shift the rows. A Rust or Go service with heavy compilation has a larger compile row and a smaller download row, so cache-first buys less and CPU matters more. The playbook still helps (dependencies and layers still cache), but a fleet building mostly Rust should weight Lever 1's CPU choice more heavily — the AX42's 8 modern cores over the older 6-core class — and treat the ~21s figure as Node-shaped, not universal.
When hardware IS the answer
This post argues cache-first, but three situations genuinely want faster machines:
- Cold-build-heavy fleets. If your builder farm scales to zero or wipes state aggressively, every build pays the cold price and only CPU/disk help — fix the persistence first, but until then hardware is the lever.
- Compile-dominated workloads. Rust, C++, large monorepos where the compile row dwarfs downloads; cache deletes the small rows and the big row still wants cores.
- Cache-miss-heavy monorepos. Where every push touches shared layers and invalidates half the cache, the warm assumption fails structurally, and faster compute is the honest fix (alongside restructuring the Dockerfile).
Render's choice sits in this frame cleanly. At their scale, with every runtime's cold builds as a permanent background load and software levers already pulled per runtime, a global node refresh was the highest-leverage remaining move — one project, every build faster, clean chart. A five-machine Hetzner fleet is the mirror image: hardware refresh is the heaviest project (per-machine migration, downtime windows, embodied waste of replaced machines), while one shared builder plus cache is an afternoon's work. Match the lever to the fleet: vendors with elastic capacity buy hardware; owners of small fleets delete work first and buy exactly one machine.
The broader lesson survives either choice. Build time is not one number to push down with undifferentiated compute — it is a budget of rows, and the rows have different owners. Downloads belong to caches, repeated layers belong to persistent builders, compilation belongs to CPUs. Render's 38-to-21 tells you what attacking the CPU rows buys at scale. The ~€46 shared builder tells you what attacking the other rows buys on owned metal. Measure your own rows — BuildKit's per-step timing makes it a one-command audit — then spend where the seconds actually live.
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.



