CLAUDARIS

/klaw-DAR-iss/ — a moveable docker container for Claude Code and OpenCode. using Arch btw.
ARCHLINUX DOCKER COMPOSE TMUX SELF-HOSTED NO-RESTART-POLICY

Configure

Run the interactive wizard — press enter at any prompt to keep the current/default value:

./claudaris config

It writes your answers to .env (gitignored — every user keeps their own). claudaris sources it, and docker compose reads the same file automatically since it lives in the project directory compose runs from — so claudaris and plain docker compose commands always agree. Set NAME to something unique to you (e.g. claude-ten) if more than one person is running a container from this same repo checkout. Re-run config / configure any time to update it.

variablepurposedefault
NAME Image name, container name, and hostname inside the container (shows up as root@$NAME in the prompt). claudaris
DATA_DIR Host directory for volume mount data — bashrc, aliases, Claude Code / OpenCode auth, ssh keys. /data/$NAME
WORKSPACE_DIR Host directory mounted as /projects inside the container. /home/$USER/projects

Commands

claudaris is a thin wrapper around docker compose (see docker-compose.yml) — it adds the config wizard and host-side seeding, then shells out to compose for the actual container lifecycle. Plain docker compose commands work directly too, once start has seeded the host once.

configconfigure
Interactive wizard — writes NAME, DATA_DIR, WORKSPACE_DIR to .env.
./claudaris config
build
docker compose build --no-cache --pull — always uncached, so it picks up the latest Claude Code / OpenCode releases.
sudo ./claudaris build
start
Seeds host files, then docker compose up -d with all volumes wired up.
sudo ./claudaris start
connect
Starts the container if needed, then docker compose execs a shell.
sudo ./claudaris connect
removestop · rm
docker compose down — stops and removes the container so the next start recreates it fresh.
sudo ./claudaris remove

Optional: SSH access to other nodes

To let the container SSH out to other machines, create $DATA_DIR/ssh on the host and populate it with keys/config before running ./claudaris start. If present, it's bind-mounted read-only to /root/.ssh.

off by default — nothing is mounted unless the directory exists. Since compose can't make a volume conditional on a host path existing, start writes (or removes) a gitignored docker-compose.override.yml to add this mount, which compose merges automatically when present.

Internals

Persistent login. Claude Code and OpenCode are both installed at build time (Claude Code via installer straight into /root; OpenCode via pacman), so a rebuild always picks up the latest release. Three files are bind-mounted individually to survive that: ~/.claude/.credentials.json (the OAuth token), ~/.claude.json (account/onboarding state — Claude Code checks this too, so persisting the token alone isn't enough to avoid a re-login prompt after a rebuild), and ~/.local/share/opencode/auth.json (OpenCode's provider credentials). ./claudaris start seeds all three from empty on the host the first time it runs. All three are individual-file mounts rather than a directory volume, since both tools likely save atomically (write a temp file, then rename() over the target) — rename() onto a symlink replaces the symlink instead of writing through it, silently breaking persistence.

Self-stopping. The container stops itself once its tmux session ends (any shell exit — Ctrl+D, exit, crash) instead of running forever, and there's no Docker restart policy — connect and start both bring it back up automatically if you find it stopped.

Dotfiles. .bashrc and bash_aliases are both bind-mounted as single files so they can be tweaked per-host (e.g. a coworker importing their own aliases) without a rebuild. ./claudaris start seeds them from files/bashrc and files/bash_aliases the first time it runs, without overwriting later edits. Anything that needs to survive regardless (PATH, zoxide, git identity) lives in /etc/... inside the image instead of under /root.

Hostname. docker-compose.yml sets hostname: ${NAME}, so the prompt reads root@claudaris (or whatever you set NAME to) instead of a random container ID.

Repo layout