Table of contents
In a container
papote has a tool that runs shell commands, and the sensible place to point
that at is a machine you would not mind losing. The Dockerfile in the
repository builds one.
./run.sh # builds, then runs
./run.sh --private # arguments are passed through
which is this, with the directories under volumes/ bind-mounted:
docker build -t papote .
docker run -it --rm \
-e TERM="$TERM" \
-v ./volumes/config:/home/papote/.config/papote \
-v ./volumes/app:/home/papote/app \
-v ./volumes/conversations:/home/papote/.local/share/papote/conversations \
papote "$@"
-it is not optional: papote takes the whole terminal, and without a tty
there is nothing to take. TERM is passed through so the colours match what
your terminal can actually do; the image defaults to xterm-256color.
The container starts in ~/app, which is where a relative path from a tool
lands and where run_shell runs. Nothing is declared a volume in the
Dockerfile — what to mount and where is yours to decide.
What is in it
Debian trixie, the papote binary, and ca-certificates, which is the whole
of what papote itself needs: rustls wants the trust store, and nothing else
is linked. The clipboard finds nothing in here, so Ctrl-V has nothing to
paste — everything else works.
The rest is for the model: git, curl, jq, less and ripgrep. A model
given a machine and nothing to run on it asks for git on its first turn.
It can install more itself — papote may sudo without a password, because
the container is the wall and working around a missing package is not what
you want it spending its turns on.
Who it runs as
Not root: a papote user, uid 1000 by default. Build with
--build-arg UID=$(id -u) if yours is not.
Under rootless Docker this has a consequence worth knowing before you hit it. Rootless maps the container's users into your subuid range:
| in the container | on the host |
|---|---|
| root | you |
| papote (1000) | 101000, a subuid |
So everything the model writes into a bind mount belongs to that subuid, and
you need sudo to edit or delete it from the host — your own
providers.toml included, once papote has written the directory. Running the
container as root instead (--user 0:0) would make those files yours, and
under rootless the container's root is your unprivileged self anyway.
Both are defensible. The Dockerfile keeps the papote user because on a
rootful daemon — a server — that difference is a real one.
What the container does not protect
Your endpoint credentials. providers.toml holds the password in clear
and is mounted in, so a model that asks the shell to read it gets to read
it. You would see the call go past in the confirmation, but the container is
a wall around your machine, not around what papote itself can see. If that
matters, give the container its own account on the endpoint.
papote
Getting started
Using it
Under the hood