A Docker image for one app should not install the dependencies of forty
others. And when another team bumps a package in their app, your image's
install layer should not rebuild. vx prune cuts the workspace down to
what one project needs.

diagram

One command

The demo workspace has four packages. @demo/web depends on @demo/ui,
and @demo/docs pulls in picocolors from npm.

$ vx prune @demo/web --docker vx prune: 2 projects → out (json/ + full/), bun.lock pruned @demo/ui (packages/ui) @demo/web (packages/web) 

The output has only the two projects web needs. The root
package.json lists only their folders, and the lockfile drops every
entry nothing kept reaches:

$ grep -c picocolors bun.lock out/json/bun.lock bun.lock:2 out/json/bun.lock:0 $ cd out/full && bun install --frozen-lockfile Checked 4 installs across 3 packages (no changes) [2.00ms] 

The pruned lockfile keeps the package manager's own layout, so a frozen
install accepts it as is.

The Dockerfile

--docker splits the output in two. json/ holds only what the install
reads, so Docker caches that layer until a dependency of this app
changes. full/ holds everything else.

COPY out/json/ . RUN bun install --frozen-lockfile COPY out/full/ . RUN bunx vx run @demo/web#build 

Where it comes from

vx prune is a verb that @vzn/vx-lockfile adds through the plugin
commands seam. Declare the plugin for your package manager, and the verb
appears:

// vx.workspace.ts import { defineWorkspace } from '@vzn/vx/config' import { bun } from '@vzn/vx-lockfile' export default defineWorkspace({ plugins: [bun()] }) 

pnpm(), npm() and yarn() work the same way. The same plugin keys
each task on its own project's dependencies, which
Lockfile-aware keys covers. Every rule and
refusal is in the
vx-lockfile README.

Originally published on the vx blog. vx is an MIT task runner and build cache for JS monorepos: vznjs.github.io/vx · GitHub.

Written with AI assistance.