Comparative Analysis of Default Buildpack Behavior Across Modern Web Frameworks

Abstract

Automatic source-to-container systems reduce the configuration required to deploy web applications, but their default behavior differs across programming languages, framework conventions, package managers, runtime metadata, and repository layouts. This paper presents an empirical comparison of five buildpack-style systems: Paketo Cloud Native Buildpacks, Heroku Cloud Native Buildpacks, Nixpacks, Railpack, and Zbpack. The benchmark covers 13 web framework families, 39 public repositories, 195 buildpack/repository cells, and 390 build runs. Each cell was evaluated with one cold build and one warm build. The primary metric is reliable build success, defined as both runs producing a container image. Runtime startup was intentionally excluded to isolate image-build behavior.

Railpack achieved the strongest result, with 32 reliable successes out of 39 cells (82.1%). Heroku Cloud Native Buildpacks followed with 25/39 (64.1%), Zbpack reached 24/39 (61.5%), Nixpacks reached 22/39 (56.4%), and Paketo Cloud Native Buildpacks reached 19/39 (48.7%). The results show that buildpack choice is not interchangeable: each system has a distinct profile across framework families and repository features. The most important failure causes were runtime version selection, unavailable runtime patch versions, package manager detection, missing start command inference, native dependency gaps, Dockerfile context assumptions, and production build scripts. These findings can inform repository-aware buildpack selection, where deployment platforms select a default builder based on framework family and repository contents rather than applying one universal build strategy.

Keywords

buildpacks; containerization; Cloud Native Buildpacks; Nixpacks; Railpack; Zbpack; deployment automation; repository analysis; web frameworks

1. Introduction

Containerized deployment is now a standard target for web applications, but producing a correct image from source code is still complex. A deployment system must infer the programming language, framework, runtime version, package manager, dependency installation steps, build command, filesystem layout, and sometimes the start command. Buildpack-style systems address this problem by inspecting repository contents and producing an image without requiring a hand-written Dockerfile.

This study compares default build behavior across five systems:

•          Paketo Cloud Native Buildpacks (CNB)

•          Heroku Cloud Native Buildpacks

•          Nixpacks

•          Railpack

•          Zbpack

The central research question is:

How reliably do different buildpack-style systems produce container images for different web framework families under default, no-intervention conditions?

The practical motivation is repository-aware buildpack selection. If different buildpacks have slight but repeatable advantages across framework families and repository contents, a deployment platform can choose a better default builder before attempting deployment. For example, the presence of a Ruby patch pin, pnpm-lock.yaml, packageManager, composer.lock, .ruby-version, go.mod, a framework-specific subdirectory, or a Dockerfile can change which buildpack is most likely to build successfully.

This paper measures image build success, not runtime correctness. A produced image can still require environment variables, external services, health checks, or a start command. However, successful image creation is a necessary deployment step and a useful basis for comparing buildpack behavior.

Cloud Native Buildpacks define a lifecycle for transforming application source into container images. Detection is a core phase: buildpacks inspect application files and decide whether they apply to the project [1]. The lifecycle also includes build, export, and related phases that together produce the final image [2]. Paketo and Heroku both provide CNB builders, but they differ in buildpack sets, runtime support, and framework policies.

Nixpacks uses source inspection to generate a build plan and container image using Nix-based packages and generated build phases [4]. Railpack detects project metadata and builds application images through BuildKit [5, 6]. Zbpack is used in Zeabur’s deployment workflow and can infer build behavior or use repository Dockerfile paths depending on project structure [7].

This work is also related to deployment automation and reproducible build research. Deployment automation technologies attempt to make the transition from source code to running software more declarative and repeatable [9]. Reproducible build research shows that build outputs can be affected by toolchain versions, dependency repositories, and environment state [10]. Containerization improves portability, but containers alone do not guarantee reproducible builds because base images, package repositories, local daemon behavior, and build context can still vary [11].

3. Materials and Methods

3.1 Dataset

The benchmark covers 13 framework families with three public repositories per family where possible. The selected repositories were chosen to represent variation in size and structure: smaller templates or examples, medium applications, and larger real-world applications.

Item

Count

Framework families

13

Repositories

39

Build systems

5

Buildpack/repository cells

195

Raw build runs

390

The framework families are Vite, Vue, Next.js, Nuxt, SvelteKit, Jekyll, Express, Django, FastAPI, Gin, Laravel, Symfony, and Rails. Earlier exploratory runs were excluded where local Docker failures or unsuitable repository choices would have distorted the benchmark. The final Rails set uses deployable Rails repositories because this paper compares buildpack behavior across framework families rather than stressing a single framework with unusually difficult applications.

3.2 Build Systems

All systems were evaluated in default mode. No project-specific tuning files, runtime overrides, or manual source edits were added to make a build pass. Paketo CNB and Heroku CNB were executed through the pack lifecycle with their respective builders. Nixpacks, Railpack, and Zbpack were executed through their default command-line interfaces. Railpack and Zbpack used BuildKit as part of their build process.

3.3 Metrics

Each buildpack/repository cell was run twice:

•          Run 1: cold build

•          Run 2: warm build

The primary metric is reliable success:

reliable success = cold build succeeded and warm build succeeded

Supporting metrics are any success, partial success, median cold build time, median warm build time, and median image size. Failed rows are excluded from build-time and image-size medians. Runtime startup probing was disabled, so a success means that an image was produced, not that the application started correctly.

3.4 Execution Environment

The benchmark ran locally under Docker Desktop with WSL2. Environment metadata was captured during report preparation on 2026-05-15.

Component

Observed value

Windows version

10.0.26200.8457

WSL version

2.6.3.0

Linux distribution

Ubuntu 24.04.4 LTS

Linux kernel

6.6.87.2-microsoft-standard-WSL2

Docker Desktop

4.70.0 (224270)

CPUs available to Docker

4

Memory available to Docker

approximately 7.8 GiB

Observed build tool versions were pack 0.40.2, Nixpacks 1.41.0, Railpack 0.23.0, and BuildKit 0.28.1. The local Zbpack binary did not expose a version flag in this setup.

4. Results

4.1 Overall Buildpack Reliability

Table 1. Reliable success and performance summary by build system.

Buildpack

Reliable successes

Reliable rate

Any-success rate

Median cold build

Median warm build

Median image size

Paketo CNB

19/39

48.7%

48.7%

142.7s

17.6s

729.8 MB

Heroku CNB

25/39

64.1%

64.1%

73.8s

29.7s

732.7 MB

Nixpacks

22/39

56.4%

56.4%

147.1s

3.4s

968.1 MB

Railpack

32/39

82.1%

82.1%

104.8s

3.1s

430.1 MB

Zbpack

24/39

61.5%

61.5%

170.0s

8.4s

1167.1 MB

Railpack had the highest reliable success rate and the smallest median successful image size. Heroku CNB was the second-most reliable system in this final dataset, while Zbpack was close behind. Nixpacks had very fast warm builds where it succeeded, but more failures on metadata-sensitive cases. Paketo CNB was limited by strict runtime availability and detection behavior in several ecosystems.

Reliable success rate by buildpack

Figure 1. Reliable success rate by build system.

4.2 Framework-Level Results

Table 2. Reliable success by framework family.

Framework

Reliable successes

Reliable rate

Best reliable buildpack(s)

Gin

14/15

93.3%

Nixpacks, Paketo CNB, Railpack, Zbpack

Express

13/15

86.7%

Nixpacks, Heroku CNB, Paketo CNB, Railpack

SvelteKit

12/15

80.0%

Heroku CNB, Paketo CNB, Railpack

Nuxt

11/15

73.3%

Heroku CNB, Railpack

Vue

11/15

73.3%

Railpack

Next.js

10/15

66.7%

Nixpacks, Railpack

Vite

10/15

66.7%

Nixpacks, Railpack

Jekyll

9/15

60.0%

Heroku CNB, Railpack

FastAPI

7/15

46.7%

Paketo CNB, Railpack, Zbpack

Laravel

7/15

46.7%

Heroku CNB, Zbpack

Rails

7/15

46.7%

Heroku CNB, Railpack, Zbpack

Symfony

6/15

40.0%

Nixpacks, Railpack, Zbpack

Django

5/15

33.3%

Zbpack

Go and conventional Node.js repositories were the easiest default-build targets. PHP, Python, and Ruby applications were more sensitive to runtime versions, native dependencies, lockfiles, production build scripts, and service assumptions.

Reliable success matrix by framework and buildpack

Figure 2. Reliable success matrix by framework family and build system.

4.3 Build Time and Image Size

Warm build times were usually much shorter than cold build times because successful first runs populated reusable layers, package caches, or previous images. Railpack and Nixpacks had the fastest median warm builds among successful cells. Railpack also produced the smallest median successful image size.

Median cold and warm build time

Figure 3. Median cold and warm build times for successful builds.

Median image size

Figure 4. Median image size for successful builds.

5. Buildpack Profiles by Framework Signal

The aggregate results show the overall ranking, but the more useful finding is that each buildpack has different advantages depending on repository contents.

Table 3. Framework-level buildpack selection signals from the benchmark.

Repository signal

Strongest observed default choice

Main evidence

Go/Gin applications with go.mod

Railpack, Nixpacks, Paketo CNB, Zbpack

Four systems built all three Gin repositories reliably.

Conventional Express applications

Railpack, Nixpacks, Heroku CNB, Paketo CNB

Four systems reached 3/3 reliable success; Zbpack was weaker where Dockerfile path assumptions appeared.

SvelteKit repositories

Railpack, Heroku CNB, Paketo CNB

These three built all SvelteKit repositories; Nixpacks was affected by start command inference.

Nuxt repositories

Railpack or Heroku CNB

Both reached 3/3 reliable success; other systems had package-manager or start-command sensitivity.

Next.js and Vite repositories

Railpack or Nixpacks

Both were strongest for these frontend families.

Vue repositories

Railpack

Railpack reached 3/3; other systems were mostly partial across the Vue set.

Jekyll/Ruby static sites

Heroku CNB or Railpack

Both reached 3/3; Nixpacks depended more on explicit Ruby metadata and Paketo was constrained by Ruby/Bundler availability.

FastAPI repositories

Paketo CNB, Railpack, or Zbpack

These systems each built 2/3; repository layout and Dockerfile behavior mattered.

Laravel repositories

Heroku CNB or Zbpack

Both built 2/3; Composer lockfiles, PHP extensions, and Node tooling were decisive.

Symfony repositories

Nixpacks, Railpack, or Zbpack

These systems each built 2/3; production scripts and PHP extension assumptions caused failures.

Deployable Rails repositories

Heroku CNB, Railpack, or Zbpack

Each built 2/3; exact Ruby patch availability blocked Paketo.

Django repositories

Zbpack

Zbpack was the only buildpack to build 2/3; production settings, mixed metadata, and native dependencies affected others.

These results suggest that buildpack selection should not be a fixed global default. A repository-aware selector should inspect both framework family and repository contents.

For example, a Nuxt repository with clear Node metadata is a strong Railpack or Heroku candidate, while a Go/Gin repository can be handled reliably by several buildpacks. A Ruby repository with an exact patch-level .ruby-version should be checked against the builder’s available Ruby versions before selecting Paketo CNB. A PHP repository should be checked for Composer lockfiles and required platform extensions before choosing a PHP-oriented build path.

6. Framework-by-Framework Reliability Diagnostics

The aggregate success rates hide the most important practical detail: each framework failed for different reasons. Table 4 summarizes the dominant causes behind the reliable-success results. These explanations come from the archived build logs and run summaries, and they show which repository signals should be inspected before selecting a buildpack.

Table 4. Framework-level reliability diagnostics by build system.

Framework

Most reliable buildpack(s)

Why builds succeeded or failed

Vite

Nixpacks, Railpack

Railpack and Nixpacks handled npm, pnpm, and Yarn/Corepack cases. Paketo failed a Yarn Berry repo by using Yarn Classic. Heroku failed a pnpm-lock-only repo by selecting npm. Zbpack failed all three through generated npm/pnpm/Yarn handling issues.

Vue

Railpack

Railpack handled the full Vue sample. Other buildpacks were mostly partial: Paketo misread a pnpm-enforced repo, Nixpacks selected an older Node version for a modern Vite stack, Heroku had one weak or failed modern Node path, and Zbpack hit Node/Corepack incompatibility on the large admin repo.

Next.js

Nixpacks, Railpack

Nixpacks and Railpack handled pnpm, Yarn, and modern Next.js projects. Heroku worked when package-manager metadata was explicit, but failed a pnpm-lock-only repo by selecting npm. Paketo failed all three, mainly because npm was selected where pnpm or different build behavior was required. Zbpack built two but failed one pnpm case on ignored native build scripts and produced large images.

Nuxt

Heroku CNB, Railpack

Heroku and Railpack handled npm, pnpm, and Yarn Nuxt repos. Nixpacks failed repos without an explicit start command, even though image build was the measured target. Paketo failed the pnpm Nuxt repo by selecting npm. Zbpack built two but failed a generated npm update step before app install.

SvelteKit

Paketo CNB, Heroku CNB, Railpack

These three built all SvelteKit repos and did not require a start script to produce an image. Nixpacks failed the repos without start commands. Zbpack built two but failed one generated npm path and produced heavier images.

Jekyll

Heroku CNB, Railpack

Heroku and Railpack were reliable across static and larger Jekyll sites. Paketo failed on detection, Bundler version availability, and exact Ruby patch availability. Nixpacks required .ruby-version. Zbpack failed an exact Ruby version case by selecting Ruby 3.3 for a repo requiring 3.4.4.

Express

Paketo CNB, Heroku CNB, Nixpacks, Railpack

Express had clear Node server signals and was broadly portable. Zbpack was the exception because it followed repository Dockerfile assumptions that expected a prebuilt output directory or Yarn in the base image.

Django

Zbpack

Zbpack was strongest because Dockerfile-aware behavior captured the setup for two complex Django apps. Paketo, Nixpacks, and Railpack built only the simple app and failed on Python version, requirement layout, or mixed metadata issues. Heroku failed all three because of Python metadata strictness and production settings loaded during build.

FastAPI

Paketo CNB, Railpack, Zbpack

Paketo and Railpack handled modern FastAPI repos but failed an old pinned native dependency. Zbpack avoided that failure by following a Dockerfile with an older Python, but failed a subpath Dockerfile whose context expected the repository root. Heroku and Nixpacks were more sensitive to missing metadata/start commands.

Gin

Paketo CNB, Nixpacks, Railpack, Zbpack

Go/Gin was the most portable framework. Four systems built all repos. Heroku failed only the large multi-package app after selecting too broad a Go build target that included generated/plugin packages.

Laravel

Heroku CNB, Zbpack

Heroku built production-like Laravel apps with lockfiles but rejected the skeleton app without composer.lock. Zbpack built two but selected too-new PHP for Akaunting. Paketo missed PHP extension/runtime needs, Nixpacks hit Node and Nix package-collision issues, and Railpack hit PHP toolchain/version availability problems.

Symfony

Nixpacks, Railpack, Zbpack

Nixpacks, Railpack, and Zbpack each built two repos. Heroku failed on missing composer.lock, Composer auto-scripts, and PHP memory limits. Paketo failed on detection, PHP version availability, and missing PHP extensions. The main signals were Composer lockfiles, PHP versions, extensions, memory, and frontend build tooling.

Rails

Heroku CNB, Railpack, Zbpack

Heroku, Railpack, and Zbpack each built two deployable Rails repos. Paketo failed all three because exact Ruby patch versions were unavailable. Nixpacks built one but missed Ruby version propagation and native package needs. The remaining failures involved REDIS_URL, exact Node engine constraints, and PostgreSQL development headers.

This table is intentionally repository-aware rather than framework-only. For example, “Next.js” alone was not enough to choose a buildpack: the difference between pnpm-lock.yaml alone and an explicit packageManager field changed Heroku’s outcome. Similarly, “Rails” alone was not enough: exact Ruby patch availability, Node engine constraints, Redis-dependent asset compilation, and PostgreSQL headers changed which buildpack succeeded. The relevant selection unit is therefore a framework plus its repository metadata.

7. Failure Analysis

The most common failure categories in the benchmark were metadata and environment inference failures rather than broad framework incompatibilities.

Table 5. Main failure categories observed in the benchmark.

Category

Typical effect

Selection implication

Runtime version selection

Builder chose a runtime incompatible with dependency constraints.

Prefer buildpacks that honor exact runtime metadata for strict repositories.

Runtime version availability

Builder could not provide the exact patch version required.

Check builder runtime catalog before selecting the buildpack.

Package manager detection

npm, pnpm, Yarn, or Corepack behavior was inferred incorrectly.

Inspect lockfiles and packageManager before selecting a Node build path.

Missing start command

Builder refused image creation without a start command.

Avoid buildpacks that require start inference when the benchmark target is image build only, or infer the command separately.

Native/system dependency gap

Image lacked headers or libraries required by gems, wheels, or extensions.

Prefer buildpacks that infer native packages from dependency metadata.

Production configuration or service assumption

Build scripts required environment variables or services.

Detect production build scripts that load application configuration.

Dockerfile/context assumption

Generated or repository Dockerfile expected a different context.

Check whether Dockerfiles are source-buildable from the selected subpath.

Examples from the benchmark illustrate these patterns. Paketo CNB failed several Ruby applications because the builder did not provide the exact Ruby patch version required by repository metadata. Nixpacks failed several Nuxt and SvelteKit cases because it required or inferred a start command even though startup was outside this benchmark. Railpack failed one Rails case by selecting a Node patch version that was close but incompatible with the repository’s strict engine constraint. Zbpack failed a Rails case because the generated image lacked PostgreSQL development tooling required by the pg gem. Heroku CNB reached a Rails asset-precompile stage but failed when the application expected a production environment variable.

8. Discussion

8.1 Comparative Buildpack Behavior

Railpack was the strongest general default builder in this dataset. It combined broad framework detection, high reliable success, fast warm builds, and small median image size. Its advantage was clearest in frontend frameworks, Go/Gin, Jekyll, and deployable Rails.

Heroku CNB was strong on conventional Node, Nuxt, SvelteKit, Jekyll, Laravel, and deployable Rails repositories. Its failures tended to appear when production framework scripts required environment variables or when dependency metadata interacted poorly with selected runtime versions.

Nixpacks was strong on Express, Gin, Next.js, and Vite and had fast warm builds. Its weaknesses were missing start command requirements, generated build-context assumptions, and some native dependency cases.

Paketo CNB performed well on several clean paths, including Express, SvelteKit, Gin, FastAPI, Vite, and Vue. Its main weakness was strict detection and exact runtime availability, especially in Ruby and PHP cases.

Zbpack performed well on Gin, deployable Rails, Laravel, Symfony, and several frontend cases. Its risk was that generated or Dockerfile-aware behavior sometimes missed native dependencies or inherited repository assumptions that were not suitable for generic source deployment.

8.2 Repository-Aware Selection Strategy

The results support a two-stage selection strategy. First, identify the framework family from repository contents. Second, inspect high-impact metadata that changes buildpack suitability:

 

•          language runtime files such as .ruby-version, .python-version, go.mod, and PHP Composer constraints;

•          JavaScript package-manager files such as package-lock.json, pnpm-lock.yaml, yarn.lock, and packageManager;

•          framework-specific directories such as SvelteKit, Nuxt, Rails, Django, Laravel, or Symfony project structure;

•          native dependency signals such as pg, sqlite3, image libraries, or Python/Ruby native extensions;

•          Dockerfiles and whether they are source-buildable from the selected repository subpath;

•          production build scripts that load framework settings or require environment variables.

A selector using these signals would not merely ask “what framework is this?” It would ask “which buildpack is most compatible with this framework and this repository’s metadata?” This is the main practical finding of the study.

8.3 Interpreting Build Success

Partial success should be interpreted cautiously. If a cold build fails and a warm build succeeds, the buildpack has shown some ability to build the repository, but not reliable reproducibility from a clean state. The strict reliable success metric is therefore the appropriate primary metric for default deployment.

The framework-level results should also not be read as universal framework rankings. They describe how selected repositories behaved under default build systems. Repository selection and metadata quality materially affect the result.

9. Threats to Validity

9.1 Construct Validity

This study measures image build success, not runtime success. A successful image build does not prove that the application starts, serves traffic, passes health checks, or is production ready. The study also does not measure security, vulnerability posture, software bill of materials quality, memory use, or runtime performance.

9.2 Internal Validity

The benchmark was executed in a local container build environment. Earlier exploratory runs exposed disk pressure, daemon instability, and local export failures. Corrected results were selected where such issues were identified. Nevertheless, local infrastructure can affect cold build time, image export behavior, and cache behavior.

Warm builds are affected by Docker cache, BuildKit cache, CNB restore behavior, and previous images. The harness reused the same image name across cold and warm runs, but warm-build timing should still be interpreted as tool-plus-environment behavior rather than pure algorithmic performance.

9.3 External Validity

Each framework family is represented by three repositories. This captures variation across repository size and shape but cannot represent the full diversity of each ecosystem. Results are directional evidence about buildpack behavior across framework families, not universal statements about all applications in a framework.

9.4 Conclusion Validity

Build-time and image-size medians are computed only from successful builds. This prevents failed rows from distorting performance data but means that performance comparisons are conditional on success. A buildpack with fewer successful cells may appear fast or small because it succeeded on an easier subset.

Failure categories were manually classified from archived logs and run summaries. The categories explain dominant root causes but should not be treated as a complete formal taxonomy of all possible build failures.

10. Artifact Availability

The benchmark results, aggregate tables, figures, and supporting logs are retained by the authors. A cleaned artifact package can be prepared for review or publication. It should include the repository list, raw result tables, aggregate summaries, generated figures, and enough environment metadata to reproduce the analysis without exposing unrelated working-directory details.

11. Conclusion

This study shows that buildpack choice significantly affects default image-build reliability across web frameworks. In the final benchmark dataset, Railpack achieved the highest reliable success rate at 82.1%, followed by Heroku CNB at 64.1%, Zbpack at 61.5%, Nixpacks at 56.4%, and Paketo CNB at 48.7%.

The main finding is that buildpack advantages are framework- and repository-dependent. Railpack was the strongest general default builder, but other systems had clear strengths in particular contexts. Heroku CNB performed well for conventional Node, Jekyll, Laravel, and deployable Rails repositories. Nixpacks was strong for Express, Gin, Next.js, and Vite. Zbpack was competitive for Gin, PHP frameworks, Django, and deployable Rails, but could miss native dependency requirements. Paketo CNB was effective on several clean paths but more sensitive to exact runtime availability and detection constraints.

Most failed builds were systematic inference failures rather than broad framework incompatibilities. Runtime metadata, package manager files, native dependencies, Dockerfile context, and production build scripts were decisive. Therefore, an effective deployment platform should select buildpacks based on repository contents, not only on framework labels or a single global default.

References

[1] Cloud Native Buildpacks. “Detect.” Available: https://buildpacks.io/docs/for-platform-operators/concepts/lifecycle/detect/

[2] Cloud Native Buildpacks. “What is the lifecycle?” Available: https://buildpacks.io/docs/for-buildpack-authors/concepts/lifecycle-phases/

[3] Heroku Dev Center. “Managing Buildpacks.” Available: https://devcenter.heroku.com/articles/managing-buildpacks

[4] Nixpacks. “Getting Started.” Available: https://nixpacks.com/docs/getting-started

[5] Railpack. “Getting Started.” Available: https://railpack.com/getting-started

[6] Railway Docs. “Railpack.” Available: https://docs.railway.com/builds/railpack

[7] Zeabur Docs. “How Deploys Work: Builds.” Available: https://zeabur.com/docs/en-US/deploy/how-deploys-work

[8] ACM. “Artifact Review and Badging - Current.” Available: https://www.acm.org/publications/policies/artifact-review-and-badging-current

[9] C. Quinton, R. Rabiser, M. Vierhauser, P. Grünbacher, and L. Baresi. “The Essential Deployment Metamodel: A Systematic Review of Deployment Automation Technologies.” Available: https://arxiv.org/abs/1905.07314

[10] C. Lamb and S. Zacchiroli. “Reproducible Builds: Increasing the Integrity of Software Supply Chains.” Available: https://arxiv.org/abs/2104.06020

[11] D. Chondros, G. Psomakis, M. Anagnostou, and D. Spinellis. “Docker Does Not Guarantee Reproducibility.” Available: https://arxiv.org/abs/2601.12811