Skip to content

deploy.base.image in railpack.json is parsed but ignored when generating the deploy base layer #551

@ganesh-rao

Description

@ganesh-rao

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions