Node.js
Railpack builds and deploys Node.js applications with support for various package managers and frameworks.
Detection
Section titled “Detection”Your project will be detected as a Node.js application if a package.json file
exists in the root directory.
Versions
Section titled “Versions”The Node.js version is determined in the following order of priority:
- Set via the
RAILPACK_NODE_VERSIONenvironment variable - Read from the
engines.nodefield inpackage.json - Read from the
.nvmrcfile - Read from the
.node-versionfile - Read from
mise.tomlor.tool-versionsfiles - Defaults to
22
This version resolution logic is applied consistently across all scenarios where Node is needed, including when Bun is the primary package manager but Node is required for native module compilation.
We officially support actively maintained Node.js LTS versions. Older versions of Node.js will likely still work but are not officially supported.
Node.js GPG verification is disabled by default; see the GPG verification recommendation to enable it in your project.
Runtime Variables
Section titled “Runtime Variables”These variables are available at runtime:
NODE_ENV=productionNPM_CONFIG_PRODUCTION=falseNPM_CONFIG_UPDATE_NOTIFIER=falseNPM_CONFIG_FUND=falseNPM_CONFIG_FETCH_RETRIES=5YARN_PRODUCTION=falseCI=trueConfiguration
Section titled “Configuration”Railpack builds your Node.js application based on your project structure. The build process:
- Installs dependencies using your preferred package manager (npm, yarn, pnpm, or bun)
- Executes the build script if defined in
package.json - Sets up the start command based on your project configuration
Railpack determines the start command in the following order:
- The
startscript inpackage.json - The
mainfield inpackage.json - An
index.jsorindex.tsfile in the root directory
Config Variables
Section titled “Config Variables”| Variable | Description | Example |
|---|---|---|
RAILPACK_NODE_VERSION |
Override the Node.js version | 22 |
RAILPACK_NO_SPA |
Disable SPA mode | true |
RAILPACK_SPA_OUTPUT_DIR |
Directory containing built static files | dist |
RAILPACK_PRUNE_DEPS |
Remove development dependencies | true |
RAILPACK_NODE_NPM_INSTALL |
Custom npm install command | npm ci |
RAILPACK_NODE_PRUNE_CMD |
Custom command to prune dependencies | npm prune --omit=dev --ignore-scripts |
RAILPACK_NODE_INSTALL_PATTERNS |
Custom patterns to install dependencies | prisma |
RAILPACK_ANGULAR_PROJECT |
Name of the Angular project to build | my-app |
RAILPACK_NX_APP |
Nx app to build and start (project name, package name, or path) | web or @org/web |
RAILPACK_NODE_PLAYWRIGHT_INSTALL |
Install Playwright browsers | 1 |
Playwright
Section titled “Playwright”When Playwright is a production dependency, Railpack suggests setting
RAILPACK_NODE_PLAYWRIGHT_INSTALL=1. Browser installation is opt-in because
it increases image size and is not required by every application that includes
Playwright.
When enabled, Railpack runs Playwright through the detected package manager to install its browser binaries and adds the required runtime system packages. Ensure Playwright is included in your production dependencies so its CLI is available during the build.
Package Managers
Section titled “Package Managers”Railpack detects your package manager in the following order:
- packageManager field: Reads the
packageManagerfield frompackage.json(uses Corepack to install the specified version) - Lock files: Falls back to detecting based on lock files:
pnpm-lock.yamlfor pnpmbun.lockborbun.lockfor Bun.yarnrc.ymlor.yarnrc.yamlfor Yarn Berry (2+)yarn.lockfor Yarn 1
- engines field: As a fallback, checks the
enginesfield inpackage.jsonfor package manager versions:engines.pnpmfor pnpm versionengines.bunfor Bun versionengines.yarnfor Yarn version- Defaults to npm if no package manager is detected
When the packageManager field is present, Railpack will use Corepack to
install the specified package manager version. When a package manager is
detected via the engines field, the specified version constraint will be
used.
Railpack supports building native modules and automatically configures node-gyp.
Monorepo Support
Section titled “Monorepo Support”Railpack automatically supports monorepo (workspaces) configurations with all major package managers. No special configuration is required.
Supported Approaches:
- npm, bun, yarn: Uses the
workspacesfield inpackage.json - pnpm: Uses
pnpm-workspace.yamlconfiguration - Nx: Detects
nx.jsonand builds Next.js apps even when targets are inferred (no rootbuild/startscripts)
See the examples
folder in the
repository for workspace examples across different package managers (e.g.,
node-pnpm-workspaces, node-npm-workspaces, node-yarn-workspaces,
node-bun-workspaces, node-nx-next).
When building a monorepo, Railpack will:
- Detect workspace configurations automatically
- Install all workspace dependencies correctly
- Respect workspace dependency links between packages
- Cache workspace node_modules appropriately
If your monorepo requires building a specific workspace package, ensure
your build and start scripts are defined in the root package.json, set
RAILPACK_NX_APP for multi-app Nx workspaces, or use a
config file to specify custom commands.
Stock Nx workspaces often rely on inferred
tasks instead of package.json
scripts. When Railpack detects Nx and the root has no build/start scripts:
- Build:
nx build <project>(uses the package name, e.g.@org/web) - Start (Next.js):
cd apps/web && next startso runtime does not depend on thenxCLI
With a single Next.js app, selection is automatic. With multiple apps, set
RAILPACK_NX_APP to the project name, package name, or package path (e.g.
web, @org/web, or apps/web).
Install
Section titled “Install”Railpack will only include the necessary files to install dependencies in order
to improve cache hit rates. This includes the package.json and relevant lock
files, but there are also a few additional framework specific files that are
included if they exist in your app. This behavior is disabled if a preinstall
or postinstall script is detected in the package.json file.
You can include additional files or directories to include by setting the
RAILPACK_NODE_INSTALL_PATTERNS environment variable. This should be a space
separated list of patterns to include. Patterns will automatically be prefixed
with **/ to match nested files and directories.
Static Sites
Section titled “Static Sites”Railpack can serve a statically built Node project with zero config. You can disable this behavior by either:
- Setting the
RAILPACK_NO_SPA=1environment variable - Setting a custom start command
These frameworks are supported:
- Vite: Detected if
vite.config.jsorvite.config.tsexists, or if the build script containsvite build - Astro: Detected if
astro.config.jsexists and the output is not type"server" - Next.js: Detected if
nextis in dependencies andnext.config.js,next.config.mjs, ornext.config.tssetsoutput: 'export'(oroutput: "export"). The defaultnext startstart script does not disable SPA mode. - CRA: Detected if
react-scriptsis in dependencies and build script containsreact-scripts build - Angular: Detected if
angular.jsonexists - React Router: Detected if
react-router.config.jsorreact-router.config.tsexists, or if the build script containsreact-router build. To enable SPA mode, setssr: falsein your React Router config. - Expo Web: Detected if
expoandreact-native-webare in dependencies andapp.jsonsetsexpo.web.outputtostaticorsingle
For all frameworks, Railpack will try to detect the output directory and will
default to dist (or build/client/ for React Router, or out for Next.js
static exports). Next.js reads distDir from your config when set. Set the
RAILPACK_SPA_OUTPUT_DIR environment variable to specify a custom output
directory. Railpack uses your app’s build script to produce the static
output.
Note that if a SPA framework is not detected automatically, can you force SPA mode
by specifying a RAILPACK_SPA_OUTPUT_DIR environment variable. This will enable SPA
mode and serve the specified directory as a static site. Some of the SPA detection
uses regexes on framework configuration files, which will fail if the default framework
configuration files are customized.
Static sites are served using the Caddy web server and a default Caddyfile. You can overwrite this file with your own Caddyfile at the root of your project.
Node SPA deploys honor the index_fallback key in a Staticfile at the
project root when set. SPA routing behavior is unchanged unless you set
index_fallback: false (for example on multi-page Astro static sites) so
unknown paths return 404 and serve 404.html when present.
Framework Support
Section titled “Framework Support”Railpack detects and configures caches and commands for popular frameworks. Including:
- Next.js: Caches
.next/cachefor each Next.js app in the workspace - Remix: Caches
.cache - Vite: Caches
node_modules/.vite - Tanstack Start: Caches
node_modules/.vite - Astro: Caches
node_modules/.astro - React Router: Caches
.react-router - Nuxt:
- Start command defaults to
node .output/server/index.mjs - Caches
node_modules/.cache
- Start command defaults to
As well as a default cache for node modules:
- Node modules: Caches
node_modules/.cache(with the cache keynode-modules)
Cache & Removing node_modules
Section titled “Cache & Removing node_modules”When you add custom build commands that remove node_modules (such as
npm ci), Railpack automatically detects this and
removes the node_modules/.cache directory from the cache configuration for
those steps. This prevents EBUSY: resource busy or locked errors
that would otherwise occur when trying to remove a cached directory.
This automatic handling applies to build steps that contain commands like:
npm cirm -rf node_modulesrimraf node_modules
The install step always retains its cache configuration regardless of the commands used.
System Dependencies
Section titled “System Dependencies”Railpack automatically installs system dependencies for Puppeteer:
- Puppeteer: When detected in workspace dependencies, Railpack installs
all necessary system packages for running headless Chrome, including
xvfb,chromiumdependencies, and font libraries. Note that Puppeteer’s bundled Chromium does not support ARM64; if you need to run on ARM hardware, consider switching to Playwright or implementing a custom workaround (e.g. installing a system Chromium and pointingexecutablePathat it).