Skip to main content

Render's 75% Faster Python Builds, Deconstructed: What a Self-Hosted PaaS Must Cache to Match It

10 min readDora NodaDora Noda
Share
On this page

Render's pitch for its 2026 native-environments stack is unusually specific: Cloud Native Buildpacks deliver Python builds up to 75% faster than a hand-written Dockerfile, thanks to multi-stage builds, hermetic dependency caching, and runtime slimming. Most vendor speed claims are vague enough to be unfalsifiable. This one names a number — which means it can be taken apart.

Here is the verdict up front: the 75% is a cache-locality number, not a buildpack-format number. The speedup comes from restoring warm dependency layers on the build node instead of re-resolving and re-downloading them, and a well-layered Dockerfile on an equally warm node lands in nearly the same place. Buildpacks win by making the fast path the default path. If you run a self-hosted PaaS and want the same number on your own hardware, you don't need a new image format — you need warm build state, cache that survives between builds, and dependency-first layer ordering.

To be transparent about sourcing: Render markets the speedup as a property of its buildpacks stack, and I could not independently verify the exact test behind the 75% figure — Render's docs describe the caching behavior, not the benchmark. So treat the number below the way you should treat every vendor number: as a warm-cache measurement to reproduce, not a law of physics.

What Render actually caches

Strip the marketing terms and Render's native Python builds do three concrete things. First, they persist well-known dependency paths between builds — community threads confirm Render caches directories like the project's virtualenv across deploys. The dashboard offers "clear build cache and deploy" precisely because stale cache is a real failure mode: users hit cases where edited build scripts or monorepo symlinks weren't picked up until the cache was cleared.

Second, the build downloads that cache before running your build command, so dependency installation usually resolves against warm bytes rather than the public PyPI index.

Third, where Cloud Native Buildpacks are in play, the CNB lifecycle's restore phase re-materializes cached layers from the previous build before detection and build even run — Paketo's own tutorial notes the second build "should be notably faster because the buildpacks are able to leverage the cache from the previous build."

None of this is exotic. It is layer-cache locality plus dependency caching, operated for you. The question is how much of the 75% each piece explains — so let's total it.

The worked breakdown: naive Dockerfile vs cold buildpack vs warm buildpack

Take a typical service the claim is about: a FastAPI app, Python 3.12, roughly 40 pinned dependencies in requirements.txt, built on python:3.12-slim. Times below are representative working numbers drawn from public build reports (pip installs of this size routinely take 5–15 minutes cold; uv is 10–100x faster at the install step; good layer caching takes an 8-minute rebuild down to ~30 seconds). Your mileage varies with dependency count and network — the structure is the point.

Build phase(a) Naive Dockerfile, cold node(b) Buildpack, cold node(c) Buildpack, warm node
Base image + stack pull~90s~90s0s (cached)
System packages, runtime setup~45s~30s0s (cached layer)
Dependency install (pip, ~40 deps)~480s~420s0s (restored layer)
Restore/download cached layers0s0s~60s
App copy, bytecode compile~20s~25s~25s
Export final image~25s~30s~40s
Total~660s (11 min)~595s (~10 min)~165s (under 3 min)

Column (c) versus column (a): (660 − 165) / 660 ≈ 75%. There is the vendor number, reproduced from cache physics alone. Note what it actually compares: a cold, cache-hostile baseline against a warm, cache-friendly run. Columns (a) and (b) differ by barely 10% — the buildpack format itself, with no cache behind it, is not meaningfully faster than a Dockerfile.

Now the sensitivity analysis the marketing never shows — the same table across the cache states real teams actually build in:

Change typeNaive Dockerfile (warm node, decent layering)Buildpack (warm node)Delta
Code-only change, deps untouched~45s (only app layer rebuilds)~60–90s (restore + rebuild + export)Rough parity, Dockerfile can win
Lockfile/dependency change~6–8 min (full reinstall)~6–8 min (cache miss, full reinstall)~0 — the 75% evaporates
Base-image bumpNear-cold for bothNear-cold for both~0

This is the honest version of the claim. The 75% is real conditional on the most common deploy shape — pushing app code without touching dependencies — and it vanishes exactly when dependencies change, which is also when builds are slowest and developers are watching the clock. Both stacks share this property. It is not a buildpack advantage; it is what "cache hit" means.

Why buildpacks alone don't close the gap

"Hermetic dependency caching" sounds like a format feature, but mechanically it is a discipline feature: install dependencies in their own layer, keyed on the lockfile, before app source enters the picture — then never rebuild that layer unless the lockfile changes. A hand-written Dockerfile that copies requirements.txt first, installs, and only then copies app code gets the identical cache behavior. Multi-stage builds and runtime slimming shrink the final image and the export step, worth tens of seconds, not hundreds.

What buildpacks genuinely buy is that this discipline is automatic. The naive Dockerfile in column (a) — COPY . . before RUN pip install, no layer ordering, --no-cache-dir cargo-culted in — is what real teams actually write, and it rebuilds the world on every commit. Paketo and Heroku's Python CNBs impose the fast layout by default, so every tenant gets column (c) behavior without knowing what a layer is.

For a managed vendor serving thousands of tenants who will never tune a Dockerfile, "the default is fast" is a real product advantage. For a platform operator who controls the build pipeline, it is a default you can replicate — which is the next section.

One more consideration cuts the other way: the fastest install step in 2026 isn't pip at all. uv resolves and installs 10–100x faster than pip, and several teams report dependency-install layers dropping from minutes to seconds just by switching the installer. A Dockerfile using uv on a warm node can beat a pip-based buildpack on a warm node. The installer choice outweighs the format choice.

The recipe: matching the number on owned Cluster API nodes

If you operate a self-hosted PaaS on owned hardware — say a Cluster API fleet on Hetzner — here is what actually stands between you and column (c). None of it is the image format.

1. Give builds a cache that survives the build pod. This is the entire game. Ephemeral build pods with empty layer stores rebuild cold every time, and no buildpack fixes that — the CNB lifecycle can only restore what was persisted. kpack, the Kubernetes-native CNB build service, supports exactly two answers: a persistent-volume cache or a registry-backed cache image. Pick one deliberately. Registry cache (mode=max for ephemeral runners, mode=min suffices when a warm local store already holds intermediates) survives node turnover; volume cache is faster but ties warmth to a specific node. Teams that skip this step and run CNB builds on throwaway nodes get column (b) forever and conclude buildpacks "didn't help."

2. Keep build state warm on purpose. A dedicated build node (or small pool) with a persistent containerd/BuildKit layer store turns every tenant's second build into a warm build. Public reports from self-hosted CI are unambiguous: warm local caches finish in a fraction of the time, while cold ephemeral runners re-pull everything over the network on every run. Size the pool's disk generously — cache is only warm if garbage collection hasn't evicted it — and monitor cache-hit rate as a build-system SLO, not a curiosity metric.

3. Enforce dependency-first layer ordering for Dockerfile builds. If your platform also accepts raw Dockerfiles (most migrations off Render bring them along), lint or default them into the fast layout: lockfile copy, dependency install, then app source. Better, offer a platform-owned base image path so tenants inherit good layering instead of hand-rolling it. This single convention is most of the gap between columns (a) and (c).

4. Swap the installer. Default Python builds to uv where the ecosystem allows. It is the cheapest minutes-per-build reduction available — minutes become seconds at the install step regardless of cache state, which also softens the lockfile-change worst case where caching can't help.

5. Treat the build fleet like production. Pin builder versions with app builds (a builder upgrade invalidates CNB layer compatibility and causes a fleet-wide cold snap), schedule base-image bumps off-peak, and expect multi-arch to roughly double cache footprint. The honest-limits section below is an operations checklist as much as a caveat list.

The honest limits

Four things bound this number no matter whose logo is on the builder. First builds are always cold: a new service, a new build node, or a cleared cache pays the full column (a)/(b) price, and platforms that autoscale build capacity to zero relearn this on every scale-from-zero event. Lockfile changes invalidate the dependency layer on every stack — the slowest builds are structurally uncacheable, improvable only via faster installers.

Disk pressure silently converts warm builds to cold ones when GC evicts layers the next build needed; if build times creep up fleet-wide with no code change, check disk before blaming the network. And multi-arch builds multiply cache volume per architecture, so the "warm node" assumption costs roughly twice the disk tenants expect.

Notice Render faces every one of these too — "clear build cache and deploy" exists because managed caches go stale, and dependency-change builds are slow on Render for the same physical reasons. The vendor advantage is operational labor, not different physics.

The takeaway for platform teams

Vendor build-speed numbers deserve the same treatment as vendor pricing pages: reproduce the measurement, find the warm cache inside it, then decide whether to rent that warmth or own it. Render's 75% decomposes into ~10% format and tooling and ~65 percentage points of cache locality — and cache locality is purchasable with a persistent volume, a registry cache image, and a build node that doesn't get wiped between runs. On a flat-rate Hetzner box, that warmth has no per-minute meter attached, which is precisely the economic argument this publication keeps making: once you own the hardware, every optimization that managed vendors sell you per-build-minute is one you keep for free.

Whether you standardize tenants on buildpacks or curated Dockerfiles, bex builds git-backed services inside your own cluster — Dockerfile and Render-native-runtime builds run as isolated BuildKit jobs and buildpack builds via kpack, with the cache living on hardware you own. Star the repo on GitHub or deploy your first app today.

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