# Recommendations ## Set the Timezone Explicitly Railpack does not set a timezone. Set `TZ` explicitly for your application. `TZ=UTC` is a good default. ```json title="railpack.json" { "deploy": { "variables": { "TZ": "UTC" } } } ``` ## 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: ```json title="railpack.json" { "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 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`](/config/file). ```json title="railpack.json" { "deploy": { "variables": { "LANG": "en_US.UTF-8" } } } ``` ## 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: ```toml title="mise.toml" [tools] node = "22.14.0" python = "3.13.2" poetry = "2.1.1" jq = "1.7.1" ``` See [Mise Configuration](/config/mise) for how Railpack detects and applies mise config during builds. ## Specify the Node and Package Manager Version Explicitly By default, Railpack will infer the node and package manager version for you. However, it's much better to explicitly define this in your project, both for your team and Railpack. This keeps local development, CI, and production builds perfectly aligned. Package managers supported by mise can be pinned in `mise.toml`: ```toml title="mise.toml" [tools] node = "22.14.0" pnpm = "10.15.1" ``` Mise is the recommended path, but you can use `package.json` configuration as well. ```json title="package.json" { "packageManager": "npm@11.4.2" } ``` The `devEngines` field can also document the required package manager and instruct supporting tooling to reject a different version: ```json title="package.json" { "devEngines": { "packageManager": { "name": "npm", "version": "11.4.2", "onFail": "error" } } } ``` ## 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. ```toml title="mise.toml" [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](/guides/installing-packages) for how to add mise and Apt packages to a build. ## Use Mise Lockfiles Add `locked = true` to your mise config. This ensures that the exact same version is used for dev, CI, and production. ```toml title="mise.toml" [tools] node = "22.14.0" python = "3.13.2" [settings] locked = true ``` Commit the generated lockfile alongside the config (`mise.lock`, or `mise.local.lock` / `mise..lock` for those configs). Railpack includes the lockfile for each detected config. ## Enable GPG Verification Mise can verify OpenPGP signatures for tools that publish them. Enable verification for all supported tools in your `mise.toml`: ```toml title="mise.toml" [settings] gpg_verify = true [settings.node] verify = true ``` Railpack 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 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 When using npm as your package manager, set `RAILPACK_NODE_NPM_INSTALL` to opt into `npm ci` instead of the default `npm install`: ```bash 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: ```json title="railpack.json" { "$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 ci` can 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 ci` installs from the lockfile instead of resolving `package.json` again ## Create a `.dockerignore` File Create a `.dockerignore` file in your repository root to exclude files and directories that are not required in production. Excluding unnecessary files makes your final images smaller, speeds up build upload times, and eliminates production security risks by minimizing the code and assets available inside the running container environment: See [Excluding Files](/config/excluding-files) for full details on pattern matching and using `railpack.json` exclusions. ## Pick One Ignore Approach Use a `.dockerignore` file or the `exclude` field in `railpack.json`. Pick one of those approaches. When both are present, the two lists are merged into a single pattern list. `.dockerignore` patterns come first, then `railpack.json` patterns. The last matching pattern wins, so the lists can negate each other if you aren't careful. An entry in `railpack.json` can undo a `.dockerignore` rule, and a `!` pattern in either list can pull a file back into the build.