Skip to content

Install with Docker / Podman

The recommended way to run pAInapple Code is in a container — it gives you the isolation the security notes call for, and the image bundles everything the bridge needs.

The image ships Python 3.13, Node 20, the @anthropic-ai/claude-code CLI, git, and a baseline dev toolkit (ripgrep, fd, jq, tmux, vim, and more). Bridge state persists in named volumes; your project and an isolated Claude CLI home are bind-mounted from the host. The Dockerfile is OCI-compliant, so it works with Docker, Podman, nerdctl, or any other OCI runtime.

Option A — built-in docker CLI (pipx, no clone needed)

The pip package ships a painapple docker command group with an interactive TUI setup — arrow-key menus, a browsable directory picker (type to filter, Left / Right to climb or enter folders), back navigation on every step, and a final review screen that jumps back into any section.

pipx install painapple-code
painapple docker           # first run opens the setup wizard
painapple docker pull      # fetch wrotek/painapple-code:latest (pull v1.0.0 to pin)
painapple docker up -d     # start the bridge detached

In a hurry?

painapple docker quick skips setup entirely and starts the bridge against the current directory with sane defaults.

Other subcommands: stop, down, restart, logs, shell, claude-login, password (reveal the login URL), extract, status, config (non-interactive --flag value overrides). Run painapple docker help for the full list.

The setup wizard configures an isolated .claude home by default — the container doesn't share state with your host CLI — and offers to seed it from your host login once.

Docker and Podman are auto-detected; override with RUNTIME=docker or RUNTIME=podman if both are installed. This CLI is pull-only — building the image from source needs a repo checkout (Option C below; both tools share the same config, so mixing them works).

Option B — raw docker run (one-liner, no clone needed)

docker run -d --name painapple-code \
    -p 127.0.0.1:8765:8765 \
    -v "$PWD:/workspace" \
    -v "$HOME/.painapple-code/.claude:/home/app/.claude" \
    -v painapple-data:/data \
    wrotek/painapple-code:latest
docker logs painapple-code 2>&1 | grep -E 'http(s)?://' | head -1   # bootstrap URL

Image tags:

Tag Meaning
:latest Newest stable release
:vX.Y.Z Pinned to a specific release
:edge Manual builds off main

Published image and UIDs (Linux)

The published image bakes in USER_UID=1000. On Linux with rootful Docker and a host UID other than 1000, bind-mount writes may land owned by 1000:1000. Build locally (Option C) when UIDs need to match exactly.

Option C — build from source with painapple-docker.sh (clone the repo)

The Bash wrapper does everything Option A does and adds local image builds — including personalized builds layered from your own devcontainer.json or Dockerfile.

git clone https://github.com/wrotek/painapple-code.git
cd painapple-code
./painapple-docker.sh setup     # interactive: workspace, claude credentials, port, accent
./painapple-docker.sh build     # build the image locally (~2-3 min, once)
./painapple-docker.sh up -d     # start the bridge detached

The wrapper auto-detects the OCI runtime (force with RUNTIME=docker / RUNTIME=podman), passes your USER_UID/USER_GID build args automatically so bind mounts stay writable, and auto-applies the Podman-specific flags (--userns=keep-id, SELinux :Z). If you don't want to build, swap build for pull — it retags the fetched image so the rest of the wrapper finds it.

To layer your own tooling on top of the base image:

# Layer Dev Container Features from a devcontainer.json
./painapple-docker.sh build --devcontainer ~/my-project/.devcontainer

# Or append your project's existing Dockerfile
./painapple-docker.sh build --dockerfile ~/my-project/Dockerfile

Open http://localhost:8765/ in a browser. The first run logs a bootstrap URL with the auth password embedded as ?tkn=… — open that link once and the cookie keeps you logged in. See First run & login.

Manual Compose / Podman (no wrapper)

docker compose build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g)
WORKSPACE=/absolute/path/to/your/project docker compose up

The WORKSPACE env var is required — compose refuses to start without it. For convenience, drop it into a .env file next to docker-compose.yml:

# .env
WORKSPACE=/Users/me/code/some-repo
BRIDGE_PORT=18765   # optional: custom host port

By default the compose file mounts ~/.painapple-code/claude-home/ as the container's .claude. To seed it with your existing login:

mkdir -p ~/.painapple-code/claude-home
cp ~/.claude/.credentials.json ~/.painapple-code/claude-home/

Podman is daemonless, rootless-by-default, and CLI-compatible with Docker:

podman build --build-arg USER_UID=$(id -u) -t painapple-code:latest .
podman run --rm -it --userns=keep-id \
    -p 8765:8765 \
    -v painapple-data:/data \
    -v "$HOME/.painapple-code/claude-home:/home/app/.claude:Z" \
    -v "/absolute/path/to/your/project:/workspace:Z" \
    painapple-code:latest

Podman-specific flags:

  • --userns=keep-id — maps your host UID directly into the container. Without it, rootless Podman remaps UIDs through /etc/subuid and bind-mounted files appear as nobody. Combined with the matching USER_UID build arg, your host user owns files inside the container.
  • :Z mount suffix — applies a private SELinux label so the container can read/write the bind mount. Required on Fedora/RHEL/CentOS/Rocky; a harmless no-op on Debian/Ubuntu/Arch. Use :z (lowercase) if the same volume is shared between containers.

Volumes

Mount target in container Purpose
/data Bridge state (sessions, shadow DB, logs, presets, uploads) via PAINAPPLE_CODE_HOME=/data. Back up this one volume to back up everything.
/home/app/.config/painapple-code Auth config — the generated password. Persist it so down/up keeps the same login.
/home/app/.claude Container-local Claude CLI state (OAuth, history). Defaults to an isolated host path so the container never writes into your host's ~/.claude. Mount $HOME/.claude instead to share state with your host CLI, or drop the mount and set ANTHROPIC_API_KEY for headless deploys.
/workspace Required. Your project directory — where Claude reads and edits files. Mount a single repo or a parent directory of many.

The workspace mount is mandatory

The entrypoint checks that a real host directory is mounted at /workspace and exits with a configuration error (exit code 78) if it isn't. Compose refuses to start with WORKSPACE unset. This catches the common "I forgot to mount my project" mistake.

Authenticating the in-container Claude CLI

The claude subprocess inside the container needs to authenticate to Anthropic. Three paths:

  1. Isolated .claude home (default) — seed it once with cp ~/.claude/.credentials.json <claude-home>/, or run painapple docker claude-login / ./painapple-docker.sh claude-login to log in inside the container.
  2. Share the host's ~/.claude — mount it directly. Easiest, but the container can mutate your host state.
  3. API key — set ANTHROPIC_API_KEY and remove the .claude mount. Cleanest for headless servers and CI.

Next step

Continue to First run & login to get the bootstrap URL and open your first session.