#!/usr/bin/env bash # claudio — run Claude Code isolated in a podman container. # # Install: keep this file next to the Containerfile, then # alias claudio=/path/to/claudio.sh (e.g. in ~/.bashrc) # # Usage: claudio [claude args...] run Claude Code in the current directory # claudio shell [bash args] open a bash shell in the container instead # claudio build build the image # claudio update rebuild from scratch (latest Debian + Claude Code) # # The current directory is mounted at its own path. If it contains a .claudio # file, that file is sourced (bash) for extra mounts and options. # The container's home (/home/claude: login, settings, history) lives in the # global podman volume "claudio-home". set -euo pipefail IMAGE=localhost/claudio:latest VOLUME=claudio-home DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" build() { podman build -t "$IMAGE" "$@" "$DIR"; } case "${1:-}" in build) build; exit ;; update) build --pull=always --no-cache && podman image prune -f; exit ;; esac if [[ "$PWD" == "$HOME" || "$PWD" == / ]]; then echo "claudio: refusing to mount $PWD — run it from a project directory." >&2 exit 1 fi podman image exists "$IMAGE" || build # Project settings (see the .claudio template). MOUNTS=() PODMAN_ARGS=() CLAUDE_ARGS=() if [[ -f .claudio ]]; then source ./.claudio; fi args=(-v "$PWD:$PWD" -w "$PWD" -v "$VOLUME:/home/claude") for m in "${MOUNTS[@]}"; do [[ "$m" == *:* ]] || m="$m:$m" # bare path -> same path inside the container args+=(-v "$m") done if [[ "${1:-}" == shell ]]; then shift args+=(--entrypoint bash) CLAUDE_ARGS=() fi if [[ -t 0 && -t 1 ]]; then args+=(-it); else args+=(-i); fi # keep-id maps your host user to the container's uid 1000, so files written # in mounts stay owned by you. label=disable avoids SELinux relabeling of mounts. exec podman run --rm "${args[@]}" \ --userns=keep-id:uid=1000,gid=1000 \ --security-opt label=disable \ --hostname claudio \ "${PODMAN_ARGS[@]}" \ "$IMAGE" "${CLAUDE_ARGS[@]}" "$@"