Search for "Render buildpacks faster than Dockerfile" and you'll find the same number repeated across a dozen SEO listicles: 75% faster builds, courtesy of Render's 2026 buildpacks rewrite. Trace that number back to its source and it dissolves — no Render blog post, changelog entry, or docs page says "75%" anywhere.
What Render's own changelog actually documents is three separate, dated, much less dramatic improvements: a 27% cut to Python build times, a 25% cut to Node.js build times, and a 60% cut to Docker build times, each shipped between April and June 2026. None of those numbers compare buildpacks to Dockerfiles at all — they compare Render's build pipeline to itself, before and after.
That's not a reason to ignore the story. It's a reason to tell the real one. The actual changelog entries name the specific mechanisms behind each improvement, and those mechanisms point at a genuine shift happening across the buildpack ecosystem in 2026 — one that Railway made even more explicit the same year, when it retired Nixpacks for a BuildKit-native successor called Railpack. Both moves are about the same underlying change: caching at the level of individual build commands, not build stages. Here's what actually changed, what it means technically, and — since this is the part every self-hoster actually cares about — whether you can get the same caching on your own infrastructure without waiting for a vendor to ship it.
The Claim vs. the Paper Trail
Here's what Render's changelog documents, in order:
| Date | Build path | Before → after | Reduction | Named cause |
|---|---|---|---|---|
| Apr 30, 2026 | Python native runtime | 76s → 55s | 27% | On-disk caching of Python versions + shared universal layers |
| Jun 4, 2026 | Node.js native runtime | 42s → 31s | 25% | Caching Node.js versions, shared universal layers, better build scheduling |
| Jun 11, 2026 | Docker-based services | 87s → 32s | 60% | Chunk-size tuning + parallelism for build image uploads, cache stored in Render's own registry |
Three things stand out. First, every entry is a within-path comparison — Python builds got faster than earlier Python builds, Docker builds got faster than earlier Docker builds. None of them is "buildpacks beat a Dockerfile," which is the comparison the 75% claim implies. Second, the biggest number (60%) belongs to the Docker path, not the native-runtime buildpack path — if anything, that undercuts the "buildpacks are inherently faster" framing rather than supporting it. Third, the mechanisms are unglamorous: upload chunk sizes, parallel restores, and where the cache physically lives. There's no secret buildpack algorithm here — it's infrastructure tuning, applied to whichever build path Render was optimizing that month.
What Actually Changed: Chunking, Parallelism, and Shared Layers
The three mechanisms Render names map directly onto the two things that make any build cache fast or slow: how much you have to re-download, and how much you can restore in parallel.
On-disk runtime caching means a Python or Node.js interpreter, once installed for one build, sits on the build host's disk instead of being re-fetched from a package index on the next build. Universal layers shared across builds means that cached interpreter layer isn't scoped to a single app — it's reused across every service on the same build host that happens to need Python 3.12 or Node 24, which is why this saves time even on a service's first build if a sibling service already primed the cache. Chunk-size tuning and parallelism for build image uploads is about the export phase: instead of pushing one large layer blob to the registry serially, the upload is split into smaller chunks pushed concurrently, which is why the Docker path — where full application layers (not just runtime layers) have to move — saw the largest gain.
None of this is unique to Cloud Native Buildpacks. It's exactly the same category of optimization BuildKit's own --cache-to type=registry,mode=max flag exists to provide for plain Dockerfiles: push every intermediate layer to a registry-backed cache, restore in parallel, and skip re-downloading anything a previous build already fetched. Render applying it across three build paths in three months is a sign the technique matured, not that buildpacks discovered something Dockerfiles structurally can't have.
But there's a layer below "cache more things in parallel" that's specific to how the cache is keyed — and that's the part that's actually new in 2026.
The Real Shift: Per-Command Cache Keys, Not Per-Stage Files
A classic Dockerfile caches at the instruction boundary you wrote. Every RUN line is one cache entry, keyed on that line's exact text plus everything above it — change line 3 of a ten-line Dockerfile and lines 4 through 10 invalidate even if nothing they actually depend on changed. Cloud Native Buildpacks improved on this with a cache = true flag per layer in a buildpack's layers.toml, which is finer-grained than a whole Dockerfile stage, but it's still the buildpack author who decides where the layer boundaries fall.
BuildKit's LLB (low-level build) representation removes the "boundary you wrote" constraint entirely. Instead of a linear list of instructions, BuildKit builds a dependency graph where each node is content-addressed — keyed on its actual inputs, not its position in a file — so independent branches of the graph can execute and cache in parallel, and a change in one branch doesn't invalidate a sibling branch it never depended on. Railpack, Railway's Nixpacks replacement, generates this graph directly: it interfaces with BuildKit to control layers and filesystem state itself, rather than emitting a Dockerfile for BuildKit to interpret.
The practical difference showed up as a real bug Railway had to solve. Railway injects a unique deployment-ID environment variable into every build for tracing. Under naive per-instruction caching, that variable is different on every build, so any layer that references the environment invalidates every single time — deployment IDs silently defeated the entire cache. Railpack's fix is to hash the deployment ID's value and mount a file containing that hash into the build filesystem, rather than setting it as a build-time environment variable directly. The hash only changes when something that should actually bust the cache changes; the deployment ID itself, which changes on every build for reasons that have nothing to do with the app's dependencies, no longer touches the cache key. That's a per-command cache-key decision a Dockerfile has no vocabulary for — you'd need to manually restructure the whole file to isolate that one variable, and most people don't.
Railway Placed the Same Bet — and Published the Receipts
Railway's move away from Nixpacks wasn't primarily about speed — it was forced by how Nixpacks was built. Nixpacks resolved dependencies against specific commits in the nixpkgs repository, which meant only the latest major version of any package was reliably available. Railway tried mapping patch versions to specific commit SHAs by hand; updating those hashes shifted every pinned package at once, and builds that worked yesterday would fail today with no code change. On top of that, Nixpacks dumped every dependency into a single monolithic /nix/store layer, which meant there was no way to cache "just the Python interpreter" separately from "just this app's pip packages" — the whole store either hit cache or it didn't.
Railpack fixes both problems at once by generating layers itself instead of delegating to Nix, and it comes with numbers Railway is willing to publish directly, rather than a vague vendor percentage: Node.js images built with Railpack are 38% smaller than the Nixpacks equivalent, and Python images are 77% smaller. Smaller images mean less to push to the registry and less to pull at deploy time — a second, compounding speed win on top of the cache-hit-rate improvement from finer-grained layers.
Two platforms, the same year, converging on the same fix — proper per-command BuildKit graphs instead of coarse, hand-drawn layer boundaries — is a stronger signal than either vendor's individual marketing number.
Can You Get This Self-Hosted? Yes — Here's the Stack
This is the question that actually matters if you're not on Render or Railway: none of the underlying mechanisms are locked behind either platform.
- Paketo Buildpacks + a cache image.
pack buildsupports--cache-image <registry>/<repo>:cache, which pushes buildpack-contributed layers to any OCI registry you control — a self-hosted Harbor instance, a plainregistry:2container, ECR, GCR, whatever you already run. The next build on a different machine pulls that cache image first. This is the direct, open-spec equivalent of Render's "cache stored in our own registry" — you just point it at your own registry instead of theirs. - kpack on Kubernetes. If you're already running a Cluster API–managed fleet, kpack extends it with
ClusterBuilder,ClusterStack, andImagecustom resources that build CNB images declaratively and rebuild automatically when the source, the buildpacks, or the base stack changes — with the resulting cache pushed to your registry via the samespec.tagmechanism. It's the closest thing to "Render's automatic buildpack pipeline" you can run entirely on your own nodes. - Railpack, standalone. Railpack isn't Railway-exclusive tooling wrapped around a proprietary API — it's an open-source, zero-config builder that emits standard BuildKit LLB and runs anywhere BuildKit does, including a plain
docker buildxinvocation with no Railway account involved. - BuildKit's registry cache, underneath all of it. Whether you're driving Paketo, kpack, or a Dockerfile you wrote by hand,
--cache-to type=registry,mode=max(or the S3 and GitHub Actions backends BuildKit also supports) is the generic substrate every one of the above ultimately calls down to.
What doesn't transfer is Render's specific ops-level tuning — its exact chunk sizes, its upload parallelism settings, and the network proximity between its build hosts and its own registry are internal parameters tuned for their infrastructure, not part of the open Cloud Native Buildpacks spec. You get the same primitives self-hosted; you don't get their exact numbers for free. Whether a 55-second Python build turns into a 40-second one on your own fleet depends on your registry's bandwidth to your build nodes, same as it would for Render on theirs.
For a Cluster API–based platform, that's the actual takeaway: the caching mechanics behind the "75% faster" headline are fully available as open building blocks — Paketo, kpack, and plain BuildKit registry caching cover the same ground Render's proprietary pipeline does. The gap isn't the technique, it's the tuning, and tuning is something you control when the build infrastructure runs on machines you own instead of a vendor's shared fleet.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Render-compatible API on top. If build-cache ownership is the kind of infrastructure decision you'd rather make yourself than inherit from a vendor's roadmap, check out the repo on GitHub.