Skip to main content

Render's 75% Faster Builds, Decoded: What Native Environments Really Measure and the Bar for Self-Hosted Pipelines

11 min readDora NodaDora Noda
Share
On this page

Render's pitch for its Native Environments is simple: skip the Dockerfile, let the platform detect your runtime, and your builds come out dramatically faster — the filed claim is 75% faster than a comparable Dockerfile path, riding on intelligent layer caching. If you run a self-hosted platform with a git-push build step, that number is not a marketing line to admire. It is a benchmark your pipeline now has to beat, or at least meet, with receipts.

Here is the short version of this post: the 75% is real but conditional. It measures a warm rebuild — code changed, dependencies didn't — against a Dockerfile that reinstalls the world on every push. Decode the arithmetic and the gap stops looking like magic and starts looking like a checklist. A self-hosted BuildKit pipeline can clear the same bar, but only if it does every boring caching chore the native path does by default.

Build shapeTypical timeWhat dominates
Cold Dockerfile build (no cache)~6 minutesDependency install from scratch
Native / buildpack warm rebuild (code-only change)~90 secondsApp-code layer only; deps reused
Delta~75% fasterThe dependency layer, skipped
Tuned Dockerfile warm rebuild (ordered layers + cache)~90–120 secondsSame skip, earned by hand

The rest of this post earns that table: what native environments actually do, a worked example of where the minutes go, the three Dockerfile traps that hand Render its 75%, the parity playbook for a self-hosted fleet, and the honest cases where the Dockerfile still wins.


What a native runtime actually does

Render's native runtimes are language-specific build paths: push code, the platform detects Node, Python, Ruby, Go, and the rest, installs dependencies with the ecosystem's own tooling, and runs your build command. Alongside them sit two Docker-specific runtimes — building from a Dockerfile or pulling a prebuilt image — which hand layout, ordering, and caching decisions back to you.

Railway draws the same line with Railpack, its auto-detecting builder that succeeded Nixpacks: source in, optimized image out, no Dockerfile required unless you ask for one.

Under the hood, the native family descends from buildpacks — Heroku's classic buildpacks, standardized as Cloud Native Buildpacks. The reason this lineage builds fast is architectural, and Salesforce's engineering writeup on standardizing Heroku buildpacks states it plainly: buildpacks are app-aware. During bin/build, a buildpack decides which artifacts go into which OCI image layers, so dependencies land in a stable, reusable layer separated from your fast-changing app code.

As Heroku's own CNB announcement puts it, that separation — plus advanced caching and rebase of the base image without rebuilding — is what saves "an enormous amount of time compared to rebuilding from a Dockerfile."

Three mechanisms do the work:

  1. Detection, not declaration. The platform inspects the repo (lockfiles, manifests) and picks the toolchain, so the base toolchain layer is pre-warmed and shared rather than rebuilt per app.
  2. Dependency layering by default. node_modules, site-packages, gems, and modules resolve into their own cached layers, keyed on the lockfile. A code-only push never touches them.
  3. Incremental rebuild as the default path. The cache is warm because the platform keeps it warm between deploys — the comparison Dockerfile in most "X% faster" claims starts cold or half-cached.

None of this is unavailable to a Dockerfile. All of it is optional in a Dockerfile, which is exactly the point. The native path's speed advantage is mostly the advantage of defaults: every tenant gets the fast layout whether or not they know what a layer is.

Where the 75% comes from: a worked example

Take a typical Node service — a few hundred megabytes of node_modules, a TypeScript build step, nothing exotic. Push a code-only change (a route handler edit; package-lock.json untouched) and follow the minutes.

StepCold Dockerfile (no cache)Native warm rebuildTuned Dockerfile (warm cache)
Base image pull~30s~0s (pre-warmed)~0–10s
Dependency install~3–4 min~0s (lockfile unchanged, layer reused)~0–15s (cached layer)
App build (tsc, bundling)~1 min~1 min~1 min
Image export + push~30–60s~20–30s (fewer changed layers)~20–30s
Total~6 minutes~90 seconds~90–120 seconds

Six minutes to ninety seconds is your 75%. Note what the number actually compares: the best case of one path (warm, code-only) against the worst case of the other (cold, reinstall everything). Change the inputs and the headline moves:

  • Dependency change in the same push? The dep layer rebuilds on both paths and the gap roughly halves — install time dominates everything else.
  • Cold cache on both sides? First build after a cache eviction is slow everywhere; native just evicts less often because the platform manages cache lifetime.
  • Tiny app, few dependencies? A 20-second install makes the whole debate noise — the 75% needs a dependency-heavy workload to show up.

This sensitivity analysis is the deliverable the headline owes you: native wins biggest on exactly the workload most production apps are — dependency-heavy services with frequent code-only pushes. And the last column is the one that matters for self-hosting: a tuned Dockerfile gets within spitting distance. The gap is closable. It is just never closed by accident.

That "by accident" is worth a number from the DORA side of the world. The 2025 State of DevOps data puts elite performers at on-demand deploys, multiple times per day, with sub-one-day lead times — only about 15–16% of respondents clear that bar. Every minute of build time is lead time, multiplied by every push, every PR preview, every agent-driven deploy. Build speed is not vanity; it is deployment frequency with the serial fraction still in it.

The three Dockerfile traps that hand Render its 75%

If tuned Dockerfiles can match native builds, why do so many real ones lose by 4x? Because the default Dockerfile authors write contains up to three cache-forfeiting mistakes, and each one converts a warm rebuild back into a cold one. The Hacker News buildpacks-vs-Dockerfiles thread has been litigating this for years, and the practitioner consensus is consistent: caching node_modules in Docker is fiddly, and the failure mode is minutes instead of seconds.

Trap 1: COPY . . before dependency install. The classic. Copying the whole repo first means any file change — a comment, a README edit — invalidates every layer below, including the dependency install. The fix is dependency-first ordering: copy the lockfile and manifest alone, install, then copy source.

dockerfile
# Slow: any source change reinstalls everything
COPY . .
RUN npm ci && npm run build
 
# Fast: code changes reuse the dependency layer
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

Trap 2: no .dockerignore. Without one, .git, local .next caches, editor swap files, and test fixtures all enter the build context — busting cache keys and slowing context upload on every push. Real-world Render deploy fixes routinely start here: shrinking the context is the cheapest minute you will ever save.

Trap 3: no persistent cache across builds. Even an ordered Dockerfile reinstalls from the network if each build runs on a fresh worker with no layer cache. The remedies are BuildKit cache mounts (RUN --mount=type=cache), registry-backed layer caching (--cache-from/--cache-to), or both. Without one of them, "layer caching" is a diagram, not a behavior. Complaints about auto-detecting builders "reinstalling dependencies and recompiling on every push" almost always trace to this missing piece rather than to detection itself.

A platform selling native environments fixes all three for you, silently, on every service. A platform selling Dockerfiles makes all three your homework. That asymmetry — not some exotic compiler trick — is the 75%.

The self-hosted parity playbook

So what does a self-hosted git-push pipeline have to do to post a number like Render's? Treat "as fast as Render" as a testable claim with a benchmark harness, not a vibe. Concretely:

  1. Benchmark both shapes on the same app. Take a representative service (dependency-heavy, TypeScript or Python), build it cold, then push five consecutive code-only changes and record the warm-rebuild distribution. That distribution — median and p95, not the best run — is your headline number. Publish the methodology alongside the result, including what changed per push, or the number is marketing.
  2. Make the warm path the default, not the opt-in. Registry-backed BuildKit layer caching on every build; dependency-first layer ordering enforced by templates or lint, not documentation; .dockerignore scaffolding in every starter. If tenants must discover caching themselves, most won't, and your fleet-wide average build time will look like Render's "before" column.
  3. Track cache-hit rate as a fleet metric. Hit rate on the dependency layer, segmented by runtime, tells you whether your defaults are working. A falling hit rate is an early warning that cache eviction policy or worker ephemerality is silently converting warm rebuilds back to cold ones.
  4. Close the loop with a buildpack path. The honest endgame for "as fast as Render" is offering the same app-aware default: detect the runtime, layer dependencies automatically, keep the cache warm between deploys. kpack/Paketo-style buildpack builders exist precisely for in-cluster use — and until that path ships, say so plainly.

That last point deserves plain speech about where bex stands today. bex builds from git with an in-cluster BuildKit job over your Dockerfilerepo/branch in bex.yml means Dockerfile builds, and the Render-facing runtime plus buildCommand/startCommand fields are translated to the internal build mechanism rather than executed natively.

The explicit kpack/Paketo buildpack path exists as a bex extension beyond Render's fields, but the in-cluster buildpack builder is not the default yet — which means bex tenants today live in the right-hand column of the table above, where speed is earned by hand. Items 1–3 of this playbook are therefore not advice for someone else; they are the checklist this platform grades itself against, in the open, until the app-aware default lands.

When the Dockerfile still wins

Fairness requires the counter-case, because native detection has real boundaries and the Dockerfile path exists for reasons beyond inertia:

  • Multi-runtime services. Need Node and Python in one image? Native runtimes are language-specific per service; a Dockerfile composes toolchains freely. This is Render's own documented reason to reach for Docker.
  • System packages and OS-level control. Native images, GPU-adjacent libraries, pinned base images for compliance — anything below the language layer belongs in a Dockerfile.
  • A tuned Dockerfile can beat a naive native build. There are real issues in the wild of teams migrating off native runtimes onto multi-stage Dockerfiles with proper layer caching for faster builds. Detection is a great default, not a ceiling; control beats defaults once you know what you're doing.

The right platform posture is both paths, with the fast default carrying the majority and the escape hatch carrying the edge cases. Render's lineup — native runtimes plus Dockerfile plus prebuilt image — is that shape. So is Railway's Railpack-default with Dockerfile override. The question for any self-hosted contender is never "which one," it is whether the default path is fast without the tenant studying layer semantics first.

The bar is public now

Render publishing a concrete build-speed number did the whole category a favor, including its open-source competitors. "75% faster than Dockerfiles" decomposes into warm-cache arithmetic any platform can reproduce, audit, and compete with — dependency layering by default, pre-warmed toolchains, incremental rebuilds, and cache-hit rates measured per fleet. The number also sets the terms of the contest: the winner is not whoever has the cleverest builder, but whoever makes the fast layout the one tenants get without asking.

For teams self-hosting on owned hardware, there is a kicker the per-push math hides. A hosted platform's build minutes are someone else's meter; your fleet's build minutes are capacity you already own. Every cached layer that turns a six-minute rebuild into ninety seconds doesn't just ship faster — it frees the build workers for the next tenant, the next PR preview, the next agent-driven deploy. Speed compounds into throughput when the hardware is yours.

So take the benchmark seriously, run it in public, and post your own number. That is what "as fast as Render" means — not a slogan, but a table with methodology attached. This post showed the shape of ours; the fleet has to earn the cells.

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.

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