Summary
deploy.base.image appears to be accepted by the schema and documentation, but railpack prepare does not apply it to the generated plan.
I expected this config to make the final deploy/runtime base image debian:bookworm-slim.
{
"$schema": "https://schema.railpack.com",
"deploy": {
"base": { "image": "debian:bookworm-slim" },
"startCommand": "node server.js"
}
}
Instead, the generated railpack-plan.json still uses the default Railpack runtime image, for example:
{
"inputs": [
{
"image": "ghcr.io/railwayapp/railpack-runtime:mise-2026.3.17"
}
],
"name": "packages:apt:runtime"
}
and deploy.base points at that generated runtime apt step:
"deploy": {
"base": {
"step": "packages:apt:runtime"
}
}
Version
Reproduced in Railpack 0.23.0
Reproduction
set -eu
tmp=/tmp/railpack-deploy-base-repro
rm -rf "$tmp"
mkdir -p "$tmp/bin" "$tmp/app"
curl -fsSL \
"https://github.com/railwayapp/railpack/releases/download/v0.23.0/railpack-v0.23.0-x86_64-unknown-linux-musl.tar.gz" \
-o "$tmp/railpack.tar.gz"
printf '%s %s\n' \
'e8bffc181c13e68c1c78ec618f1418e60b1602ec548f3ec1054996394ce0f06a' \
"$tmp/railpack.tar.gz" | sha256sum -c -
tar -xzf "$tmp/railpack.tar.gz" -C "$tmp/bin" railpack
cat > "$tmp/app/package.json" <<'JSON'
{
"scripts": {
"start": "node server.js",
"build": "echo build"
},
"dependencies": {
"express": "latest"
}
}
JSON
cat > "$tmp/app/server.js" <<'JS'
console.log("hello");
JS
cat > "$tmp/app/railpack.json" <<'JSON'
{
"$schema": "https://schema.railpack.com",
"deploy": {
"base": { "image": "debian:bookworm-slim" },
"startCommand": "node server.js"
}
}
JSON
"$tmp/bin/railpack" prepare "$tmp/app" \
--plan-out "$tmp/app/railpack-plan.json" \
--info-out "$tmp/app/railpack-info.json"
grep -nE 'debian:bookworm|railpack-runtime|packages:apt:runtime' \
"$tmp/app/railpack-plan.json" || true
Actual output
Railpack logs that it found the config file:
↳ Using config file railpack.json
But the generated plan does not contain debian:bookworm-slim. It contains the default Railpack runtime image:
"image": "ghcr.io/railwayapp/railpack-runtime:mise-2026.3.17"
Expected behavior
When deploy.base.image is set, the generated deploy base should use that image, either directly as:
"deploy": {
"base": {
"image": "debian:bookworm-slim"
}
}
or, if Railpack needs to create a runtime apt/package step, that generated step should use the configured image as its first/base input instead of the default Railpack runtime image.
Documentation this seems to diverge from
The configuration file docs say If found, that configuration will be used to change how the plan is built.
The same page’s Deploy section documents say base The base layer for the deploy step. The schema also accepts deploy.base as a layer, including an image layer.
Possible implementation cause
From a quick read of the current source, DeployConfig has a Base field. But GenerateContext.applyConfig() appears to apply these deploy config fields:
- startCommand
- aptPackages
- inputs
- paths
- variables
and does not appear to copy Config.Deploy.Base into the deploy builder. So deploy.base may be parsed successfully but never applied before the plan is generated.
Why this matters
This makes it hard to customize the final runtime image while still using Railpack’s generated build plan. In my case I need Railpack to keep its provider/build behavior, but use a specific runtime base image for the deploy layer (I need glibc 2.38, rather than the default base image's 2.36).
Summary
deploy.base.imageappears to be accepted by the schema and documentation, butrailpack preparedoes not apply it to the generated plan.I expected this config to make the final deploy/runtime base image debian:bookworm-slim.
{ "$schema": "https://schema.railpack.com", "deploy": { "base": { "image": "debian:bookworm-slim" }, "startCommand": "node server.js" } }Instead, the generated railpack-plan.json still uses the default Railpack runtime image, for example:
{ "inputs": [ { "image": "ghcr.io/railwayapp/railpack-runtime:mise-2026.3.17" } ], "name": "packages:apt:runtime" }and deploy.base points at that generated runtime apt step:
Version
Reproduced in Railpack 0.23.0
Reproduction
Actual output
Railpack logs that it found the config file:
↳ Using config file
railpack.jsonBut the generated plan does not contain debian:bookworm-slim. It contains the default Railpack runtime image:
Expected behavior
When deploy.base.image is set, the generated deploy base should use that image, either directly as:
or, if Railpack needs to create a runtime apt/package step, that generated step should use the configured image as its first/base input instead of the default Railpack runtime image.
Documentation this seems to diverge from
The configuration file docs say
If found, that configuration will be used to change how the plan is built.The same page’s Deploy section documents say
base The base layer for the deploy step. The schema also accepts deploy.base as a layer, including an image layer.Possible implementation cause
From a quick read of the current source, DeployConfig has a Base field. But GenerateContext.applyConfig() appears to apply these deploy config fields:
and does not appear to copy Config.Deploy.Base into the deploy builder. So deploy.base may be parsed successfully but never applied before the plan is generated.
Why this matters
This makes it hard to customize the final runtime image while still using Railpack’s generated build plan. In my case I need Railpack to keep its provider/build behavior, but use a specific runtime base image for the deploy layer (I need glibc 2.38, rather than the default base image's 2.36).