Docs menu

Bun on Primcloud

How Primcloud detects a Bun app, installs Bun and your dependencies, runs build script hooks, prunes unlisted packages, and picks the default web process.

On this page

Primcloud detects a Bun app from the files in your repo, installs the Bun version you ask for, installs dependencies from your lockfile, runs your build scripts, prunes packages your lockfile doesn't list, and, if you don't ship a Procfile, picks a default web process. This page covers each of those phases and the config knobs that shape them.

Detection#

Primcloud runs the Bun toolchain when the root of your repo contains a package.json and at least one of:

  • A Bun lockfile: bun.lock or bun.lockb
  • bunfig.toml
  • .bun-version
  • A packageManager field in package.json that names Bun ("packageManager": "[email protected]")

A package.json with none of these builds as a Node.js app. Bun writes no lockfile for an app with no dependencies, so that kind of app needs one of the other three markers to be recognized.

One language per app. Primcloud checks languages in a fixed order and builds with the first that matches. Bun is checked ahead of Node.js and after everything else. A repo that also carries a Gemfile.lock, a composer.json, or a Cargo.toml builds as a Ruby, PHP, or Rust app, and Bun builds its frontend assets instead of running the app. See Package managers → Bun as the asset builder. If you mean to ship a Bun app, don't leave another language's manifest at the root.

Install phase#

The install phase runs in two parts: install Bun, then install your dependencies.

Bun. The version is resolved from .bun-version, then the packageManager field, then engines.bun. If none of them names a version, Primcloud's default is installed. See Runtime versions. The download is verified against its published checksum before it's unpacked.

A Bun app gets Bun only. Node.js isn't installed alongside it.

Dependencies. What runs depends on whether a lockfile is committed:

Repo has Command Result
bun.lock or bun.lockb bun install --frozen-lockfile Installs exactly what the lockfile records. Fails if the lockfile is out of date with package.json.
No lockfile bun install Succeeds only for an app with no dependencies. If the install has anything to lock, the build fails with a "Missing Bun lockfile" error.

An app with dependencies must commit its lockfile.

Compile phase#

Bun apps don't have a separate compile step. The build phase runs your package.json scripts in a fixed order, each with bun run:

  1. heroku-prebuild: always runs first if defined.
  2. heroku-build if defined, otherwise build. Only one of the two runs (heroku-build wins if both are present).
  3. heroku-postbuild: always runs last if defined.

Missing scripts are skipped silently. This is the hook for bundling (bun build), framework builds, asset generation, and anything else that needs to run once per deploy. Dev dependencies are installed while these scripts run.

Cache behavior#

Primcloud persists a per-app build cache between deploys:

  • The Bun runtime: cached by exact version. A repeat deploy on the same version reuses it without re-downloading; the build log shows Reusing Bun or Installed Bun.
  • Bun's install cache: downloaded packages, reused across builds so bun install doesn't fetch them again.

Pruning. After the build scripts finish, the build runs bun prune, which removes the packages in node_modules that your lockfile doesn't list (something a build script installed on the side, say). devDependencies stay installed, so they ship in the final app.

Skip this step by setting the following in project.toml:

project.toml
[_]
schema-version = "0.2"

[com.primcloud.buildpacks.bun]
actions.prune_dev_dependencies = false

Do this only when your app needs a package at runtime that a build script installs outside the lockfile. The correct fix is almost always to add it to package.json and commit the lockfile.

Pruning requires Bun 1.4.0 or newer. On an older pinned Bun the step is skipped. An app with no lockfile has nothing installed, so there's nothing to prune.

Release phase conventions#

Procfile takes precedence. If your repo has a Procfile at the root, Primcloud uses it verbatim to define process types, the same as every other language on Primcloud. See Pod Formations for the file format.

Auto-detected web process kicks in when there's no Procfile, in this order:

  1. If package.json declares a start script: web: bun run start.
  2. Otherwise, Bun run against the first of these that exists at the root:
    • server.js
    • index.js
    • server.ts
    • index.ts
  3. Otherwise, no web process is registered. The build produces nothing to run, so the deploy fails with a process-types error. Add a Procfile, a start script, or one of the root entry files.

A Procfile with no web line. The start script isn't used. If one of the root entry files above exists, Bun run against it is the web process alongside the processes your Procfile declares.

The check runs after your build scripts, so an entry file they generate counts.

Port binding. Primcloud sets PORT on the web formation (the constant 5000), and your app must bind to it. With Bun.serve, pass port: process.env.PORT. Any other port is unreachable from outside the pod. Non-web formations get no PORT; a worker that doesn't serve HTTP doesn't need one.

Runtime environment. These variables are set on your runtime process:

Variable Value Purpose
PATH With Bun and your app's node_modules/.bin prepended So bun, bunx, and the binaries your dependencies install resolve.
NODE_ENV production A default. Set NODE_ENV in the Secrets panel to override it.
WEB_CONCURRENCY Derived from the pod's memory limit Hint for sizing a pool of worker processes.
WEB_MEMORY Derived from the pod's memory limit Hint for sizing a pool of worker processes.
PORT Assigned by Primcloud Set on web process types only.

WEB_CONCURRENCY and WEB_MEMORY are only hints. Nothing starts extra processes on your behalf. Read them from your app if you want to size a worker pool.

Anything you set in the Secrets panel is layered on top of these at runtime.