Recipes¶
Practical workflows that combine ixt features. Each recipe shows the commands, the resulting state, and what the
ixt.tomllooks like if you want to commit it.
🔀 Side-by-side versions of the same tool¶
Use case. You want to keep ruff at the latest version for everyday
work, but also pin ruff==0.5.7 for a legacy project — both reachable from
your shell, no juggling.
ixt tool install ruff # exposed as `ruff`
ixt tool install ruff==0.5.7 --slot ruff-old --bare # env only, no shim yet
ixt tool config ruff-old expose ruff:ruff-old # exposed as `ruff-old`
After this:
$ ixt tool list
ruff.pypi 0.13.0 [python] bins: ruff
ruff-old.ruff.pypi 0.5.7 [python] bins: ruff-old (-> ruff)
The output shows the two separate names involved:
- Installed id —
ruff-old.ruff.pypiis the slotted install of theruffpackage. - After the bin name — the shim
ruff-oldon PATH points at theruffbinary inside the env.
ixt tool info ruff-old resolves the id prefix and shows the exact id:
$ ixt tool info ruff-old
ruff
Id: ruff-old.ruff.pypi
Backend: python
Package: ruff
Spec: ruff==0.5.7
...
Exposed:
ruff-old -> /.../envs/ruff-old.ruff.pypi/bin/ruff
| Flag / command | Role |
|---|---|
--slot ruff-old |
Side-by-side slot. The installed id becomes ruff-old.ruff.pypi; the package being installed is still ruff. |
--bare |
Skip the default __main__ shim. Without it, install would try to expose ruff, collide with the first install, and warn conflict: already exists. |
ixt tool config ruff-old expose ruff:ruff-old |
Renames the exposed shim. Rule orig:alias takes the binary ruff from the env and links it as ruff-old on PATH. |
Skipping the rename step
ixt tool install ruff==0.5.7 --slot ruff-old --bare installs the env but
leaves it unreachable from PATH (bins: (none) in ixt tool list). Run
ixt tool config ruff-old expose ruff:ruff-old to fix it.
Persisting the layout in ixt.toml¶
Add --save on the install and the toml round-trips through ixt tool export / ixt tool apply. Config steps (expose, inject, …) don't touch ixt.toml — capture them with ixt tool export > ixt.toml once you're happy with the layout:
ixt tool install ruff --save
ixt tool install ruff==0.5.7 --slot ruff-old --bare --save
ixt tool config ruff-old expose ruff:ruff-old
ixt tool export > ixt.toml # snapshot current state
# ixt.toml
[tools]
"@pypi:ruff" = {}
ruff-old = { install = "@pypi:ruff", version = "==0.5.7", expose = ["ruff:ruff-old"] }
The install = "@pypi:ruff" field tells apply which package to install for
the ruff-old slot.
🛡️ Run an unfamiliar npm CLI with runtime policy¶
Use case. You found a new npm CLI you want to try. The package is recent, from an author you don't know. After installation, you want to run it once without giving the runtime process your tokens or normal home-directory view.
Runtime policy starts after install
The commands below restrict the installed CLI when it is launched through the ixt shim. They do not sandbox bun add, npm lifecycle scripts, or other install-time code. If you do not trust the package installation itself, install it in a disposable VM/container/user first.
ixt tool install @acme/shiny-new-cli
# Nothing passes except basic OS vars
ixt tool config shiny-new-cli env base none
ixt tool config shiny-new-cli env allow 'HOME' 'PATH' 'TERM' 'LANG' 'NO_COLOR'
# Filesystem at runtime: read-only host, selected secret paths hidden
ixt tool config shiny-new-cli fs base app-common
ixt tool config shiny-new-cli fs scratch ~/.aws ~/.ssh ~/.npmrc ~/.config/gh
Dry-run before trusting it:
[ixt shim: shiny-new-cli] env passed (5 vars): HOME LANG PATH TERM NO_COLOR
[ixt shim: shiny-new-cli] env blocked (41 vars): AWS_ACCESS_KEY_ID GH_TOKEN NPM_TOKEN …
[ixt shim: shiny-new-cli] fs base='app-common' scratch=['~/.aws', '~/.ssh', '~/.npmrc', '~/.config/gh']
Once you're satisfied the runtime behaviour is acceptable, lift the policy and switch to a direct symlink (zero overhead):
Commit the runtime policy posture in ixt.toml¶
If you want ixt tool apply to reinstate the policy on a new machine, declare it explicitly:
[tools."@acme/shiny-new-cli"]
env_base = "none"
env_allow = ["HOME", "PATH", "TERM", "LANG", "NO_COLOR"]
fs_base = "app-common"
fs_scratch = ["~/.aws", "~/.ssh", "~/.npmrc", "~/.config/gh"]
🔒 Lock a formatter to its working directory¶
Use case. You run a code formatter (e.g. biome, prettier) on every save. It has no reason to read ~/.ssh or write outside the project. Lock it down so it can't — even if the package is later compromised.
ixt tool install @biomejs/biome
# Standard linter preset: OS vars + tool-specific vars
ixt tool config biome env base os-common
ixt tool config biome env allow '*BIOME*'
# Filesystem: read the project (already covered by app-common cwd bind),
# hide secrets
ixt tool config biome fs base app-common
ixt tool config biome fs scratch ~/.ssh ~/.aws
app-common already binds the entire host read-only and makes cwd read-write — a formatter needs nothing more. The scratch entries replace ~/.ssh and ~/.aws with empty writable tmpfs: the formatter won't crash (no ENOENT), it just sees nothing there.
ixt.toml for team-wide enforcement¶
[tools."@biomejs/biome"]
version = "1.9.4"
env_base = "os-common"
env_allow = ["*BIOME*"]
fs_base = "app-common"
fs_scratch = ["~/.ssh", "~/.aws"]
Commit this and every teammate who runs ixt tool apply gets the same runtime policy enforced on Linux with bwrap — no per-person setup.