Let op, dit bericht is achterhaald. Sinds v2.0.0 is claudebox een dun kindimage van aicodebox, de agent-agnostische basis die inmiddels elk hieronder beschreven oppervlak bezit. De API, het OpenAI-compatibele endpoint, de MCP-server, de Telegram-bot en de cron-scheduler leven allemaal in de basis en worden gedeeld met pibox en codexbox. claudebox zelf is nu de Claude Code-adapter plus een wrapper aan de hostkant. Lees eerst het aicodebox-bericht, daar zit de architectuur echt. Wat hieronder staat werkt nog steeds en documenteert claudebox’ eigen oppervlak, maar het echte verhaal is nu de basis.
MCP is een eigen modus, geen hoekje van de API
Dat mag expliciet gezegd worden, want de documentatie beschreef het lange tijd als een endpoint binnen de API-modus en dat doet het tekort. MCP draait op twee manieren:
Inside API mode mounted at /mcp on the API server, no extra process port 8080
Standalone its own uvicorn process, spawned as a sidecar port 8081De losstaande variant leeft samen met elke andere modus in plaats van ze te vervangen, en hij pakt zijn eigen token, CLAUDEBOX_MCP_MODE_TOKEN, dat niet terugvalt op het API-token. Laat het leeg en het MCP-oppervlak heeft helemaal geen auth, wat een andere en veel slechtere default is dan «erft wat de API gebruikt». Zet hem bewust.
Vijf tools: run_prompt plus vier bestandstools, list_files, read_file, write_file, delete_file, die elk hun path onder de workspace-wortel oplossen en alles weigeren dat eruit klimt. Neem die liever dan een lading in prompt te proppen: de agent kan de workspace zelf lezen, dus «schrijf de invoer naar een bestand en zeg welk bestand» verslaat een prompt van 50 KB.
Ik gebruik Claude Code voor alles. Code schrijven, shit debuggen, infrastructuur uitrollen, repo’s beheren, artikelen voor dit blog schrijven en zelfs ter plekke browsersessies automatiseren. Het is de ruggengraat geworden van hoe ik werk. Maar een AI-agent volledige toegang tot je systeem geven is verdomd eng, niet omdat Claude kwaadwillend is, maar omdat hij draait met
--permission-mode bypassPermissions en de macht heeft om te doen wat hij wil. Eén verkeerd commando en je host is naar de klote. Tegenwoordig is de container waarin hij draait niet eens meer claudebox-specifiek, het is aicodebox met een Claude-vormige adapter erop geschroefd.Het voor de hand liggende antwoord is: stop het in een container. Maar dat écht goed doen is een heel ander probleem. Ik heb claudebox gebouwd om het op te lossen. Wat begon als een simpele gecontaineriseerde Claude Code-wrapper is uitgegroeid tot zeven verschillende manieren om Claude te draaien, elk daadwerkelijk nuttig, geen enkele opvulling.
De Naamswijziging
Dit heette vroeger docker-claude-code, image psyb0t/claude-code, binary claude. Het is nu claudebox, image psyb0t/claudebox, binary claudebox. De SSH-sleutels zijn verhuisd van ~/.ssh/claude-code naar ~/.ssh/claudebox.
Als je upgradet: deïnstalleer het oude binary, haal het nieuwe image op, draai het installatiescript opnieuw. Je configmap ~/.claude en je sessiegeschiedenis overleven de naamswijziging ongemoeid.
v2.0.0, gerebased op aicodebox
De grotere verandering kwam na de naamswijziging. claudebox is nu een dun kindimage van psyb0t/aicodebox, een gedeelde, agent-agnostische basis die elk modusoppervlak afhandelt. Hetzelfde patroon als psyb0t/pibox en psyb0t/codexbox. De API-server, de Telegram-bot, de cron-scheduler en het MCP-endpoint leven nu allemaal in de basis; claudebox levert een adapter die weet hoe hij specifiek met Claude Code moet praten. Fixes in de basis bereiken elk kindimage gratis.
Dat is een volledige architecturale rebase, dus er is van alles gesneuveld. Alles is via alias of symlink vooruit geleid zodat bestaande configuraties blijven werken, maar de canonieke namen zijn veranderd:
- Endpoints:
POST /run/cancel?runId=…werdDELETE /run/{run_id}.GET /healthwerdGET /healthz. - MCP-tool:
claude_runwerdrun_prompt. Werk je MCP-clientconfiguraties bij. - Omgevingsvariabelen:
CLAUDEBOX_MODE_APIwerdCLAUDEBOX_API_MODE,CLAUDEBOX_MODE_CRON_FILEwerdCLAUDEBOX_CRON_MODE_FILE, en zo de hele lijst door. De entrypoint aliast de oude namen vooruit. - Paden: de workspace-wortel
/workspaceswerd/workspace(enkelvoud). De containerhome/home/claude/.claudewerd/home/aicode/.aicodebox. Compatibiliteitssymlinks houden oude bind mounts oplosbaar. - Cron: alleen nog croniter met zes velden. Zet een
0vóór elk schema met vijf velden dat je gebruikte. - Weg: het Telegram-commando
/bash. Bouw het clientkant na als je het nodig hebt. - Full-variant:
make build-fullstapelt nu bovenop het minimale image in plaats van een apart multi-stage target te zijn.
De adapter komt met 32 pytest-unittests, plus 9 gecontaineriseerde smoke tests die tegen een nep-claude-binary draaien, healthz, de OpenAI-modellenlijst, de init.d-markers, de compatibiliteitssymlinks, het env-aliasen, de always-skills injectie, de extra argumenten en de standaard permissiemodus.
Zeven Interfaces, Eén Container
claudebox is niet langer alleen een wrapper. Het zijn zeven verschillende interfaces naar Claude Code die binnen Docker draaien:
- Interactieve CLI: persistente container, sessiehervatting, de oorspronkelijke modus
- Programmatische CLI: niet-interactief, werkt vanuit scripts en CI, met een eigen toegewijde container
- HTTP-API-server: REST-API met workspacebeheer, bestandsoperaties, synchrone en asynchrone runs
- OpenAI-compatibel endpoint: directe vervanger op
/openai/v1/chat/completionsmet streaming - MCP-server: vijf tools die Claude vanuit andere agents kan gebruiken via het Model Context Protocol
- Telegram-bot: workspaces per chat, bestanden delen, shellcommando’s vanaf je telefoon
- Cron-scheduler: in YAML gedefinieerde geplande jobs met resolutie onder de minuut, historie per job en optionele Telegram-meldingen
Installatie
curl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claudebox/master/install.sh | bashDit genereert SSH-sleutels in ~/.ssh/claudebox, haalt het image op (altijd, het installatieprogramma opnieuw draaien is de ondersteunde manier om te upgraden) en zet het claudebox-binary neer op /usr/local/bin/claudebox. Moet je omgevingsvariabelen aan het installatieprogramma meegeven, exporteer ze dan eerst op een aparte regel, want VAR=x curl ... | bash door een pipe geeft de variabele niet door aan het script:
export CLAUDEBOX_INSTALL_DIR=/usr/local/bin
curl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claudebox/master/install.sh | bashDaarna:
claudeboxDe eerste run vraagt om authenticatie. Daarna werkt het gewoon. De wrapper regelt de hele levenscyclus van de container, hij maakt een nieuwe aan als er geen bestaat voor de huidige map, en herstart en hangt zich er weer aan vast als er al een is.
Imagevarianten
Full: psyb0t/claudebox:latest-full
Ubuntu-basis volgeladen met alles wat een ontwikkelaar echt nodig heeft: Go met de volledige toolchain (golangci-lint, gopls, delve), Python 3.14 via pyenv (flake8, black, mypy, pyright, vulture, pytest, poetry), Node.js 24 LTS met het gebruikelijke ecosysteem, C/C++-toolchain, Docker CE met Compose, Terraform, kubectl, helm, GitHub CLI, databaseclients voor SQLite/PostgreSQL/MySQL/Redis, en een berg hulpprogramma’s (jq, ripgrep, fd-find, bat, shellcheck, shfmt, httpie). De container genereert automatisch een CLAUDE.md met elk beschikbaar gereedschap erin, zodat Claude weet waarmee hij het moet doen.
Minimaal: psyb0t/claudebox:latest
Alleen het hoognodige bovenop de aicodebox-basis: Ubuntu 24.04, git/curl/wget/jq, Node.js 24 LTS met npm, Python 3.14 met uv, Docker CE. Kleiner image, snellere pull. Claude heeft sudo zonder wachtwoord, dus installeert onderweg wat hij nodig heeft. Let op dat de naamgeving in v2 is omgedraaid: latest is nu het minimale image (dat was eerst de volle), latest-full is de build met toolchain, en de oude opt-in CLAUDEBOX_MINIMAL=1 doet niets meer omdat minimaal de standaard is. CLAUDEBOX_FULL=1 is hoe je de andere kant op kiest, en installeren met die variabele gezet bakt die keuze in de wrapper zodat hij blijft plakken. Gebruik init hooks om je setup voor te bakken, zodat je niet bij elke verse container op pakketinstallaties zit te wachten.
Claude Code zelf zit in geen van beide images meer, en de reden is de licentie. De CLI van Anthropic is propriëtair zonder herdistributierecht, dus een image publiceren met dat binary erin gebakken zou neerkomen op andermans software uitleveren. In plaats daarvan draagt het image de vastgepinde versie in CLAUDEBOX_CLAUDE_VERSION en draait de entrypoint npm install -g @anthropic-ai/claude-code@<version> de eerste keer dat een verse container start. Niets van Anthropic reist mee in de gepubliceerde lagen; elke container haalt het zelf van npm. De prijs is dat de eerste start van een nieuwe container netwerk en een paar seconden extra kost. Warme herstarts slaan het volledig over. Wil je een andere versie, zet CLAUDEBOX_CLAUDE_VERSION bij de docker run.
De dozen kunnen elkaar nu starten
Installeer claudebox, codexbox en pibox in dezelfde map en elke wrapper mount de andere twee alleen-lezen op /usr/local/bin/<name>. Dat betekent dat Claude, vanuit zijn eigen container, codexbox of pibox kan aanroepen en een andere agent een stuk werk kan laten doen. Het voor de hand liggende gebruik is een tweede mening over een diff zonder de sessie te verlaten.
Het deel dat echt nadenken kostte is de context. Een geneste run moet weten waar dingen op de host staan, niet binnen de container die toevallig aanroept, dus daar is een geversioneerd blok voor: AICODEBOX_LAUNCH_CONTEXT_VERSION=1 plus AICODEBOX_HOST_HOME, AICODEBOX_HOST_WORKSPACE, AICODEBOX_HOST_WRAPPER_DIR, en per agent AICODEBOX_HOST_CLAUDE_HOME / CODEX_HOME / PI_HOME met een bijpassende *_WRAPPER voor elk. Geversioneerd omdat de vorm gaat veranderen en een geneste start dat moet kunnen merken.
Geneste runs zijn ook bewust wegwerpbaar: ze slaan de auth-bestandsschrijfacties over die een run op het hoogste niveau wel doet, dus een kind-agent kan niet stilletjes de credentials herschrijven van de doos die hem aanriep.
Daarnaast sturen AICODEBOX_ENV_* en AICODEBOX_MOUNT_* omgeving en mounts door naar elke doos, naast de bestaande CLAUDEBOX_ENV_* en CLAUDEBOX_MOUNT_* die per doos gelden. En AICODEBOX_MANAGED_INSTALL=1 is een niet-interactieve installatie voor provisioningscripts die, belangrijk, weigert een al bestaande SSH-sleutel te overschrijven. CLAUDEBOX_INSTALL_DIR en CLAUDEBOX_BIN_NAME bepalen waar het landt en hoe het heet.
Eén toeleveringsketendetail dat het vermelden waard is: het installatieprogramma haalt wrapper.sh nu van de bijbehorende onveranderlijke release-tag in plaats van uit master, dus een vastgepinde versie installeren geeft je daadwerkelijk de wrapper van díe versie.
Interactieve Modus
Draai claudebox vanuit welke map dan ook en je krijgt een levende sessie. De container blijft tussen runs bestaan, de sessie gaat verder waar je gebleven was. Elke workspace krijgt zijn eigen container, vernoemd naar het mappad.
Hulpcommando’s:
claudebox --version # show version
claudebox doctor # health check
claudebox auth # manage authentication
claudebox setup-token # interactive OAuth token setup
claudebox stop # stop the running container for this workspace
claudebox clear-session # wipe session history, next run starts fresh
claudebox --update # pull the latest image and reinstallSessiecontinuïteit. claudebox draait Claude met --continue, dus hij hervat het laatste gesprek uit de huidige map. Kill de terminal, kom de volgende dag terug, start hem opnieuw, en Claude pakt precies op waar hij gebleven was. Geen sessie, geen probleem, hij begint fris.
Geheugenlimiet. Containers zitten standaard op maximaal 10g. Overschrijf dat bij het starten met CLAUDEBOX_MAX_MEM=16g claudebox (het oude CLAUDE_MAX_MEM werkt nog).
UID/GID-afstemming. De entrypoint detecteert de eigenaar van de workspace en past de gebruiker van de container daarop aan. Bestanden die binnen de container worden gemaakt hebben op de host de juiste eigenaar. Geen chown -R-gezeik.
Programmatische Modus
Geef een prompt mee en claudebox draait niet-interactief. Het gebruikt een toegewijde _prog-container per workspace, los van de interactieve, zonder TTY, werkt vanuit scripts, vanuit cron, vanuit andere tools:
# basic run
claudebox "explain this codebase"
# pick a model
claudebox "explain this codebase" --model sonnet
claudebox "audit this" --model opus
# output formats
claudebox "list all TODOs" --output-format json
claudebox "list all TODOs" --output-format json-verbose | jq .
claudebox "list all TODOs" --output-format stream-json | jq .
# reasoning effort
claudebox "debug this complex issue" --effort high
claudebox "quick question" --effort low
# custom system prompt
claudebox "review this" --system-prompt "You are a security auditor"
claudebox "review this" --append-system-prompt "Focus on SQL injection"
# structured output
claudebox "extract author and title" --output-format json
--json-schema '{"type":"object","properties":{"author":{"type":"string"},"title":{"type":"string"}}}'
# session control
claudebox "start over" --no-continue
claudebox "keep going" --resume abc123-def456Modelaliassen: opus (Opus 4.6), sonnet (Sonnet 4.6), haiku (Haiku 4.5), opusplan (Opus voor het plannen plus Sonnet voor de uitvoering), sonnet[1m] (Sonnet met een contextvenster van 1M). Of geef een volledige modelnaam mee om een specifieke versie vast te pinnen.
Uitvoerformaten: text (standaard), json (één resultaatobject met kosten en tokenuitsplitsing), json-verbose (hetzelfde als json maar met een turns-array die elke toolaanroep, elk toolresultaat en elk assistentbericht toont, volledig zicht op wat Claude deed), stream-json (NDJSON, één event per regel, systeeminit, assistentantwoorden, toolgebruik, toolresultaten, rate limit-events, eindresultaat).
API-Modus
Zet CLAUDEBOX_API_MODE=1 om de container als HTTP-API-server te draaien. Hang hem in een docker-compose stack en andere diensten kunnen via HTTP met Claude praten:
services:
claudebox:
image: psyb0t/claudebox:latest
ports:
- "8080:8080"
environment:
- CLAUDEBOX_API_MODE=1
- CLAUDEBOX_API_MODE_TOKEN=your-secret-token
- CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx
volumes:
- ~/.claude:/home/aicode/.aicodebox
- /your/projects:/workspace
- /var/run/docker.sock:/var/run/docker.sockEndpoints:
- POST /run: stuur een prompt, krijg een resultaat. Velden:
prompt,workspace,model,system_prompt,append_system_prompt,json_schema,effort,no_continue,resume. Geeft 409 als de workspace al aan het verwerken is. - POST /run met
"async": true: geeft meteen eenrunIdterug. Pol GET /run/result?runId=X tot het klaar is. Afvuren en vergeten. - DELETE /run/{run_id}: doodt een lopend proces (was vóór v2
POST /run/cancel?runId=…) - GET /files/{path}: map opsommen of bestand downloaden
- PUT /files/{path}: bestand uploaden (bovenliggende mappen worden automatisch aangemaakt)
- DELETE /files/{path}: bestand verwijderen
- GET /healthz: health check, zonder auth (was vóór v2
GET /health) - GET /status: toont welke workspaces op dit moment bezet zijn
Alle paden zijn relatief aan /workspace. Auth via bearer token in de Authorization-header, zet CLAUDEBOX_API_MODE_TOKEN om het aan te zetten. Het bijhouden van bezette workspaces geeft 409 Conflict terug zodat je niet per ongeluk overlappende runs op dezelfde workspace opstapelt.
Gestructureerde uitvoer en de volledige registratie zijn nu losse knoppen
Er zijn twee dingen veranderd in de API, twee dingen die eerst in elkaar verstrikt zaten. jsonSchema loopt nu via Claude Codes eigen --json-schema-flag in plaats van er bovenop geschroefd te zijn, en het bewaren van events is zijn eigen knop: eventMode: "full" zet het streamen van deelberichten, subagenttekst en hook-events aan en geeft je elke native stream-json-registratie terug zonder beurten samen te vouwen of toolresultaten af te kappen. Het hele transcript willen vereist niet langer doen alsof je een schema wilde, en om een schema vragen duwt je niet langer de volledige brandslang op. Je kiest ze allebei apart.
OpenAI-Compatibel Endpoint
POST /openai/v1/chat/completions, een OpenAI-adapter als directe vervanger die verzoeken routeert naar Claude Code in de container. Werkt met alles dat de OpenAI-API spreekt: LiteLLM, Open WebUI, eigen clients, wat dan ook.
curl https://ciprian.51k.eu80/openai/v1/chat/completions
-H "Authorization: Bearer your-secret-token"
-H "Content-Type: application/json"
-d '{
"model": "sonnet",
"messages": [{"role": "user", "content": "explain this codebase"}],
"stream": true
}'Streaming werkt via Server-Sent Events. Gesprekken over meerdere beurten werken, je geeft de volledige berichtgeschiedenis mee en Claude houdt de context vast. Multimodaal werkt ook, je stuurt base64-beeldinhoud in het bericht en Claude kan het zien.
Eigen headers om het gedrag te sturen:
X-Claude-Workspace: in welke workspace er gedraaid wordtX-Claude-Continue: of de vorige sessie wordt voortgezetX-Claude-Append-System-Prompt: plakt extra instructies aan de systeemprompt
Voor LiteLLM richt je het op http://your-host:8080/openai/v1 als eigen OpenAI-provider en het werkt zonder enige speciale configuratie.
Hardeningsronde. De OpenAI-adapter heeft een echte testsuite en een beveiligingsaudit gekregen: een SSRF-wacht weigert verzoeken die interne URL’s door workspacevelden proberen te smokkelen, finish_reason-waarden worden netjes gemapt zodat OpenAI-clients stop/length/tool_calls zien in plaats van rommel, gesprekken over meerdere beurten blijven bij vervolgvragen correct op dezelfde workspace, en niet-ondersteunde verzoekvelden geven nu 400 in plaats van stil te worden weggegooid. Onderbouwd door 24 unittests en 3 integratietests zodat het oppervlak eerlijk blijft terwijl het groeit.
MCP-Server
Zet de MCP-server aan op /mcp/ om andere agents en tools via het Model Context Protocol in jouw Claude-container te laten bellen. Vijf tools worden blootgesteld:
- run_prompt: draait een prompt in een workspace en geeft het resultaat terug (in v2.0.0 hernoemd vanaf
claude_run) - list_files: somt de bestanden in een workspacemap op
- read_file: leest een bestand uit een workspace
- write_file: schrijft een bestand naar een workspace
- delete_file: verwijdert een bestand uit een workspace
Dat betekent dat andere Claude-instanties, eigen agents of elke MCP-compatibele client jouw claudebox-instantie als tool kunnen gebruiken, en werk kunnen delegeren aan een verse Claude-sessie met volledige bestandstoegang.
Telegram-Modus
Zet CLAUDEBOX_TELEGRAM_MODE=1 en je krijgt een Telegram-bot die met Claude praat. Elke chat krijgt zijn eigen workspace en instellingen. Stuur tekst, bestanden, foto’s, video’s, spraakberichten. Haal bestanden terug met /fetch.
De configuratie leeft in een YAML-bestand met model, effort, workspace, systeemprompt en budget per chat:
# ~/.claude/telegram.yml
allowed_chats:
- 123456789
- -987654321
default:
model: sonnet
effort: high
continue: true
chats:
123456789:
workspace: my-project
model: opus
effort: max
system_prompt: "You are a senior engineer"
max_budget_usd: 5.00
-987654321:
workspace: team-stuff
model: sonnet
allowed_users:
- 123456789services:
claudebox-telegram:
image: psyb0t/claudebox:latest
environment:
- CLAUDEBOX_TELEGRAM_MODE=1
- CLAUDEBOX_TELEGRAM_MODE_TOKEN=123456:ABC-DEF
- CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx
volumes:
- ~/.claude:/home/aicode/.aicodebox
- ~/telegram-workspaces:/workspace
- /var/run/docker.sock:/var/run/docker.sockBotcommando’s:
- willekeurige tekst → als prompt naar Claude gestuurd
- stuur een bestand/foto/video/spraakbericht → opgeslagen in de workspace, het bijschrift wordt de prompt
/model [name]: toont het huidige model met selecteerbare knoppen, of zet het direct:haiku,sonnet,opus,opusplan,reset/effort [level]: toont of selecteert de effort:low,medium,high,xhigh,max,reset/system_prompt [text]: toont, zet of reset de systeempromptoverschrijving voor deze chat/append_system_prompt [text]: hetzelfde voor de aangehechte systeemprompt/fetch <path>: stuurt een workspacebestand terug als Telegram-bijlage/cancel: doodt het lopende Claude-proces voor deze chat/status: toont welke chats nu lopende processen hebben/config: toont de huidige configuratie van deze chat/reload: herlaadt de YAML-config warm zonder de container te herstarten
Claude kan bestanden terugduwen door [SEND_FILE: path] in zijn antwoord te zetten. Afbeeldingen komen binnen als foto’s, video’s als video’s, al het andere als documenten. Lange antwoorden worden automatisch over meerdere berichten verdeeld.
Markdown-rendering. De bot vertaalt Claudes markdown-uitvoer vóór verzending naar de HTML-variant van Telegram, en vet, cursief, inline code, codeblokken, citaten, koppen, lijsten en links renderen allemaal native in de chat. Geen rauwe **sterretjes en backticks meer die je berichten vervuilen. NUL-bytes in tooluitvoer worden gemapt naar een plaatsvervanger uit het private-use gebied zodat ze de heen-en-terugreis naar Telegram overleven zonder het bericht af te kappen.
Cron-Modus
Zet CLAUDEBOX_CRON_MODE=1 en richt CLAUDEBOX_CRON_MODE_FILE op een YAML-bestand om geplande Claude-jobs te draaien. Sinds v2.0.0 alleen nog croniter met zes velden, */30 * * * * * vuurt elke 30 seconden. Configuraties van vóór v2 met invoer van vijf velden hebben een 0 ervoor nodig.
model: haiku # default model for all jobs
append_system_prompt: |
The current date and time is {system_datetime}.
telegram_chat_id: -1001234567890 # optional: post results to this Telegram chat
jobs:
- name: hourly_check
schedule: "0 0 * * * *"
instruction: |
Look at the git log for the last hour. Summarize commits.
- name: every_30_seconds
schedule: "*/30 * * * * *" # sub-minute
model: sonnet
instruction: Write the current UTC timestamp to ./status.txt.
- name: nightly_cleanup
schedule: "0 0 3 * * *"
model: opus
system_prompt: |
You are a cleanup agent. Current time: {system_datetime}.
instruction: |
Find files older than 7 days under ./tmp and delete them.Templatevariabelen beschikbaar in instruction, system_prompt en append_system_prompt: {system_datetime} (huidige UTC-datum en -tijd) en {job_name} (het naamveld van de job). De model, system_prompt en append_system_prompt per job overschrijven de standaarden uit de wortel.
services:
claudebox-cron:
image: psyb0t/claudebox:latest
environment:
- CLAUDEBOX_CRON_MODE=1
- CLAUDEBOX_CRON_MODE_FILE=/home/aicode/.aicodebox/cron.yaml
- CLAUDEBOX_WORKSPACE=/workspace
- CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx
volumes:
- ./cron.yaml:/home/aicode/.aicodebox/cron.yaml:ro
- ./workspace:/workspace
- ~/.claude:/home/aicode/.aicodebox
- /var/run/docker.sock:/var/run/docker.sockDe scheduler draait op de voorgrond, en docker logs toont elke tik. Draait een job nog als de volgende tik afgaat, dan wordt die tik overgeslagen. De jobhistorie stroomt naar ~/.claude/cron/history/<workspace-slug>/<timestamp>-<job-name>/ als activity.jsonl, stderr.log en meta.json.
Zet telegram_chat_id (in de wortel of per job) plus CLAUDEBOX_TELEGRAM_MODE_TOKEN om Claudes resultaat na elke job op Telegram te krijgen. De Telegram-bot hoeft niet te draaien, de cron gebruikt het token rechtstreeks.
Redeneerinspanning per job. Zet effort in de wortel als standaard en overschrijf per job, dezelfde schaal als de CLI (low, medium, high, xhigh, max). Goedkope modellen voor goedkope jobs, maximale inspanning voor de vervelende nachtelijke audit.
Historie van eerdere runs, automatisch geïnjecteerd. Elke cron-tik plakt er nu een systeemblok aan dat Claude vertelt waar zijn vorige runs staan, de historiewortel, de historiemap van de workspace en de historiemap per job (~/.claude/cron/history/<workspace-slug>/*-<job-name>/). Claude leest ze niet gretig; hij krijgt de paden en besluit of hij Glob of Read doet wanneer de job daadwerkelijk om trendanalyse of regressiedetectie vraagt. De allereerste run slaat de hint over omdat er nog niets is. Dit ontsluit «vergelijk met vorige week», «is deze metriek geregresseerd», «wat is er veranderd sinds de run van gisteren», zonder dat je daar per job iets voor bedraadt. Gecombineerd met telegram_chat_id krijg je een dagelijkse digest-agent die echt weet wat hij gisteren zei.
Aanpassen
Altijd Actieve Skills
Zet SKILL.md-bestanden in ~/.claude/.always-skills/ en ze worden automatisch geïnjecteerd in elke claudebox-aanroep, interactief, programmatisch, API, Telegram, cron, allemaal. Blijvende context die Claude elke sessie in volgt zonder de CLAUDE.md-bestanden van losse projecten aan te raken.
Init-Hooks
Scripts in ~/.claude/init.d/*.sh draaien eenmalig bij het eerste aanmaken van de container, als root, voordat er naar de claude-gebruiker wordt gezakt. Ze draaien niet opnieuw bij latere docker start-acties, alleen bij verse containers. Gebruik dit voor extra pakketinstallaties of eenmalige setup:
mkdir -p ~/.claude/init.d
cat > ~/.claude/init.d/setup.sh << 'EOF'
#!/bin/bash
apt-get update && apt-get install -y some-package
pip install some-library
EOF
chmod +x ~/.claude/init.d/setup.shEigen Scripts
Zet uitvoerbare bestanden in ~/.claude/bin/ op de host en ze staan in het PATH binnen elke container. Blijft bestaan over alle sessies, alle workspaces.
Omgevingsvariabelen Doorgeven
Gebruik het voorvoegsel CLAUDEBOX_ENV_ om willekeurige omgevingsvariabelen de container in te geven, het voorvoegsel wordt eraf gehaald:
CLAUDEBOX_ENV_GITHUB_TOKEN=xxx CLAUDEBOX_ENV_MY_VAR=hello claudebox "do stuff"Extra Volume-Mounts
Het voorvoegsel CLAUDEBOX_MOUNT_ om extra mappen te mounten:
# Mount at same path on both sides
CLAUDEBOX_MOUNT_DATA=/data claudebox "process the data"
# Explicit source:dest
CLAUDEBOX_MOUNT_STUFF=/host/path:/container/path claudebox "do stuff"
# Read-only
CLAUDEBOX_MOUNT_RO=/data:/data:ro claudebox "read the data"Het Workspace-Model
claudebox maakt twee containers per workspace: claude-<path> voor interactieve sessies en claude-<path>_prog voor programmatische runs. Ze delen geen staat en kunnen tegelijk draaien. De interactieve sessie blokkeert je scripts niet. De scripts onderbreken je sessie niet.
De map ~/.claude wordt in elke container gemount, en de configuratie, API-sleutels, always-skills, init-hooks en eigen scripts worden allemaal over workspaces gedeeld. SSH-sleutels uit ~/.ssh/claudebox worden automatisch meegemount. De isolatie zit op workspaceniveau, de identiteit is gedeeld.
De Docker-socket wordt ook doorgegeven zodat Claude images kan bouwen, compose-stacks kan optrekken en containers kan beheren vanuit zijn eigen container. Omdat de workspace op zijn echte hostpad gemount is ($PWD:$PWD), lossen volume-mounts van binnen Claude correct op op de host. Claude schrijft een docker-compose.yml, draait hem, en de paden kloppen.
Het Levert Zijn Eigen Skill en Plugin Mee
De repo draagt .agents/skills/claudebox/, een agentskill die elke modus documenteert die de doos blootstelt, zodat een assistent die je erop richt de interactieve shell, de eenmalige exec, de REST-API, het OpenAI-compatibele endpoint, de MCP-server, de Telegram-bot en de cron-scheduler al kent zonder dat jij er iets van uitlegt.
Daarnaast @psyb0t/claudebox in .agents/plugins/claudebox/, een MCP-brug stdio↔HTTP bovenop mcp-remote, zodat een OpenClaw- of MCP-agent het /mcp-endpoint van een draaiende doos rechtstreeks kan aansturen. MIT-gelicentieerd. CI publiceert beide bij tag-pushes op ClawHub.
Beveiligingsmodel
Dit draait met --permission-mode bypassPermissions, het moderne equivalent van het oude --dangerously-skip-permissions. Claude heeft sudo zonder wachtwoord binnen de container en kan doen wat hij wil.
De beveiligingsgrens is de container. Claude kan niet bij je hostbestandssysteem voorbij de gemounte workspace en de ~/.claude-config. Slaat hij op hol, dan docker stop en docker rm en weg is hij. Een verse trek je er in seconden op.
De Docker-socketmount is de uitzondering, want die geeft toegang tot de Docker-daemon van de host. Mount hem niet als je dat zorgen baart. De rest blijft ingesloten.
De SSH-sleutels leven in ~/.ssh/claudebox, een toegewijd sleutelpaar dat tijdens de installatie wordt gegenereerd. Je persoonlijke sleutels komen de container nooit binnen. Stel via CLAUDEBOX_SSH_DIR in welke sleutel gebruikt wordt als dat nodig is.
Toeleveringsketen. Het base image is vastgepind op @sha256:-digest, niet alleen op een tag, want tags zijn veranderlijk terwijl een digest inhoudsgeadresseerd is, dus een herbouw kan niet stilletjes andere basisbytes ophalen. In het volle image wordt de Go-tarball naar schijf gedownload en vóór het uitpakken geverifieerd met sha256sum -c tegen een checksum per architectuur, in plaats van de oude ongeverifieerde pipe curl … | tar. Een geknoeide of afgekapte download laat de build falen in plaats van in het image te belanden.
De auth van de API-modus loopt via bearer token in de Authorization-header. Zet CLAUDEBOX_API_MODE_TOKEN. Zet je hem niet, dan draait de API ongeauthenticeerd, wat prima is voor lokaal gebruik en een slecht idee blootgesteld aan een netwerk.
De Slotsom
Ik draai inmiddels elke Claude-sessie binnen claudebox. Zeven modi, nul hostvervuiling. De isolatie betekent dat ik er niet over nadenk om hem pakketten te laten installeren, configuraties te laten herschrijven of welke commando's dan ook te laten draaien om de klus te klaren. De sessiecontinuïteit betekent dat ik mijn terminal sluit, uren later terugkom en precies oppak waar ik gebleven was. De API en het OpenAI-endpoint betekenen dat ik Claude aan andere diensten kan knopen zonder lijmcode te schrijven. De Telegram-bot betekent dat ik een taak vanaf mijn telefoon kan starten terwijl ik niet aan mijn bureau zit. De cron-scheduler betekent dat Claude werkt terwijl ik slaap.
Haal het hier: github.com/psyb0t/docker-claudebox
Onder WTFPL-licentie, want het enige dat gevaarlijker is dan een AI met roottoegang is een AI met roottoegang en een restrictieve licentie.
Hoe Je Het In Je Agent Installeert
Claude Code die Claude Code in een doos draait, en nu installeerbaar vanuit Claude Code, wat ongeveer zo recursief is als ik wil gaan. Alles onder .agents/ staat gecatalogiseerd in één marketplace, dus het zijn twee commando's:
claude plugin marketplace add psyb0t/agents
claude plugin install claudebox@psyb0tCodex gebruikt dezelfde marketplace met een ander werkwoord, codex plugin add claudebox@psyb0t, want codex plugin install bestaat niet. Het vindt de skill ook zelf in een checkout van de repo, aangezien het .agents/skills/ native scant zonder dat er ook maar iets geïnstalleerd is. Het staat inmiddels ook in de officiële MCP Registry, dus een client die servers daarvandaan oplost kan het vinden zonder dat je hem een URL aanreikt.