Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Installation

hort is a single binary written in Rust. You build it from source, put it on your PATH, and check that your host can run sandboxes with hort doctor.

Host requirements

hort runs on Linux only, as your normal unprivileged user. It needs no root and no daemon.

RequirementNeeded forIf it is missing
Unprivileged user namespacesEvery sandboxNo sandbox can be built at all.
pasta on PATH (the passt package)Sandbox networkinghort up refuses to start.
git on PATHEvery hort up, repository or nothort up refuses to start.
Overlay filesystem usable inside a user namespaceEvery sandbox rootA build fails when it mounts the root.
libseccomp (the shared library)Running the hort binaryThe binary does not start.
ip on PATH (the iproute2 package)Only sandboxes with an egress allowlisthort up refuses an allowlisted sandbox.
cgroup v2 memory and cpu controllers delegated to your userOnly a resources ceilingThe ceiling is dropped with a warning; the sandbox still starts.
Landlock ABI 4 or later (Linux 6.7+)One of the layers of an egress allowlistThe allowlist runs without that layer, with a warning.
notify-send on PATH (libnotify)Only desktop notificationsNo notification is raised, with a warning.

You also need a prepared rootfs directory: the base filesystem, with your agents inside, that every sandbox runs. That has its own page, Preparing a rootfs.

Building the binary

You need a Rust toolchain, version 1.89 or newer, and the libseccomp development package (libseccomp-devel on Fedora, libseccomp-dev on Debian and Ubuntu).

git clone https://github.com/johnsilverio/hort.git
cd hort
cargo build --release --locked
mkdir -p ~/.local/bin
install -m 0755 target/release/hort ~/.local/bin/hort

Make sure ~/.local/bin is on your PATH, then:

$ hort --help
The parsed command line: one subcommand and its flags

Usage: hort <COMMAND>

Commands:
  up      Build a sandbox and open a session in it
  attach  Open one more session in a running sandbox
  ls      List every sandbox with its reconciled state
  down    Tear a sandbox down in the mandatory order
  prune   Remove idle sandboxes and abrupt-death debris after confirming
  config  Ask what this host can do and write the global configuration
  doctor  Report what this host can do, changing nothing
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

The binary links dynamically against the C library and libseccomp, so copy it only to machines that have both.

Checking your host with hort doctor

hort doctor reads the host and prints one row per capability, changing nothing. Run it now, and again whenever you change something on the host.

$ hort doctor
host
  user namespaces      yes
  pasta                /usr/sbin/pasta
  ip                   /usr/sbin/ip
  cgroup memory        delegated
  cgroup pids          delegated
  cgroup cpu           not delegated
    a sandbox runs with no CPU ceiling. Add cpu to Delegate= in a systemd drop-in for user@.service.
  cgroup cpuset        not delegated
    a sandbox cannot be pinned to a set of cores. Add cpuset to Delegate= in a systemd drop-in for user@.service.
  landlock             ABI 7
  rootless overlayfs   yes
  notify-send          /usr/sbin/notify-send
  git                  yes

configuration
  rootfs               no rootfs configured — set "rootfs" to a prepared rootfs directory in .hort.json or ~/.config/hort/config.json

Under each thing the host lacks, doctor says what that costs and how to get it. The last row reports your configured rootfs with the same message hort up would print, or ready once it can carry a sandbox.

The exit status is 0 when the three hard requirements (user namespaces, pasta, git) are met and 1 otherwise, whatever the rootfs row says, so you can use it as a gate in a script:

hort doctor > /dev/null && echo "this host can build sandboxes"

See hort doctor for every row.

Fixing what doctor reports

User namespaces. Most distributions allow unprivileged user namespaces by default. If doctor says no, check the user.max_user_namespaces sysctl (it must be above zero). On Ubuntu 23.10 and later, AppArmor restricts them through the kernel.apparmor_restrict_unprivileged_userns sysctl; read your distribution's guidance before relaxing it.

pasta, git, ip, notify-send. Install the passt, git, iproute2 and libnotify packages of your distribution.

cgroup controllers. systemd delegates only memory and pids to users by default. A resources.cpus ceiling needs cpu as well. Add a drop-in and restart your user session:

sudo mkdir -p /etc/systemd/system/user@.service.d
printf '[Service]\nDelegate=cpu cpuset io memory pids\n' | sudo tee /etc/systemd/system/user@.service.d/delegate.conf
sudo systemctl daemon-reload

Then log out completely and back in (or reboot) and run hort doctor again. hort caps CPU time, never pins cores, so cpuset is reported but not used by any setting.

Landlock. The Landlock row matters only for egress allowlists. Below ABI 4 an allowlisted sandbox loses its kernel port restriction; the other layers still hold. See Networking and egress.

Where hort keeps things

WhatWhere
Global configuration~/.config/hort/config.json (or $XDG_CONFIG_HOME/hort/config.json)
Per-sandbox records, overlays, worktrees, notify channels~/.local/state/hort/sandboxes/<name>/ (or under $XDG_STATE_HOME/hort)
Dependency caches~/.local/state/hort/cache/<project>/
Container state, helper pid files and the per-sandbox log$XDG_RUNTIME_DIR/hort/ (usually /run/user/<uid>/hort/), emptied on reboot

Worktrees live under hort's state directory, not next to your repository, so nothing is added to your project folder.

Next

Prepare a rootfs, then follow the Quickstart.