Recommendations
Set the Timezone Explicitly
Section titled “Set the Timezone Explicitly”Railpack does not set a timezone. Set TZ explicitly for your application.
TZ=UTC is a good default.
{ "deploy": { "variables": { "TZ": "UTC" } }}Install Python when using pipx
Section titled “Install Python when using pipx”If you install pipx without also installing Python via mise, pipx will use
the system Python and pip from the Debian runtime image. That Python is old and
can cause compatibility issues with mise and other tooling.
Install Python via mise whenever you use pipx:
{ "packages": { "python": "latest", "pipx:httpie": "3.2.4" }}Or, even better, configure this in a mise.toml instead of a railpack.json
Set the Locale Explicitly
Section titled “Set the Locale Explicitly”Set LANG=en_US.UTF-8 so applications and shell tools handle Unicode
correctly. Railpack does not bundle additional locales — only en_US.UTF-8
is available in the runtime image. If your application requires a different
locale, install the corresponding locale packages via
deploy.aptPackages.
{ "deploy": { "variables": { "LANG": "en_US.UTF-8" } }}Use Mise for Language and Package Manager Versioning
Section titled “Use Mise for Language and Package Manager Versioning”Although mise supports extracting versions from language-specific configuration
files (.node-version, .python-version, runtime.txt, etc.), mise offers a
much more unified and streamlined way of managing these versions. We highly
recommend moving development configuration as much as you can to mise.
Specify versions of languages, package managers, tools, and other dependencies
in your mise config rather than relying on defaults or latest. Pinning
versions keeps local development, CI, and production aligned.
A mise.toml in your project keeps tool versions in one place:
[tools]node = "22.14.0"python = "3.13.2"poetry = "2.1.1"jq = "1.7.1"See Mise Configuration for how Railpack detects and applies mise config during builds.
Prefer Mise Over Apt, pipx, and Other Installers
Section titled “Prefer Mise Over Apt, pipx, and Other Installers”When you need a CLI tool or utility, prefer installing it with mise over Apt, pipx, npm, or other package managers. Mise does not support every tool, but it supports most external tools you are likely to need.
Using mise keeps tool versions consistent with the rest of your config and avoids coupling your build to distro packages or a separate install path.
[tools]jq = "1.7.1"ripgrep = "14.1.1"Reach for Apt when you need system libraries or packages that mise does not
provide (for example libpq-dev or ffmpeg). See Installing Additional
Packages for how to add mise and Apt packages to
a build.
Use Mise Lockfiles
Section titled “Use Mise Lockfiles”Add locked = true to your mise config. This ensures that the exact same
version is used for dev, CI, and production.
[tools]node = "22.14.0"python = "3.13.2"
[settings]locked = trueCommit the generated mise.lock file alongside your mise.toml. Railpack
automatically includes mise.lock files in the build when present.
Enable GPG Verification
Section titled “Enable GPG Verification”Mise can verify OpenPGP signatures for tools that publish them. Enable
verification for all supported tools in your mise.toml:
[settings]gpg_verify = true
[settings.node]verify = trueRailpack disables Node.js GPG verification by default because newly released
versions may not have a public key available yet. A project-local mise.toml
takes precedence over Railpack’s generated /etc/mise/config.toml, so
verify = true reverses that default.
With verification enabled, the build fails when mise cannot verify a tool’s downloaded assets. Pin tool versions to avoid unexpectedly selecting a new release whose signatures are not yet available.
Commit Package Manager Lockfiles
Section titled “Commit Package Manager Lockfiles”Always generate and commit a lockfile from your package manager
(package-lock.json, pnpm-lock.yaml, yarn.lock, Cargo.lock,
poetry.lock, etc.), and keep it in sync with your dependency manifests.
Without a lockfile (or with one that has drifted) installs resolve versions at build time. That leads to non-deterministic images and therefore possible bugs that don’t occur locally.
Prefer npm ci for npm Node Projects
Section titled “Prefer npm ci for npm Node Projects”When using npm as your package manager, set RAILPACK_NODE_NPM_INSTALL to
opt into npm ci instead of the default npm install:
RAILPACK_NODE_NPM_INSTALL="npm ci"You can also customize the install command in railpack.json if you need more
control over the install step:
{ "$schema": "https://schema.railpack.com", "steps": { "install": { // Configuring a step auto-adds it to deploy with include: ["."]. // Use an empty deployOutputs so install is not copied into the final // image; node_modules still reach the image via the build step. "deployOutputs": [], "commands": [ // auto-generated commands from railpack { "path": "/app/node_modules/.bin" }, { "src": "package-lock.json", "dest": "package-lock.json" }, { "src": "package.json", "dest": "package.json" }, // By default, `npm install` is used here. Use `npm ci` for increased // determinism. "npm ci" ] } }}Railpack defaults to npm install because package.json and
package-lock.json often drift out of sync — usually from npm bugs rather than
intentional local changes. That mismatch rarely shows up in development and only
fails at image build time with:
npm cican only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync.
Using npm install avoids build failures at the cost of weaker
determinism. You should keep your lockfile in sync and opt into npm ci for:
- Deterministic installs — exact versions from the lockfile, matching local and CI environments, with no silent drift in production
- Faster installs —
npm ciinstalls from the lockfile instead of resolvingpackage.jsonagain