Packages
Install the toolchain of a workspace from nixpkgs, pin versions, and update them.
Last updated
On this page
Overview
Packages are the toolchain of a workspace, installed from nixpkgs. The list belongs to the workspace and carries over to a clone or a restore. A base set (base_packages) is always installed. Set the list at creation, change it from code, or use kd pkg inside the workspace.
| Entry | Example | Meaning |
|---|---|---|
| Name | jq | The package at the platform's nixpkgs revision. |
| Name and version | nodejs@22 | The newest cached release that matches the version, locked when added. |
A name is the nixpkgs attribute: up to 64 letters, digits, _, ., +, or -. A version is latest, N, N.N, or N.N.N.
To do these tasks with an AI agent, see Agentic workflows.
Find a package
Search Nixhub for the package name and its published versions. The attribute name that Nixhub shows is the entry name, for example nodejs or python312. Nixhub lists every version, so use it to choose a value for @version. The platform resolves versions from the same index. NixOS Search also lists the attribute names, at the current nixpkgs revision only.
Locks
A versioned entry is locked to an exact release, shown in locks with its version, nixpkgs rev, and source. A lock changes only when you run update. Only releases in the public binary cache can be installed. A short version selects the newest cached release. An uncached exact version is refused, and the error lists the cached versions.
Installation
A change installs in about 30 seconds. A running workspace sees the new tools at its next shell prompt; a new workspace starts after the first installation. A failed installation keeps the previous tools. packages_status shows progress:
reason | Meaning |
|---|---|
Building | Installing. |
Built | Installed. |
NotCached | A locked release has no prebuilt binary. Change the list to retry. |
BuildFailed | Installation failed. Retried after 1 minute, backing off to 1 hour. |
Failed | Installation exceeded the time limit. Change the list to retry. |
Unresolved | A versioned entry has no lock. Set the packages again. |
A declared package can install without putting a binary on the path. packages_status then names the entry in packages_missing, and the packages list shows it as missing. Check the attribute name, or change the entry.
The limit is 100 packages per workspace.
Installing language packages
The Nix store is read-only, so each language's own installer writes under the home instead. A workspace and a capsule set the variables below, and put each bin directory on PATH after the package list's bin and before the system PATH. An env entry of your own with the same name replaces ours.
| Tool | Command | Lands in |
|---|---|---|
| Python | pip install requests | ~/.local (no flag; a virtual environment keeps plain pip) |
| pipx, uv | pipx install, uv tool install | ~/.local/bin |
| npm, yarn | npm i -g, yarn global add | ~/.local (NPM_CONFIG_PREFIX) |
| pnpm | pnpm add -g | ~/.local/share/pnpm |
| corepack | corepack enable --install-directory ~/.local/bin | ~/.local/bin |
| Bun, Deno | bun add -g, deno install -g | ~/.bun/bin, ~/.deno/bin |
| Rust | cargo install | ~/.cargo/bin |
| Go | go install | ~/go/bin |
| Ruby | gem install | ~/.local/share/gem (GEM_HOME) |
| PHP | composer global require | ~/.composer/vendor/bin |
| .NET | dotnet tool install -g | ~/.dotnet/tools |
| Perl | perl Makefile.PL && make install | ~/perl5 |
| R | install.packages() after mkdir -p ~/.local/share/R/library | R_LIBS_USER |
| Lua | luarocks install | ~/.luarocks |
| Elixir | mix escript.install | ~/.mix/escripts |
| Haskell | cabal install | ~/.cabal/bin |
A capsule's home is lost when the capsule ends; a workspace keeps it. The Python package that provides pip must match python3: python314Packages.pip with a 3.14 python3.
Tasks
Add a package
Reason for the task
Install a tool in a workspace.
Job set-up
- Access: you.
- References: Find a package,
PATCH /v1/workspaces/{id}.
Procedure
Add the package, with
@versionto pin it. The SDK call replaces the whole list, so keep the current entries.from kloudlite import Kodekl = Kloudlite()ws = kl.workspaces.get(workspace="backend")kl.workspaces.set_packages(workspace="backend", packages=[*ws["packages"], "nodejs@22"])import { Kode } from "./index.ts";const kl = new Kloudlite();const ws: any = await kl.workspaces.get({ workspace: "backend" });await kl.workspaces.setPackages({ workspace: "backend", packages: [...ws.packages, "nodejs@22"] });kl := kloudlite.New(kloudlite.Options{})ws, err := kl.Workspaces.Get("backend")if err != nil { log.Fatal(err)}var packages []stringfor _, p := range ws["packages"].([]any) { packages = append(packages, p.(string))}err = kl.Workspaces.SetPackages(kloudlite.SetPackages{Workspace: "backend", Packages: append(packages, "nodejs@22")})if err != nil { log.Fatal(err)}require_relative "kloudlite"kl = Kloudlite::Client.newws = kl.workspaces.get(workspace: "backend")kl.workspaces.set_packages(workspace: "backend", packages: ws["packages"] + ["nodejs@22"])var kl = new Kloudlite();// Replaces the list: name every current entry and the new one.kl.workspaces().setPackages(new Kloudlite.SetPackages().workspace("backend").packages("jq", "nodejs@22"));# Replaces the list: repeat --package for every current entry.kode workspaces set-packages backend --package jq --package nodejs@22Use
kd pkginside the workspace instead. It keeps the current entries and prints the lock.kd pkg add nodejs@22
Close-out
- List the packages as in task 002. Make sure that the entry and its lock are listed.
List the packages
Reason for the task
Check which tools and versions a workspace has.
Job set-up
- Access: you.
- References:
GET /v1/workspaces/{id}.
Procedure
List the packages and locks. Inside the workspace,
kd pkg listdoes the same.ws = kl.workspaces.get(workspace="backend")print(ws["packages"], ws["locks"])const ws: any = await kl.workspaces.get({ workspace: "backend" });console.log(ws.packages, ws.locks);ws, err := kl.Workspaces.Get("backend")if err != nil { log.Fatal(err)}fmt.Println(ws["packages"], ws["locks"])ws = kl.workspaces.get(workspace: "backend")p ws["packages"], ws["locks"]System.out.println(kl.workspaces().get("backend"));kode workspaces get backend
Close-out
- Make sure that every package that you expect is listed.
Remove a package
Reason for the task
Remove a tool that you no longer need.
Job set-up
- Access: you.
- References:
PATCH /v1/workspaces/{id}.
Procedure
Remove the package by name, without a version. Inside the workspace,
kd pkg rm jqdoes the same.ws = kl.workspaces.get(workspace="backend")kl.workspaces.set_packages(workspace="backend", packages=[p for p in ws["packages"] if p.split("@")[0] != "jq"])const ws: any = await kl.workspaces.get({ workspace: "backend" });await kl.workspaces.setPackages({ workspace: "backend", packages: ws.packages.filter((p: string) => p.split("@")[0] !== "jq") });ws, err := kl.Workspaces.Get("backend")if err != nil { log.Fatal(err)}var packages []stringfor _, p := range ws["packages"].([]any) { if strings.Split(p.(string), "@")[0] != "jq" { packages = append(packages, p.(string)) }}if err := kl.Workspaces.SetPackages(kloudlite.SetPackages{Workspace: "backend", Packages: packages}); err != nil { log.Fatal(err)}ws = kl.workspaces.get(workspace: "backend")kl.workspaces.set_packages(workspace: "backend", packages: ws["packages"].reject { |p| p.split("@").first == "jq" })// Replaces the list: name every entry that stays.kl.workspaces().setPackages(new Kloudlite.SetPackages().workspace("backend").packages("nodejs@22"));# Replaces the list: repeat --package for every entry that stays.kode workspaces set-packages backend --package nodejs@22
Close-out
- List the packages as in task 002. Make sure that the entry is gone.
Update the locked versions
Reason for the task
Move each pinned package to the newest cached release that matches its version.
Job set-up
- Access: you.
- References:
POST /v1/workspaces/{id}/packages/update.
Procedure
Run the update. Inside the workspace,
kd pkg updatedoes the same.kl.workspaces.update_packages(workspace="backend")await kl.workspaces.updatePackages({ workspace: "backend" });if err := kl.Workspaces.UpdatePackages("backend"); err != nil { log.Fatal(err)}kl.workspaces.update_packages(workspace: "backend")kl.workspaces().updatePackages("backend");kode workspaces update-packages backend
Close-out
- List the packages as in task 002. Make sure that the locks show the expected versions.
Troubleshooting
| Symptom | Possible cause | Corrective action |
|---|---|---|
422 with "nodejs@x" is not a version | The version format is not valid. | Use latest, N, N.N, or N.N.N. |
422 with is not a version anyone published; nearest: … | The version does not exist. | Use one of the versions in the message. |
422 with has no cached build in cache.nixos.org; nearest cached: … | The release has no prebuilt binary. | Use one of the cached versions in the message. |
packages_status is NotCached | A locked release has no prebuilt binary. | Pin a cached version. See task 001. |
packages_status is BuildFailed or Failed | Installation failed or timed out. | Read the message, fix the list, and set it again. |