Files
claudio/README.md

5.3 KiB

claudio

Run Claude Code isolated in a podman container, one command per project.

Files

  • Containerfile: the image (Debian slim + Claude Code).
  • claudio.sh: builds, updates and runs the container.
  • .claudio: template for per-project settings (plain bash).
  • test.sh: end-to-end tests, run on the host (./test.sh, or ./test.sh --update to also test the slow claudio update).

Install

mkdir -p ~/claudio && cp Containerfile claudio.sh ~/claudio/
chmod +x ~/claudio/claudio.sh
echo 'alias claudio=~/claudio/claudio.sh' >> ~/.bashrc

Requires podman (rootless is fine).

Usage

cd ~/projects/myapp
claudio              # start Claude Code here (builds the image on first run)
claudio --resume     # any extra args are passed to claude
claudio shell        # bash inside the container instead of claude
claudio build        # build the image
claudio update       # rebuild from scratch: latest base image + latest Claude Code
claudio reset        # remove this project's persistent container (PERSIST=1)

The current directory is mounted at the same path inside the container. Nothing else from the host is visible unless you add it to a .claudio file.

Per-project settings

Copy the template into a project and uncomment what you need:

cp ~/claudio/.claudio ~/projects/myapp/
GUI=1
AUDIO=1
ROOT=1
PERSIST=1
PACKAGES=(python3 nodejs)
MOUNTS=("$HOME/.gitconfig:/home/claude/.gitconfig:ro" "/data/shared")
PODMAN_ARGS=(-e GITHUB_TOKEN -p 3000:3000)
CLAUDE_ARGS=(--model opus)
  • GUI=1: graphical apps open on your desktop (see below).
  • AUDIO=1: apps can play sound (see below).
  • ROOT=1: passwordless sudo inside the container (see below).
  • PERSIST=1: keep the container, and system changes, between runs (see below).
  • PACKAGES: extra Debian packages for this project (see below).
  • MOUNTS: extra -v mounts. A bare path is mounted at the same path. Add :ro for read-only.
  • PODMAN_ARGS: extra podman run options.
  • CLAUDE_ARGS: default arguments for claude.

.claudio is sourced as bash, so only use ones you trust.

Graphical apps and sound

Both are off by default and work by sharing a single socket from your session.

  • GUI=1 shares your Wayland socket and the GPU (/dev/dri), and makes GTK, Qt, SDL and Electron apps use Wayland. It requires a Wayland session (there is no X11 support). How isolated the container is depends on your compositor: on KDE Plasma it can type and click into other apps, record the screen, read the clipboard and control all windows, because KWin offers those interfaces to every app.
  • AUDIO=1 shares the PulseAudio socket (pulse/native, also provided by PipeWire). It works both ways: anything in the container can also record from your microphone.

The image only ships OpenGL drivers, a font and the PulseAudio client. Add the apps you want to run (and their libraries) to the apt-get install line.

Root access

ROOT=1 lets Claude (and you, in claudio shell) run sudo without a password, for example to apt-get install a missing tool. It is off by default: the image grants passwordless sudo to the sudo group, and claudio only adds you to that group when ROOT=1.

  • System changes last only as long as the container, which is removed after every run unless PERSIST=1 (see below).
  • With rootless podman, root in the container is still your unprivileged host user, so it gets no access to the host beyond what is already mounted. With rootful podman it is real root (limited by the container's capabilities).

Keeping system changes

There are two ways, and they combine well:

  • PACKAGES=(python3 nodejs) builds a per-project image with those packages on top of the base image (localhost/claudio:pkgs-<hash>). It is rebuilt automatically when the list changes or after claudio update, so this is the place for anything the project always needs. Projects with the same list share the image.
  • PERSIST=1 keeps one container per project instead of a fresh one each run, so anything done with sudo (or written outside the mounts) is still there next time. All claudio and claudio shell sessions in the project share it, and it is stopped when the last one exits. It is recreated, losing those changes, when the image changes (claudio update, a new PACKAGES list) or when the container's options change (MOUNTS, GUI, AUDIO, ROOT, PODMAN_ARGS); editing CLAUDE_ARGS or comments does not. Run claudio reset to start it over by hand. Background processes do not survive the stop, and -e VAR values in PODMAN_ARGS are read when the container is created.

A good habit: let Claude install things ad hoc with PERSIST=1, and move the ones worth keeping into PACKAGES.

State

The container's home (/home/claude) lives in the podman volume claudio-home. This volume is shared by all projects and holds your login, settings and history. The container itself is removed after every run, unless PERSIST=1.

  • Reset everything: podman volume rm claudio-home
  • Need more tools in the image? Add them to the apt-get install line, then run claudio update.

Notes

  • Files written in mounts are owned by your host user (--userns=keep-id).
  • SELinux labeling is disabled for the container, so mounts work without relabeling.
  • claudio refuses to run from $HOME or /.