Tool Index

The tool index is the mapping from tool names (rg, python, curl) to the container images kapsl runs. kapsl consults a local cache of a global index, plus any overrides in your kapsl.toml. You rarely need to think about it — but it's the mechanism that makes kapsl rg resolve to a real image.

How resolution works

When you run kapsl <tool>, kapsl resolves the image in this order:

  1. Local kapsl.toml — the [tools] section (see Tool Mappings)
  2. Cached global index~/.cache/kapsl/index/index.json
  3. Fallbackkapsl.sh/<tool>:latest

The first match wins. Overriding a tool from the global index is just a matter of adding it to your local [tools].

The global index

The global index is a single JSON file published at https://index.kapsl.sh/index.json. It currently maps 236 tools to images across categories: programming languages, network utilities, devops tools, databases, text processing, system monitors, and AI tools (model hubs, agentic coding CLIs, local LLM hosting). Each entry can carry:

FieldPurpose
imageOCI image reference
binBinary to run (for multi-tool images, e.g. dig/nslookup/host from one bind image)
descriptionOne-line synopsis shown by kapsl --search and kapsl --info
tagsSearchable keywords
categoryBroad grouping for display
lifecycleruntime (multiple maintained version lines) or rolling (:latest is the primary tag); absent = rolling
versionsSelectable tags for tool@version (e.g. Python's 3.12, 3.13, latest) — any tool may declare them, not just runtimes
capabilitiesRuntime capabilities kapsl enables automatically (e.g. net, browser, pid)
dotfilesHost paths to mount read-write into the container (e.g. ~/.aws), each declared as a file or a directory so a missing path is bootstrapped correctly on first run
package_rulesPer-package effects keyed on (provider, package): a dotfiles host-path mount and/or an auto-enabled capabilities entry, triggered when a specific package is installed rather than the tool itself (e.g. huggingface_hub under pip mounts ~/.cache/huggingface; requests under pip auto-enables net) — see Package managers

Every container gets HOME=/kapsl/home, backed by an ephemeral tmpfs: tool-written files like shell history and caches succeed and vanish with the container instead of failing against the read-only image or landing in your project directory. Declared dotfiles are mounted under /kapsl/home (e.g. ~/.aws/kapsl/home/.aws), so the tool finds its persisted config via $HOME while everything undeclared stays ephemeral. package_rules dotfiles mounts land the same way, confined to ~/.cache/ or ~/.config/ (where XDG_CACHE_HOME/XDG_CONFIG_HOME already point); their capabilities are applied with the same precedence as any other auto-capability — explicit CLI flags always win.

Using the index

kapsl --search <query> matches tool names, descriptions, and tags. Local kapsl.toml entries are listed first:

kapsl --search dns
kapsl --search python
kapsl --search kubernetes

Show details

kapsl --info <tool> prints the index entry plus — if the image is pulled locally — labels read from the image itself:

kapsl --info curl

Refresh the index

The index is cached and checked automatically on a schedule (see Freshness). To check it now — and download a new version if index.kapsl.sh/index.json has one:

kapsl --update

Version selection

Tools that declare a versions list can be pinned with @version:

kapsl [email protected] --version
kapsl node@22 --version

Tools without a versions list still accept @<tag>, but the tag must be one the image actually publishes.

Lifecycle: runtime vs rolling

Each tool follows one of two versioning lifecycles, shown by kapsl --info <tool>:

  • runtime — a tool with multiple maintained version lines (python, node). The tag model is the one IMAGE-REQUIREMENTS.md §18 defines: each active upstream maintenance line publishes an exact build-version tag (python 3.14.6, node 22.23.2) plus a major/minor floating tag (and a major-only float for the current line: python 3.14 AND python 3), with latest pointing at the newest/default line only. The floating tags move forward on each rebuild (the 3.12 line's tag advances 3.12.x → newer 3.12.y), which is what keeps a maintained line patched. For bit-for-bit reproducibility, pin a digest in your kapsl.toml instead (the exact build-version tag for node is immutable; python's is rebuilt within the same version — see §18 item 1). A digest-pinned entry looks like:

    [tools]
    python = { image = "kapsl.sh/python@sha256:…", command = "python3" }
  • rolling (the default) — :latest is the primary tag and what kapsl <tool> gives you: always the current build. Versioned tags may exist alongside it and are selected the same way (tool@version).

Old version lines are kept, and they age. A version line that upstream stops maintaining keeps its tag but stops receiving rebuilds: known CVEs in it stay open, permanently. This is deliberate — version history exists so you can reproduce a bug or test against several versions of a tool (especially in CI/CD), not to be a maintained distribution channel. Reach for an old line when you need that version, and expect it to be vulnerable. The vulnerability scan still runs on old lines by default and blocks on CRITICAL findings; a testing setup that accepts the risk opts out per-run with --skip-scan, or globally with security_scan_enabled = false in kapsl.toml.

Automatic capabilities

Some index entries declare capabilities, which kapsl enables automatically so you don't have to pass a flag. For example, the system-monitor tools (htop, ps, top) declare capabilities = ["pid"], so kapsl shares the host PID namespace for them without requiring --cap pid. Likewise, a tool that needs the browser relay can declare capabilities = ["browser"], or a GPU-only tool can declare capabilities = ["gpu"] (podman/docker only — see Security Layers > GPU device passthrough).

Adding a tool to the index

The index source lives in kapsl-index/tools/*.toml of the kapsl repo (this repo holds only the documentation), grouped by category. To add a tool, edit the relevant file in a kapsl checkout:

# kapsl-index/tools/text.toml

[ripgrep]
image = "kapsl.sh/rg:latest"
bin = "/usr/bin/rg"
description = "Fast regex search tool"
tags = ["rg", "ripgrep", "search", "grep", "text"]
category = "text"

Then compile and push — the CI workflow (.forgejo/workflows/deploy-index.yml) recompiles index.json and deploys it on any push to main that touches kapsl-index/. To test locally first:

# in a checkout of the kapsl repo:
cd kapsl-index
./compile.py     # the shebang runs python through kapsl itself
cp index.json ~/.cache/kapsl/index/index.json
kapsl --search ripgrep

See Tool Mappings for overriding a tool in your personal config without changing the global index.