Run CheetahClaws in a container as a long-running home-server service: Web UI in the browser, Telegram bridge for your phone, all backed by an Ollama instance on the host.
This guide targets a Linux host (Ubuntu / DGX-Spark) running Docker 20.10+ and a local Ollama install. The same compose file works on macOS or Windows with minor tweaks called out inline.
CheetahClaws also runs perfectly well as a normal
pip install. Use Docker when you want a long-running service, network-accessible UI, or process isolation. For local CLI use against your own files, native install is usually smoother.
- Docker Engine 20.10 or newer (
docker --version) - Docker Compose v2 (
docker compose version) - Ollama running on the host with at least one model pulled:
ollama serve & ollama pull qwen2.5:7b # or whichever model you plan to use - Optional: a Telegram bot token (
@BotFather) and your numeric chat id
git clone https://github.com/SafeRL-Lab/cheetahclaws.git
cd cheetahclawscp .env.example .env
$EDITOR .envSet:
UID/GID— match your host user (id -u,id -g) so files written into./workspaceare owned by you, not by root or some random container UID.WEB_PORT— public port on the host. Default 8080.ANTHROPIC_API_KEY/OPENAI_API_KEY— only if you plan to use the cloud providers. Leave blank for an Ollama-only setup.
mkdir -p ./workspace ./data./workspace— the agent's working directory. Mount whatever you want it to read/edit. Share this folder over Samba to access it from your phone or other PCs../data— persists~/.cheetahclaws(config, session history, snapshots).
docker compose up -d --build
docker compose logs -f cheetahclawsYou should see:
CheetahClaws Web Terminal
────────────────────────────────────────
Terminal: http://localhost:8080
Chat UI: http://localhost:8080/chat
Host: 0.0.0.0 (network accessible)
Terminal pwd: <auto-generated>
Open http://<host-ip>:8080/chat from any device on your LAN.
The first time you visit the Chat UI it'll prompt to create an account. After login:
- Pick a model: open settings → choose
customprovider, set the base URL tohttp://host.docker.internal:11434/v1, and set the model toollama/qwen2.5:7b(or whatever you pulled). Save. - Telegram bridge (optional): in the agent prompt, type
/config telegram_token <your-bot-token>and/config telegram_chat_id <your-chat-id>. Restart the container (docker compose restart) — the bridge auto-starts on next boot.
Config persists to ./data/config.json on the host.
The compose file adds:
extra_hosts:
- "host.docker.internal:host-gateway"Inside the container, host.docker.internal resolves to the host's Docker gateway IP. Use http://host.docker.internal:11434/v1 as the OpenAI-compatible base URL.
If your Ollama is bound only to 127.0.0.1 on the host, you'll need to either:
- Set
OLLAMA_HOST=0.0.0.0:11434beforeollama serve, or - Add
network_mode: hostto the service in compose (and remove theports:block, since host networking takes them as-is).
sudo apt install samba
sudo tee -a /etc/samba/smb.conf <<'EOF'
[cheetahclaws]
path = /home/<you>/cheetahclaws/workspace
browseable = yes
writable = yes
guest ok = no
valid users = <you>
EOF
sudo smbpasswd -a <you>
sudo systemctl restart smbdFrom your other PC / phone, connect to smb://<host-ip>/cheetahclaws. Files you drop in show up at /workspace inside the container immediately.
| Task | Command |
|---|---|
| Tail logs | docker compose logs -f cheetahclaws |
| Restart | docker compose restart |
| Update to latest source | git pull && docker compose up -d --build |
| Shell into the container | docker compose exec cheetahclaws bash |
| Run the CLI directly | docker compose exec cheetahclaws cheetahclaws |
| Reset config | rm -rf ./data && docker compose restart |
"Cannot connect to Ollama" — the container can't reach host.docker.internal:11434. Verify:
docker compose exec cheetahclaws curl -s http://host.docker.internal:11434/api/tagsIf this fails, your Ollama is bound to localhost only. Set OLLAMA_HOST=0.0.0.0:11434 and restart it.
Files in /workspace show up as root-owned on the host — UID/GID in .env don't match the host user. Run id -u/id -g, update .env, docker compose up -d.
Web UI not reachable from phone — check the host firewall (sudo ufw allow 8080/tcp) and confirm --host 0.0.0.0 is in the logs.
Telegram bridge not responding — check docker compose logs cheetahclaws | grep -i telegram. Token/chat-id are loaded from ~/.cheetahclaws/config.json (i.e., ./data/config.json on the host). Validate they're set, then restart the container.
Chat UI loads but every JS/CSS asset is 404 (/marked.min.js, /static/js/chat.js, …) — the running server is reading static files from a web/ directory that doesn't actually contain them. This almost always means a custom Dockerfile used a non-editable install (pip install .[all]) without bundling package data, so web/ in site-packages/ is missing the static/js/ subtree. Two ways out:
- Easiest: use this repo's
Dockerfile+docker-compose.ymlunchanged. It usespip install -e '.[web]', which keepsweb/pointed at the source tree. - Or, in your custom Dockerfile, switch to editable install:
Editable install leaves
RUN pip install --no-cache-dir -e '.[all]'
web/server.pynext to itsstatic/directory, so the asset paths resolve correctly regardless of how setuptools handled package-data.
If you must do a non-editable install, make sure your build is using setuptools >= 62 and that pyproject.toml's [tool.setuptools.package-data] for web includes static/**/* (it does, on main). Older setuptools or stale build caches can silently drop subdirectory data.
If you're rolling your own image instead of the one in this repo, keep these in mind:
- Use editable install (
pip install -e '.[web]'). The chat UI's static files (web/static/js/*.js) are package data, not Python code. Editable install removes any dependency on package-data correctly making it into the wheel —web/server.pyis read directly from the source tree. - Don't
WORKDIRaway beforepip install. The install must run with the project'spyproject.tomlat the build context root so setuptools can resolve[tool.setuptools.package-data]. COPYthe full source tree, not justpyproject.toml+ a few.pyfiles. The chat UI ships HTML/JS/CSS that lives outside the Python source — leaving them out is the most common reason/chatloads but assets 404.- Match Python ≥ 3.10. The server uses
Path.is_relative_to,match/case, and other 3.10+ features. - Run
cheetahclaws --web --host 0.0.0.0. Without--host 0.0.0.0the server only binds to127.0.0.1inside the container, which Docker's port mapping cannot reach from the host.
- The Web UI ships with first-visit-creates-admin auth. Don't run with
--no-authoutside127.0.0.1. - Restrict
WEB_PORTto the LAN with your router/firewall — it is not designed to be exposed to the public internet without extra hardening (reverse proxy + TLS + IP allowlist at minimum). - The agent has full read/write inside
/workspace. Mount only directories you're comfortable letting it edit.