Docker CLI¶
The painapple docker subcommand group runs the bridge in a Docker or Podman container instead of directly on the host — a Python port of the repo's painapple-docker.sh wrapper, sharing the same config.
Pull-only
This CLI fetches the prebuilt image (wrotek/painapple-code) from Docker Hub — there is intentionally no build subcommand. Building from source (and the personalize/devcontainer paths) requires a repo checkout and ./painapple-docker.sh build, which reads the same config as this CLI.
Bare invocation¶
painapple docker with no subcommand shows status if a config exists, or launches the interactive setup wizard on first run.
Subcommands¶
| Command | Aliases | Description |
|---|---|---|
setup |
— | Interactive setup wizard (workspace, Claude credentials, network). Safe to rerun — edits config in place. |
list |
profiles, ls |
List every deployment (the default one + named profiles) with host-bind address, container name, and running state |
quick [-d] [--cwd PATH] |
— | Zero-config start: current directory (or --cwd) as the workspace, defaults for everything else |
pull [TAG] [--source REF] |
— | Fetch a prebuilt image and tag it for up. Default: wrotek/painapple-code:latest |
up [-d] [WS] [--flag …] |
start |
Start the bridge. -d = detached (durable: --restart unless-stopped); foreground runs are ephemeral (--rm). Extra --flag value pairs are one-off config overrides; a positional path overrides the workspace. |
stop |
— | Stop the running container |
down |
rm |
Stop and remove the container |
restart [--flag …] |
reload |
Recreate the container (down + up -d) |
logs |
log |
Follow container logs |
shell |
sh, exec |
Open a shell inside the container |
claude-login |
login |
Run claude login inside the container |
password |
show-password, url, token |
Show the bridge login URL(s) + password (+ TLS fingerprint when applicable) |
extract [DEST] |
export |
Copy the data volume to a host directory (default ./painapple-data-export). No-op with a hint if the data volume is already a bind mount. |
status |
ps |
Show the resolved config and running state |
config … |
— | Manage config non-interactively (see below) |
help |
--help, -h |
Full command reference |
config subcommands¶
| Command | Description |
|---|---|
config show |
Print the raw docker.yaml (falls back to wrapper.conf if no YAML yet) |
config edit |
Open docker.yaml in $EDITOR (default vi) |
config path |
Print the config file path |
config get [KEY …] |
Print one key, or all keys when none given |
config set --flag value … |
Set keys and save (config --flag value works too) |
Config keys (each also accepts a --kebab-case flag, e.g. LISTEN_HOST → --listen-host):
| Group | Keys |
|---|---|
| Runtime | RUNTIME (docker | podman, empty = auto-detect), RUNTIME_FLAGS |
| Workspace | WORKSPACE, WORKSPACE_MODE (project | parent | multi), CLAUDE_HOME, CLAUDE_JSON |
| Storage | DATA_VOLUME (host dir or named volume for sessions, logs, DuckDB), CONFIG_VOLUME |
| Container | IMAGE, CONTAINER |
| Network | PORT, LISTEN_HOST, TLS_MODE (auto | on | off) |
| UI | INSTANCE_NAME (max 12 chars), ACCENT (preset name or #RRGGBB) |
LISTEN_HOST is the host interface the container publishes on (-p LISTEN_HOST:PORT:8765), not the container's internal bind (always 0.0.0.0:8765). 0.0.0.0 exposes the bridge on every host interface — reachable from your LAN (phones, other machines); 127.0.0.1 (or localhost) keeps it to the local machine only. status and list both show this as the host bind.
DEVCONTAINER_PATH / DOCKERFILE_PATH also exist but are only used by ./painapple-docker.sh build.
Profiles — multiple sandboxes¶
Every command accepts --profile NAME (anywhere in the arguments) to target an independent deployment — its own container, port, data volume, and bridge config. One sandbox per project is the intended use:
painapple docker --profile myapp setup # configure a second sandbox
painapple docker --profile myapp up -d # runs alongside the default one
painapple docker profiles # list them all
A named profile's config lives at ~/.painapple-code/docker-profiles/NAME/docker.yaml, with collision-free defaults derived from the name: container painapple-code-NAME, data volume painapple-data-NAME, bridge config ~/.config/painapple-code/docker-NAME. CLAUDE_HOME still defaults to the shared isolated directory, so one claude login serves every sandbox.
The flag-less invocation is the default profile — the classic ~/.painapple-code/docker.yaml, and the only deployment painapple-docker.sh knows about (named profiles are CLI/desktop-app territory). PAINAPPLE_DOCKER_PROFILE=NAME sets the profile via the environment when the flag is absent.
The macOS desktop app's launcher drives the same profiles: sandboxes added in its setup wizard appear in profiles, and terminal-made profiles show up as instances in the launcher.
The top-level painapple list shows these sandboxes together with any local (non-container) server processes — the whole machine at a glance.
Configuration files & sync¶
The canonical store is ~/.painapple-code/docker.yaml. The bash wrapper's wrapper.conf (in the same directory) is kept in two-way sync so a repo checkout and a pip install can manage the same container:
- Load: whichever file is newer wins (
wrapper.confnewer thandocker.yaml→ the script-side config is used). - Save: both files are written —
wrapper.conffirst, thendocker.yaml, so the YAML ends up newest.
The setup wizard (setup) is a step-by-step TUI with back navigation, pre-filled editable path prompts, and a final review screen you can jump back from. It covers the runtime, workspace layout, Claude credential mounts, storage volumes, network binding, and instance branding.
Environment overrides¶
| Variable | Purpose |
|---|---|
RUNTIME |
Override the container runtime for this run (docker or podman) |
RUNTIME_FLAGS |
Extra global flags passed to the runtime binary on every invocation |
PAINAPPLE_HOME |
Move the config (docker.yaml, wrapper.conf) and isolated Claude home elsewhere (default ~/.painapple-code) |
PAINAPPLE_DOCKER_PROFILE |
Default profile when --profile isn't given |
NO_COLOR |
Disable colored output |
Runtime auto-detection¶
When RUNTIME is unset, the CLI prefers docker (the more common default) and falls back to podman; it errors if neither is on PATH. Podman gets --userns=keep-id applied automatically so bind mounts stay writable, and SELinux-enforcing hosts get :Z-labeled mounts.
Typical first run¶
painapple docker setup # interactive wizard
painapple docker pull # fetch the prebuilt image
painapple docker up -d # start detached
painapple docker password # login URL + password
Or skip the wizard entirely: