{"id":45890260,"url":"https://github.com/psyb0t/docker-claude-code","last_synced_at":"2026-04-10T00:22:42.297Z","repository":{"id":298986562,"uuid":"1001753343","full_name":"psyb0t/docker-claude-code","owner":"psyb0t","description":"Claude Code in a Docker container. No host installs. No permission nightmares. Just vibes and --dangerously-skip-permissions.  Four ways to unleash it: interactive, programmatic, api server, telegram bot. Get shit done while takin' a shit.","archived":false,"fork":false,"pushed_at":"2026-04-03T00:02:44.000Z","size":226,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-03T03:22:32.514Z","etag":null,"topics":["ai","ai-agent","api","claude","claude-code","code-agent","container","development-agent","docker","docker-image","multimodal-agent","programmatic","telegram","web-api","wrapper"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/psyb0t.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-14T00:32:36.000Z","updated_at":"2026-04-03T00:56:43.000Z","dependencies_parsed_at":"2025-07-09T13:44:55.964Z","dependency_job_id":"24b74dd3-95d0-452f-a776-c494b2ae3421","html_url":"https://github.com/psyb0t/docker-claude-code","commit_stats":null,"previous_names":["psyb0t/docker-claude-code"],"tags_count":53,"template":false,"template_full_name":null,"purl":"pkg:github/psyb0t/docker-claude-code","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psyb0t%2Fdocker-claude-code","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psyb0t%2Fdocker-claude-code/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psyb0t%2Fdocker-claude-code/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psyb0t%2Fdocker-claude-code/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/psyb0t","download_url":"https://codeload.github.com/psyb0t/docker-claude-code/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/psyb0t%2Fdocker-claude-code/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31518643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agent","api","claude","claude-code","code-agent","container","development-agent","docker","docker-image","multimodal-agent","programmatic","telegram","web-api","wrapper"],"created_at":"2026-02-27T17:32:43.269Z","updated_at":"2026-04-07T16:01:54.374Z","avatar_url":"https://github.com/psyb0t.png","language":"Python","readme":"# 🧠 docker-claude-code\n\n[Claude Code](https://claude.com/product/claude-code) in a Docker container. No host installs. No permission nightmares. Just vibes and `--dangerously-skip-permissions`.\n\nFour ways to unleash it:\n\n- **Interactive** — drop-in `claude` CLI replacement, persistent container, picks up where you left off\n- **Programmatic** — pass a prompt, get a response, pipe it into your cursed pipeline\n- **API server** — HTTP endpoints for prompts, file management, monitoring. Slap it in your infra\n- **Telegram bot** — talk to Claude from your phone when you're takin' a shit. Per-chat workspaces, models, effort levels, file sharing, shell access\n\n## Table of Contents\n\n- [Why?](#-why)\n- [Image Variants](#-image-variants)\n- [What's Inside?](#-whats-inside-full-image)\n- [Requirements](#-requirements)\n- [Quick Start](#%EF%B8%8F-quick-start)\n- [Usage](#-usage)\n  - [Env vars](#env-vars)\n  - [Interactive mode](#interactive-mode)\n  - [Programmatic mode](#programmatic-mode)\n  - [API mode](#api-mode)\n  - [Telegram mode](#telegram-mode)\n- [Customization](#-customization)\n- [Gotchas](#-gotchas)\n- [License](#-license)\n\n## 💀 Why?\n\nBecause installing things natively is for people who enjoy suffering.\n\nThis image exists so you can run Claude Code in a fully isolated container with every tool known to humankind pre-installed, passwordless sudo, docker-in-docker, and zero concern for your host system's wellbeing. It's like giving an AI a padded room with power tools.\n\n## 🎞️ Image Variants\n\nPick your poison:\n\n### `latest` (full) — the kitchen sink\n\nEverything pre-installed. Go, Python, Node, C/C++, Terraform, kubectl, database clients, linters, formatters, the works. Big image, zero wait time. Claude wakes up and gets to work immediately.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claude-code/master/install.sh | bash\n```\n\n### `latest-minimal` — diet mode\n\nJust enough to run Claude: Ubuntu, git, curl, Node.js, Docker. Claude has passwordless sudo so it'll install whatever it needs on the fly. Smaller pull, but first run takes longer while Claude figures out its life choices.\n\n```bash\nCLAUDE_MINIMAL=1 curl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claude-code/master/install.sh | bash\n```\n\nPro tip: use `~/.claude/init.d/*.sh` hooks to pre-install your tools on first container create instead of waiting for Claude to `apt-get` its way through life.\n\n### Side by side\n\n|                                       | `latest` (full) | `latest-minimal` |\n| ------------------------------------- | :-------------: | :--------------: |\n| Ubuntu 22.04                          |       yes       |       yes        |\n| git, curl, wget, jq                   |       yes       |       yes        |\n| Node.js LTS + npm                     |       yes       |       yes        |\n| Docker CE + Compose                   |       yes       |       yes        |\n| Claude Code CLI                       |       yes       |       yes        |\n| Go 1.26.1 + tools                     |       yes       |        -         |\n| Python 3.12.11 + tools                |       yes       |        -         |\n| Node.js dev tools                     |       yes       |        -         |\n| C/C++ tools                           |       yes       |        -         |\n| DevOps (terraform, kubectl, helm, gh) |       yes       |        -         |\n| Database clients                      |       yes       |        -         |\n| Shell utilities (ripgrep, bat, etc.)  |       yes       |        -         |\n\n## 🎞️ What's Inside? (full image)\n\nThe full image is a buffet of dev tools. Here's what Claude gets to play with:\n\n**Languages \u0026 runtimes:**\n\n- Go 1.26.1 with the whole toolchain (golangci-lint, gopls, delve, staticcheck, gofumpt, gotests, impl, gomodifytags)\n- Python 3.12.11 via pyenv with linters, formatters, testing (flake8, black, isort, autoflake, pyright, mypy, vulture, pytest, poetry, pipenv) plus common libs (requests, beautifulsoup4, lxml, pyyaml, toml)\n- Node.js LTS with the npm ecosystem loaded (eslint, prettier, typescript, ts-node, yarn, pnpm, nodemon, pm2, framework CLIs, newman, http-server, serve, lighthouse, storybook)\n- C/C++ (gcc, g++, make, cmake, clang-format, valgrind, gdb, strace, ltrace)\n\n**DevOps \u0026 infra:**\n\n- Docker CE with Docker Compose (docker-in-docker chaos)\n- Terraform, kubectl, helm, gh CLI\n\n**Databases:**\n\n- sqlite3, postgresql-client, mysql-client, redis-tools\n\n**Shell \u0026 system:**\n\n- jq, tree, ripgrep, bat, exa, fd-find, ag, htop, tmux, shellcheck, shfmt, httpie, vim, nano\n- Archive tools (zip, unzip, tar), networking (net-tools, iputils-ping, dnsutils)\n\n**Magic under the hood:**\n\n- Auto-generated `CLAUDE.md` in workspace listing all available tools (so Claude knows what it has)\n- Auto-Git config from env vars\n- Claude Code (auto-updates disabled by default, opt in with `--update`)\n- Workspace trust dialog pre-accepted (no annoying prompts)\n- Custom scripts via `~/.claude/bin` (in PATH automatically)\n- Init hooks via `~/.claude/init.d/*.sh` (run once on first container create)\n- Session continuity with `--continue` / `--no-continue` / `--resume \u003csession_id\u003e`\n- Debug logging (`DEBUG=true`) with timestamps everywhere\n\n## 📋 Requirements\n\n- Docker installed and running. That's it.\n\n## ⚙️ Quick Start\n\n### One-liner install\n\n```bash\n# full image (recommended)\ncurl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claude-code/master/install.sh | bash\n\n# minimal image\nCLAUDE_MINIMAL=1 curl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claude-code/master/install.sh | bash\n\n# custom binary name (if you already have a native `claude` install)\ncurl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claude-code/master/install.sh | bash -s -- dclaude\n# or: CLAUDE_BIN_NAME=dclaude curl -fsSL .../install.sh | bash\n```\n\n### Manual setup\n\nIf you don't trust piping scripts to bash (understandable):\n\n```bash\n# 1. create dirs\nmkdir -p ~/.claude\nmkdir -p \"$HOME/.ssh/claude-code\"\n\n# 2. generate SSH keys (for git push/pull inside the container)\nssh-keygen -t ed25519 -C \"claude@claude.ai\" -f \"$HOME/.ssh/claude-code/id_ed25519\" -N \"\"\n# then add the pubkey to GitHub/GitLab/wherever\n\n# 3. pull\ndocker pull psyb0t/claude-code:latest\n# or: docker pull psyb0t/claude-code:latest-minimal\n\n# 4. check install.sh for how the wrapper script works and wire it up yourself\n```\n\n## 🧙 Usage\n\n### Env vars\n\nSet these on your host (e.g. `~/.bashrc`). Apply to all modes — the wrapper forwards them to the container.\n\n| Variable                  | What it does                                                                    | Default              |\n| ------------------------- | ------------------------------------------------------------------------------- | -------------------- |\n| `ANTHROPIC_API_KEY`       | API key for authentication                                                      | _(none)_             |\n| `CLAUDE_CODE_OAUTH_TOKEN` | OAuth token for authentication                                                  | _(none)_             |\n| `CLAUDE_GIT_NAME`         | Git commit name inside the container                                            | _(none)_             |\n| `CLAUDE_GIT_EMAIL`        | Git commit email inside the container                                           | _(none)_             |\n| `CLAUDE_DATA_DIR`         | Custom `.claude` data directory                                                 | `~/.claude`          |\n| `CLAUDE_SSH_DIR`          | Custom SSH key directory                                                        | `~/.ssh/claude-code` |\n| `CLAUDE_INSTALL_DIR`      | Custom install path for the wrapper (install-time only)                         | `/usr/local/bin`     |\n| `CLAUDE_BIN_NAME`         | Custom binary name (install-time only)                                          | `claude`             |\n| `CLAUDE_ENV_*`            | Forward custom env vars (prefix is stripped: `CLAUDE_ENV_FOO=bar` → `FOO=bar`) | _(none)_             |\n| `CLAUDE_MOUNT_*`          | Mount extra volumes (path = same in container, or `src:dest`)                   | _(none)_             |\n| `DEBUG`                   | Enable debug logging with timestamps                                            | _(none)_             |\n\n#### Authentication\n\nEither log in interactively or set up a token:\n\n```bash\n# one-time interactive OAuth setup\nclaude setup-token\n\n# then use the token for programmatic/headless runs\nCLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx claude \"do stuff\"\n\n# or just use an API key\nANTHROPIC_API_KEY=sk-ant-api03-xxx claude \"do stuff\"\n```\n\n#### Forwarding env vars\n\nThe `CLAUDE_ENV_` prefix lets you inject arbitrary env vars into the container. The prefix gets stripped:\n\n```bash\n# inside the container: GITHUB_TOKEN=xxx, MY_VAR=hello\nCLAUDE_ENV_GITHUB_TOKEN=xxx CLAUDE_ENV_MY_VAR=hello claude \"do stuff\"\n```\n\n#### Extra volume mounts\n\nThe `CLAUDE_MOUNT_` prefix mounts additional directories:\n\n```bash\nCLAUDE_MOUNT_DATA=/data claude \"process the data\"                    # same path inside container\nCLAUDE_MOUNT_1=/opt/configs CLAUDE_MOUNT_2=/var/logs claude \"go\"     # mount multiple\nCLAUDE_MOUNT_STUFF=/host/path:/container/path claude \"do stuff\"      # explicit mapping\nCLAUDE_MOUNT_RO=/data:/data:ro claude \"read the data\"                # read-only\n```\n\nIf the value contains `:`, it's used as-is (docker `-v` syntax). Otherwise, same path on both sides.\n\n### Interactive mode\n\n```bash\nclaude\n```\n\nJust like the native CLI but in a container. The container persists between runs — `--continue` resumes your last conversation automatically.\n\n```bash\nclaude --update        # opt in to auto-update on this run\nclaude --no-continue   # start fresh (skip auto-resume of last conversation)\n```\n\n### Utility commands\n\nSome claude commands are passed through directly:\n\n```bash\nclaude --version      # show claude version\nclaude -v             # same thing\nclaude doctor         # health check\nclaude auth           # manage authentication\nclaude setup-token    # interactive OAuth token setup\nclaude stop           # stop the running interactive container for this workspace\nclaude clear-session  # delete session history for this workspace (next run starts fresh)\n```\n\n### Programmatic mode\n\nPass a prompt and get a response. `-p` is added automatically. No TTY, works from scripts, cron, CI, whatever.\n\n```bash\nclaude \"explain this codebase\"                                      # plain text (default)\nclaude \"explain this codebase\" --output-format json                 # JSON response\nclaude \"list all TODOs\" --output-format json-verbose | jq .          # JSON with full tool call history\nclaude \"list all TODOs\" --output-format stream-json | jq .          # streaming NDJSON\nclaude \"explain this codebase\" --model opus                         # pick your model\nclaude \"review this\" --system-prompt \"You are a security auditor\"   # custom system prompt\nclaude \"review this\" --append-system-prompt \"Focus on SQL injection\" # append to default\nclaude \"debug this\" --effort max                                    # go hard\nclaude \"quick question\" --effort low                                # go fast\nclaude \"start over\" --no-continue                                   # fresh session\nclaude \"keep going\" --resume abc123-def456                          # resume specific session\n\n# structured output with JSON schema\nclaude \"extract the author and title\" --output-format json \\\n  --json-schema '{\"type\":\"object\",\"properties\":{\"author\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}},\"required\":[\"author\",\"title\"]}'\n```\n\n`--continue` is passed automatically so successive programmatic runs share conversation context. Use `--no-continue` to start fresh or `--resume \u003csession_id\u003e` to continue a specific conversation.\n\n#### Model selection\n\n| Alias        | Model                                | Best for                                        |\n| ------------ | ------------------------------------ | ----------------------------------------------- |\n| `opus`       | Claude Opus 4.6                      | Complex reasoning, architecture, hard debugging |\n| `sonnet`     | Claude Sonnet 4.6                    | Daily coding, balanced speed/intelligence       |\n| `haiku`      | Claude Haiku 4.5                     | Quick lookups, simple tasks, high volume        |\n| `opusplan`   | Opus (planning) + Sonnet (execution) | Best of both worlds                             |\n| `sonnet[1m]` | Sonnet with 1M context               | Long sessions, huge codebases                   |\n\nYou can also pin specific versions with full model names (`claude-opus-4-6`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001`, etc.). If not specified, defaults based on your account type.\n\n#### Output formats\n\n**`text`** (default) — plain text response.\n\n**`json`** — single JSON object (all keys normalized to camelCase):\n\n```json\n{\n  \"type\": \"result\",\n  \"subtype\": \"success\",\n  \"isError\": false,\n  \"result\": \"the response text\",\n  \"numTurns\": 1,\n  \"durationMs\": 3100,\n  \"totalCostUsd\": 0.156,\n  \"sessionId\": \"...\",\n  \"usage\": { \"inputTokens\": 3, \"outputTokens\": 4, \"cacheReadInputTokens\": 512 },\n  \"modelUsage\": {\n    \"glm-5.1\": {\n      \"inputTokens\": 15702,\n      \"outputTokens\": 28,\n      \"cacheReadInputTokens\": 6836,\n      \"costUsd\": 0.0826,\n      \"contextWindow\": 200000,\n      \"maxOutputTokens\": 32000\n    }\n  },\n  \"permissionDenials\": [],\n  \"iterations\": []\n}\n```\n\n**`json-verbose`** — single JSON object like `json`, but with a `turns` array showing every tool call, tool result, and assistant message. Under the hood it runs `stream-json` and assembles the events into one response. Best of both worlds — one object to parse, full visibility into what Claude did:\n\n```json\n{\n  \"type\": \"result\",\n  \"subtype\": \"success\",\n  \"result\": \"The hostname is mothership.\",\n  \"turns\": [\n    {\n      \"role\": \"assistant\",\n      \"content\": [\n        { \"type\": \"tool_use\", \"id\": \"toolu_abc\", \"name\": \"Bash\", \"input\": { \"command\": \"hostname\" } }\n      ]\n    },\n    {\n      \"role\": \"tool_result\",\n      \"content\": [\n        { \"type\": \"tool_result\", \"tool_use_id\": \"toolu_abc\", \"is_error\": false, \"content\": \"mothership\" }\n      ]\n    },\n    {\n      \"role\": \"assistant\",\n      \"content\": [\n        { \"type\": \"text\", \"text\": \"The hostname is mothership.\" }\n      ]\n    }\n  ],\n  \"system\": { \"session_id\": \"...\", \"model\": \"claude-opus-4-6\", \"cwd\": \"/workspace\", \"tools\": [\"Bash\", \"Read\", ...] },\n  \"numTurns\": 2,\n  \"durationMs\": 10600,\n  \"totalCostUsd\": 0.049,\n  \"sessionId\": \"...\"\n}\n```\n\n**`stream-json`** — NDJSON stream, one event per line. Event types: `system` (init), `assistant` (text/tool_use), `user` (tool results), `rate_limit_event`, `result` (final summary with cost). A typical multi-step run: `system` → (`assistant` → `user`) × N → `result`.\n\n\u003cdetails\u003e\n\u003csummary\u003eFull stream-json event examples\u003c/summary\u003e\n\n**`system`** — session init:\n\n```json\n{\n  \"type\": \"system\",\n  \"subtype\": \"init\",\n  \"cwd\": \"/your/project\",\n  \"session_id\": \"...\",\n  \"tools\": [\"Bash\", \"Read\", \"Write\", \"Glob\", \"Grep\"],\n  \"model\": \"claude-opus-4-6\",\n  \"permissionMode\": \"bypassPermissions\"\n}\n```\n\n**`assistant`** — Claude's response (text or tool_use):\n\n```json\n{\n  \"type\": \"assistant\",\n  \"message\": {\n    \"model\": \"claude-opus-4-6\",\n    \"role\": \"assistant\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"I'll install cowsay first.\" }],\n    \"usage\": { \"input_tokens\": 3, \"output_tokens\": 2 }\n  }\n}\n```\n\n```json\n{\n  \"type\": \"assistant\",\n  \"message\": {\n    \"content\": [\n      {\n        \"type\": \"tool_use\",\n        \"id\": \"toolu_abc123\",\n        \"name\": \"Bash\",\n        \"input\": { \"command\": \"sudo apt-get install -y cowsay\" }\n      }\n    ]\n  }\n}\n```\n\n**`user`** — tool execution result:\n\n```json\n{\n  \"type\": \"user\",\n  \"message\": {\n    \"content\": [\n      {\n        \"tool_use_id\": \"toolu_abc123\",\n        \"type\": \"tool_result\",\n        \"content\": \"Setting up cowsay (3.03+dfsg2-8) ...\",\n        \"is_error\": false\n      }\n    ]\n  }\n}\n```\n\n**`result`** — final summary:\n\n```json\n{\n  \"type\": \"result\",\n  \"subtype\": \"success\",\n  \"is_error\": false,\n  \"num_turns\": 10,\n  \"duration_ms\": 60360,\n  \"total_cost_usd\": 0.203,\n  \"result\": \"Here's what I did:\\n1. Installed cowsay...\"\n}\n```\n\n\u003c/details\u003e\n\n### API mode\n\nTurn the container into an HTTP API server. Useful for integrating Claude into your services.\n\n```yaml\n# docker-compose.yml\nservices:\n  claude:\n    image: psyb0t/claude-code:latest\n    ports:\n      - \"8080:8080\"\n    environment:\n      - CLAUDE_MODE_API=1\n      - CLAUDE_MODE_API_TOKEN=your-secret-token\n      - CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx\n    volumes:\n      - ~/.claude:/home/claude/.claude\n      - /your/projects:/workspaces\n      - /var/run/docker.sock:/var/run/docker.sock\n```\n\n#### Env vars\n\n| Variable                | What it does                                                             | Default  |\n| ----------------------- | ------------------------------------------------------------------------ | -------- |\n| `CLAUDE_MODE_API`       | Set to `1` to run as HTTP API server instead of interactive/programmatic | _(none)_ |\n| `CLAUDE_MODE_API_PORT`  | Port for the API server                                                  | `8080`   |\n| `CLAUDE_MODE_API_TOKEN` | Bearer token for API auth (optional)                                     | _(none)_ |\n\n#### Endpoints\n\n**`POST /run`** — send a prompt, get JSON back:\n\n```bash\ncurl -X POST http://localhost:8080/run \\\n  -H \"Authorization: Bearer your-secret-token\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt\": \"what does this repo do\", \"workspace\": \"myproject\"}'\n```\n\n| Field                  | Type   | Description                                                              | Default         |\n| ---------------------- | ------ | ------------------------------------------------------------------------ | --------------- |\n| `prompt`               | string | The prompt to send                                                       | required        |\n| `workspace`            | string | Subpath under `/workspaces` (e.g. `myproject` → `/workspaces/myproject`) | `/workspaces`   |\n| `model`                | string | Model to use (same aliases as CLI)                                       | account default |\n| `systemPrompt`         | string | Replace the default system prompt                                        | _(none)_        |\n| `appendSystemPrompt`   | string | Append to the default system prompt                                      | _(none)_        |\n| `jsonSchema`           | string | JSON Schema for structured output                                        | _(none)_        |\n| `effort`               | string | Reasoning effort (`low`, `medium`, `high`, `max`)                        | _(none)_        |\n| `outputFormat`         | string | Response format: `json` or `json-verbose` (includes tool call history)   | `json`          |\n| `noContinue`           | bool   | Start fresh (don't continue previous conversation)                       | `false`         |\n| `resume`               | string | Resume a specific session by ID                                          | _(none)_        |\n| `fireAndForget`        | bool   | Don't kill the process if the client disconnects                         | `false`         |\n\nReturns `application/json`. Default format is `json` (same as `--output-format json`). Use `json-verbose` to get a `turns` array with every tool call and result (see [output formats](#output-formats) above). Returns **409** if the workspace is already busy.\n\n**`GET /files/{path}`** — list directory or download file:\n\n```bash\ncurl \"http://localhost:8080/files\" -H \"Authorization: Bearer token\"                    # list root\ncurl \"http://localhost:8080/files/myproject/src\" -H \"Authorization: Bearer token\"      # list subdir\ncurl \"http://localhost:8080/files/myproject/src/main.py\" -H \"Authorization: Bearer token\"  # download\n```\n\n**`PUT /files/{path}`** — upload a file (auto-creates parent dirs):\n\n```bash\ncurl -X PUT \"http://localhost:8080/files/myproject/src/main.py\" \\\n  -H \"Authorization: Bearer token\" --data-binary @main.py\n```\n\n**`DELETE /files/{path}`** — delete a file:\n\n```bash\ncurl -X DELETE \"http://localhost:8080/files/myproject/src/old.py\" -H \"Authorization: Bearer token\"\n```\n\n**`GET /health`** — health check (no auth).\n**`GET /status`** — which workspaces are busy.\n**`POST /run/cancel?workspace=X`** — kill a running claude process.\n\nAll file paths are relative to `/workspaces`. Path traversal outside root is blocked.\n\n### Telegram mode\n\nTalk to Claude from Telegram. Each chat gets its own workspace and settings. Send text, files, photos, videos, voice messages. Run shell commands. Get files back.\n\n#### Setup\n\n1. **Create a bot** — talk to [@BotFather](https://t.me/BotFather), run `/newbot`, save the token\n2. **Get your chat ID** — message [@userinfobot](https://t.me/userinfobot), it replies with your user ID (which is also your DM chat ID). Group chat IDs are negative.\n3. **Create `~/.claude/telegram.yml`:**\n\n```yaml\n# which chats the bot responds in\n# DM user IDs (positive) and/or group chat IDs (negative)\n# empty = no restriction (dangerous!)\nallowed_chats:\n  - 123456789 # your DM\n  - -987654321 # a group\n\n# defaults for chats not explicitly configured\ndefault:\n  model: sonnet\n  effort: high\n  continue: true\n\n# per-chat overrides\nchats:\n  123456789:\n    workspace: my-project\n    model: opus\n    effort: max\n    system_prompt: \"You are a senior engineer\"\n\n  -987654321:\n    workspace: team-stuff\n    model: sonnet\n    effort: medium\n    continue: false\n    append_system_prompt: \"Keep responses short\"\n    # only these users can talk in this group\n    allowed_users:\n      - 123456789\n      - 111222333\n```\n\nPer-chat options: `workspace`, `model`, `effort`, `continue`, `system_prompt`, `append_system_prompt`, `max_budget_usd`, `allowed_users`.\n\n4. **Run it:**\n\n```yaml\n# docker-compose.yml\nservices:\n  claude-telegram:\n    image: psyb0t/claude-code:latest\n    environment:\n      - CLAUDE_MODE_TELEGRAM=1\n      - CLAUDE_TELEGRAM_BOT_TOKEN=123456:ABC-DEF\n      - CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx\n    volumes:\n      - ~/.claude:/home/claude/.claude\n      - ~/telegram-workspaces:/workspaces\n      - /var/run/docker.sock:/var/run/docker.sock\n```\n\n#### Env vars\n\n| Variable                    | What it does                                        | Default                             |\n| --------------------------- | --------------------------------------------------- | ----------------------------------- |\n| `CLAUDE_MODE_TELEGRAM`      | Set to `1` to run as Telegram bot                   | _(none)_                            |\n| `CLAUDE_TELEGRAM_BOT_TOKEN` | Bot token from [@BotFather](https://t.me/BotFather) | _(none)_                            |\n| `CLAUDE_TELEGRAM_CONFIG`    | Path to the YAML config file inside the container   | `/home/claude/.claude/telegram.yml` |\n\n#### Bot commands\n\n| Command                       | What it does                                              |\n| ----------------------------- | --------------------------------------------------------- |\n| any text message              | Sent to Claude as a prompt                                |\n| send a file/photo/video/voice | Saved to workspace; caption becomes the prompt if present |\n| `/bash \u003ccommand\u003e`             | Run a shell command in the chat's workspace               |\n| `/fetch \u003cpath\u003e`               | Get a file from the workspace as a Telegram attachment    |\n| `/cancel`                     | Kill the running Claude process for this chat             |\n| `/status`                     | Show which chats are busy                                 |\n| `/config`                     | Show this chat's config                                   |\n| `/reload`                     | Hot-reload the YAML config without restarting             |\n\nClaude can send files back by putting `[SEND_FILE: relative/path]` in its response — images get sent as photos, videos as videos, everything else as documents. Long responses are automatically split across multiple messages (4096 char Telegram limit).\n\n## 🔧 Customization\n\n### Custom scripts (`~/.claude/bin`)\n\nDrop executables into `~/.claude/bin/` and they're in PATH inside every container session:\n\n```bash\nmkdir -p ~/.claude/bin\necho '#!/bin/bash\necho \"hello from custom script\"' \u003e ~/.claude/bin/my-tool\nchmod +x ~/.claude/bin/my-tool\n# now available inside the container as `my-tool`\n```\n\n### Init hooks (`~/.claude/init.d`)\n\nScripts in `~/.claude/init.d/*.sh` run once on first container create (as root, before dropping to the claude user). They don't re-run on subsequent `docker start` — only on fresh containers.\n\n```bash\nmkdir -p ~/.claude/init.d\ncat \u003e ~/.claude/init.d/setup.sh \u003c\u003c 'EOF'\n#!/bin/bash\napt-get update \u0026\u0026 apt-get install -y some-package\npip install some-library\nEOF\nchmod +x ~/.claude/init.d/setup.sh\n```\n\nGreat for pre-installing tools on the minimal image so Claude doesn't waste your tokens figuring out `apt-get`.\n\n## 🦴 Gotchas\n\n- **`--dangerously-skip-permissions`** is always on. Claude has full access. That's the point.\n- **SSH keys** are mounted for git operations. Don't share your container with strangers.\n- **Host paths are preserved** — your project at `/home/you/project` stays at `/home/you/project` inside the container. This means docker volume mounts from inside Claude work correctly against host paths.\n- **UID/GID matching** — the container user's UID/GID auto-matches the host directory owner. File permissions just work.\n- **Docker-in-Docker** — the Docker socket is mounted. Claude can spawn containers within containers. It's fine. Probably.\n- **Two containers per workspace** — `claude-_path` (interactive, TTY) and `claude-_path_prog` (programmatic, no TTY). They share the same mounted data.\n- **`~/.claude/bin`** is in PATH. Custom scripts are available everywhere.\n- **Telegram config is required** — the bot won't start without `telegram.yml`. No config = no bot. This is intentional so you don't accidentally expose Claude to the world.\n\n## 📜 License\n\n[WTFPL](http://www.wtfpl.net/) — do what the fuck you want to.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsyb0t%2Fdocker-claude-code","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpsyb0t%2Fdocker-claude-code","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsyb0t%2Fdocker-claude-code/lists"}