Install modes

TL;DR

  • Install modes decide when a component's requirements.txt dependencies install: at publish time, baked into the image, or at deploy time, installed on the node when the container starts. One optional key in component.yml selects the mode — omit it for build-time install, set install: node for deploy-time install.
  • Build-time install bakes every dependency into the published image. Deploys are pure image pulls, so they are fast and predictable, but the image grows with the dependency set — substantially for ML stacks — and publish takes longer because of the install step.
  • install: node publishes a skeleton image that carries the source and requirements.txt but no installed dependencies. The first deploy on a fresh node runs the install on that node; same-node redeploys reuse the cached install.
  • The two modes do the same total install work and differ only in which lifecycle stage pays for it, so the choice is a trade-off about deploy speed, image size, and iteration time.
  • Removing install: node is a mode change, not a cleanup. It moves the install cost back to publish, enlarges the image, and changes deploy behaviour, so confirm the new mode fits the workload before flipping the field.

Mental model — where the install lands

   Build-time install (default; install: omitted)
   ┌────────────────────────────────────────────────────────────┐
   │ ppl component publish                                      │
   │   build_system image + pip install requirements.txt        │
   │     ─▶ FAT image (multi-GB for ML)                         │
   │                ─▶ registry                                 │
   │                                                            │
   │ ppl backend deploy                                         │
   │   every Node:  pull FAT image  ▶  start container          │
   └────────────────────────────────────────────────────────────┘

   install: node
   ┌────────────────────────────────────────────────────────────┐
   │ ppl component publish                                      │
   │   build_system image + Component source + requirements.txt │
   │     ─▶ SKELETON image (no deps installed)                  │
   │                ─▶ registry                                 │
   │                                                            │
   │ ppl backend deploy                                         │
   │   every Node:                                              │
   │     pull skeleton  ▶  install deps locally                 │
   │     (layered on the skeleton)  ▶  start container          │
   └────────────────────────────────────────────────────────────┘

The total install work is the same. The choice is which stage of the lifecycle pays it.

Why two modes exist

Build-time install is the default because a pure image pull is fast and predictable. It lands a known set of bits on the node and starts the container, with no PyPI dependency at deploy time, no resolution surprises, and no per-node install variability. That predictability suits production deployments and any workload that lands on many fresh nodes.

The cost is image size. An ML stack with PyTorch, CUDA, transformers, torchvision, and a handful of opencv extras bakes into a multi-gigabyte image. Every published version of the component carries that image, and every node that runs the component pulls it. For ML-heavy components the image size dominates: pulls take minutes, registry storage grows with every version, and the publish-iterate loop slows down because each change reruns the bake.

install: node fits workloads where the image-size cost outweighs the deploy-time install cost. The published image is a skeleton — the runtime base, the component source, and the requirements.txt, with no installed dependencies. The first deploy on a fresh node runs pip install on that node, layered on top of the skeleton; subsequent deploys on the same node reuse the cached install and start almost instantly.

install: node pays off when the dependency set is large, the same nodes are reused across many deploys, and the publish loop's iteration speed matters. An author iterating on a large ML component gains a much smaller skeleton image that publishes quickly and pays the install cost only on the first deploy per node. The opposite case — a small dependency set, ephemeral nodes, infrequent deploys — does not benefit, because the deploy-time install machinery costs more than the bake would have.

The CUDA wheel variant is the build system's job, not the install mode's

Read this whenever a component pulls CUDA-, CPU-feature-, or architecture-specific dependencies.

Some Python wheels are not portable across hardware. torch+cu126 is a different wheel than torch+cu128, OpenCV with AVX-512 is a different binary than the generic CPU build, and bitsandbytes ships per-CUDA-version wheels. The wheel a component gets is decided by the build_system key, not by the install mode: each CUDA-flavoured build system pins its own stack (torch+cu126 on a CUDA 12.6 stack, torch+cu128 on a CUDA 12.8 stack) and its own package-index URLs. That same pinned template is what the deploy-time node install reuses.

install: node does not inspect the node's GPU class, CPU features, or CUDA version and does not auto-select a wheel for the hardware it lands on. The deploy-time node build receives only the skeleton image and the build system's fixed install-template; it resolves the same wheels the build-time path would have. To target a particular CUDA version, pick the matching build_system — switching the install mode does not change which wheel is installed.

Walkthrough — flip the mode

# component.yml (build-time install — default)
name: "..."
language: py
build_system: 2-cuda12.8-torch2.8-onnxrtgpu1.22

# component.yml (deploy-time install on the Node)
name: "..."
language: py
build_system: 2-cuda12.8-torch2.8-onnxrtgpu1.22
install: node
ppl component publish -m "<msg>"
ppl component promote

install: is a free-form string. Only node defers the install to deploy time; omitting the field — or any other value — uses the build-time bake. There is no special install: build literal. A non-node value still bakes at build time, but it requires a higher build tier (the platform layers an extra install step on the stock builder) and is accepted only on plans that entitle that tier.

Variations — picking which mode

Pick install: node when:

  • The dep set is large enough that the baked image size dominates pull time and registry storage. Baking is wasteful.
  • Iteration speed matters more than deploy speed. Faster publish loop while developing — a small skeleton publishes quickly and the install runs only on the first deploy per Node.

Pick the default (build-time install) when:

  • The dep set is small. Deploy-time install machinery costs more than baking.
  • Deploys land on many fresh Nodes. First-touch install would dominate.
  • Every deploy must be a guaranteed-fast pull — no PyPI dependency at deploy time.

The CUDA wheel variant (torch+cu126 vs torch+cu128, etc.) is set by the build_system key, not by the install mode; install: node does not auto-select a wheel for the Node's hardware. Pick the matching build_system for the CUDA version you target.

Mixing Dockerfile-base with install: node works, but Dockerfile-base runs at publish time while the install runs at deploy time. Order accordingly.

Reference snapshot

The YAML field

install: node    # OR omit entirely for build-time install

Detect the mode

grep '^install:' component.yml
  • Line present and reads install: node → deploy-time install on the Node.
  • Line absent, commented, or any value other than node → build-time install (deps baked into the published image).

Image-size hint

  • Skeleton image (install: node): the runtime base plus the source, with no installed dependencies.
  • Build-time-installed image: the same, plus the entire dependency set baked in — substantially larger for an ML stack.

Common mistakes

MistakeWhy it's wrong
"Deploy is slow — let me drop install: node."Backwards. Removing it slows publish and bloats the image; total work unchanged.
Adding install: node to a tiny component.Deploy-time install machinery costs more than baking a small dep set.
Editing requirements.txt and expecting in-place re-install on a live deployment.Neither mode re-installs against a running deployment; you have to publish a new version and redeploy.

Where this fits

Install modes decide where in the build-deploy lifecycle the dependency install lands. The right answer depends on the workload: image-bake cost versus deploy-time install cost, iteration speed versus production reliability, registry storage versus first-deploy latency. The platform leaves the choice to the component author, with a sensible default and stated trade-offs, rather than forcing every component into one design. The build_system key sits beside this choice — it selects the curated base image and pins the dependency stack (including the CUDA wheel variant), independently of when the install runs (see Build systems).

The discipline is to pick the mode that fits the workload rather than copy the default. install: node fits an iteration-heavy ML component with a large dependency set; build-time install fits a small utility component with a modest one. The wrong choice produces a cost the team pays later in deploy speed, image size, or iteration time.

Related

  • Build systemsbuild_system: picks the curated stack independently of install:.
  • Components — where requirements.txt / xmake_packages are authored.
  • Deployments — what changes when an install: node deploy lands on a fresh node.
  • Publish semantics — the publish + promote loop the install runs inside.

Was this page helpful?