Docker Sandbox: اجرای ایجنت‌های برنامه‌نویسی در یک MicroVM ایزوله

فهرست مطالب

ایجنت‌های برنامه‌نویسی مثل Claude Code یا Codex وقتی واقعاً به کار می‌آیند که بتوانند بدون تأیید گرفتن برای هر قدم، دستور اجرا کنند، پکیج نصب کنند و فایل بنویسند. همین آزادی روی لپ‌تاپ اصلی دردسر است: پروسه ایجنت به ~/.ssh، فایل‌های .env، توکن‌های ابری و Docker میزبان دسترسی دارد، و یک prompt injection در یک README یا یک پکیج آلوده کافی است تا از این دسترسی سوءاستفاده شود.

من Claude Code را داخل Docker Sandbox اجرا می‌کنم. در این نوشته توضیح می‌دهم این ابزار چه چیزی را ایزوله می‌کند، چطور نصب و استفاده می‌شود و کجا هنوز باید خودتان حواستان باشد.

این نوشته بر اساس مستندات Docker در اکتبر ۲۰۲۶ است. sbx سریع تغییر می‌کند؛ پیش از اجرای هر دستور، خروجی sbx <command> --help را ببینید.

Docker Sandbox چیست؟

Docker Sandbox هر ایجنت را در یک microVM اجرا می‌کند که kernel خودش را دارد. داخل این VM یک Docker Engine جداگانه هم هست، پس ایجنت می‌تواند docker build و docker compose up اجرا کند بی‌آنکه به Docker میزبان راهی داشته باشد. همه این‌ها با یک CLI به نام sbx مدیریت می‌شوند.

داخل VM، ایجنت دسترسی کامل دارد (حتی sudo). مرز امنیتی خود VM است و ایجنت فقط از چند مسیر مشخص به میزبان می‌رسد:

  • workspace: پوشه پروژه‌ای که خودتان به Sandbox می‌دهید.
  • شبکه: همه ترافیک TCP خروجی از یک proxy روی میزبان می‌گذرد که سیاست شبکه را اعمال می‌کند.
  • کلیدها: ایجنت فقط یک مقدار جایگزین (sentinel) می‌بیند. proxy میزبان پس از خروج درخواست از VM، مقدار واقعی کلید را در header قرار می‌دهد.
  • MCP gateway و skillهای مشترک، اگر فعالشان کنید.
flowchart LR subgraph host["میزبان"] ws["پوشه پروژه"] proxy["proxy میزبان
سیاست شبکه + تزریق کلید"] subgraph vm["Sandbox (microVM)"] agent["ایجنت"] --> dockerd["Docker Engine داخلی"] end end ws <-->|"mount مستقیم یا clone"| agent agent -->|"HTTP/HTTPS"| proxy proxy --> api["اینترنت و API مدل"]

مستندات Docker نام hypervisor را ذکر نمی‌کند؛ فقط می‌گوید هر Sandbox یک microVM با ایزوله‌سازی در سطح hypervisor است.

چرا یک کانتینر معمولی کافی نیست؟

کانتینر kernel میزبان را به اشتراک می‌گذارد. مشکل بزرگ‌تر این است که ایجنت‌ها معمولاً به Docker نیاز دارند و راه ساده برای دادن Docker به یک کانتینر، mount کردن /var/run/docker.sock است؛ یعنی دسترسی معادل root روی میزبان. یک ماشین مجازی کامل این مشکل را ندارد، اما ساختن و نگه‌داشتن یک VM برای هر پروژه، با شبکه و مدیریت کلید جداگانه، کار زیادی است. Docker Sandbox همان VM را با چند دستور در اختیارتان می‌گذارد.

کانتینر معمولیماشین مجازی کاملDocker Sandbox
مرز ایزوله‌سازیkernel مشترک با میزبانhypervisorhypervisor (microVM)
Docker برای ایجنتمعمولاً با mount کردن docker.sock میزبانباید جدا نصب شودیک Docker Engine جدا در هر Sandbox
کنترل ترافیک خروجیباید خودتان بسازیدباید خودتان بسازیدproxy میزبان با سیاست deny-by-default
کلیدهای APIمعمولاً متغیر محیطی داخل کانتینرداخل VMروی میزبان می‌ماند
هزینهکمزیادیک VM و یک daemon برای هر Sandbox

هزینه‌اش هم واقعی است: هر Sandbox کش ایمیج و لایه‌های Docker خودش را دارد و چیزی را با بقیه به اشتراک نمی‌گذارد.

نصب

پیش‌نیازها:

  • macOS: نسخه 14 (Sonoma) یا بالاتر، فقط Apple silicon.
  • Windows: ویندوز 11 روی پردازنده 64 بیتی Intel یا AMD، با Windows Hypervisor Platform فعال.
  • Linux: اوبونتو 24.04 یا بالاتر (Intel/AMD یا Arm 64 بیتی)، با KVM فعال و کاربرتان در گروه kvm. Docker توزیع‌های مشتق‌شده مثل Linux Mint را تست نمی‌کند. اگر خود لینوکس داخل یک VM است، nested virtualization لازم است.

macOS:

brew trust docker/tap
brew install docker/tap/sbx

Windows (خط اول در PowerShell با دسترسی Administrator):

Enable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform -All
winget install -h Docker.sbx

Ubuntu:

# KVM فعال است؟ باید kvm_intel، kvm_amd یا kvm را ببینید
lsmod | grep kvm

sudo usermod -aG kvm $USER
newgrp kvm

# Docker Engine و sbx با هم
curl -fsSL https://get.docker.com | sudo SBX=1 sh

# یا فقط sbx، اگر Docker Engine را نمی‌خواهید
curl -fsSL https://get.docker.com | sudo REPO_ONLY=1 sh
sudo apt install docker-sbx

بعد وارد حساب Docker شوید. این دستور مرورگر را برای ورود باز می‌کند:

sbx login

اولین بار که Sandbox می‌سازید، sbx از شما می‌خواهد سیاست سراسری شبکه را انتخاب کنید: allow-all، balanced (ترافیک معمول توسعه، شامل سرویس‌های AI و مخازن پکیج) یا deny-all. من balanced را انتخاب کردم و بعد بقیه را دستی اضافه می‌کنم. همین را بعداً با sbx policy init هم می‌شود انجام داد.

استفاده از CLI و Sandboxهای محلی رایگان است، حتی برای کار تجاری. Sandboxهای ابری و قابلیت‌های مدیریت سازمانی اشتراک جداگانه دارند و هزینه API مدل هم با خود ارائه‌دهنده است.

اولین Sandbox

sbx run claude ~/my-project

اگر مسیر ندهید، پوشه فعلی mount می‌شود. ایجنت‌های فعلی این‌ها هستند: claude، codex، copilot، cursor، devin، docker-agent، droid، gemini، kiro، opencode و shell (یک Sandbox بدون ایجنت، برای کار دستی).

Claude Code داخل Sandbox به‌صورت پیش‌فرض با claude --dangerously-skip-permissions اجرا می‌شود. یعنی هیچ تأییدی از شما نمی‌گیرد، و دلیل اصلی که اصلاً آن را داخل VM اجرا می‌کنیم همین است. برای احراز هویت دو راه هست: کلید API را با sbx secret set anthropic ذخیره کنید، یا داخل Claude Code دستور /login را بزنید و با اشتراک Claude وارد شوید.

نام پیش‌فرض Sandbox به شکل <agent>-<workdir> است. اگر چند Sandbox دارید، بهتر است خودتان نام بگذارید:

sbx run --name blog claude ~/blog

# بعداً، از هر پوشه‌ای، دوباره به همان Sandbox وصل شوید
sbx run --name blog

# آرگومان‌های بعد از -- به خود ایجنت می‌رسند
sbx run --name blog -- --continue

# ساخت بدون وصل شدن
sbx create --name blog claude ~/blog

دستورهای روزمره:

sbx                           # داشبورد تعاملی (TUI)
sbx ls                        # فهرست Sandboxها
sbx exec -it blog bash        # یک شل داخل Sandbox
sbx cp ./notes.txt blog:/tmp/ # کپی فایل بین میزبان و Sandbox
sbx stop blog                 # توقف؛ VM و محتوایش می‌ماند
sbx rm blog                   # حذف کامل VM و هرچه داخلش بود
sbx prune                     # حذف همه Sandboxهای متوقف

sbx stop چیزی را پاک نمی‌کند: پکیج‌های نصب‌شده، ایمیج‌های Docker و فایل‌های داخل VM در اجرای بعدی سر جایشان هستند. فقط sbx rm همه را حذف می‌کند. اگر --rm را به sbx run بدهید، Sandbox بعد از بسته شدن جلسه ایجنت خودبه‌خود حذف می‌شود.

سه حالت workspace

این مهم‌ترین تصمیمی است که هنگام ساختن Sandbox می‌گیرید، چون بعداً عوض نمی‌شود.

حالت مستقیم (پیش‌فرض): پوشه پروژه با همان مسیر مطلق میزبان داخل VM دیده می‌شود و تغییرات در هر دو جهت فوری است. ایجنت روی فایل‌های واقعی شما کار می‌کند.

حالت clone:

sbx run --clone claude ~/my-project

مخزن میزبان به‌صورت فقط‌خواندنی در /run/sandbox/source mount می‌شود و ایجنت روی یک clone خصوصی داخل Sandbox کار می‌کند. commitهای ایجنت داخل Sandbox می‌مانند تا خودتان از میزبان آن‌ها را fetch کنید:

git fetch sandbox-<name>
git diff main..sandbox-<name>/<branch>

این remote را یک git-daemon داخل Sandbox سرو می‌کند و فقط وقتی Sandbox روشن است در دسترس است. پس پیش از sbx rm، کار را fetch یا push کنید؛ sbx rm این remote را هم از مخزن میزبان حذف می‌کند.

بدون mount: sbx create claude بدون مسیر، Sandboxی می‌سازد که هیچ پوشه‌ای از میزبان ندارد و در /home/agent/workspace کار می‌کند.

اگر ایجنت به پوشه دیگری فقط برای خواندن نیاز دارد، آن را با :ro اضافه کنید:

sbx run claude . ~/docs:ro

قاعده خودم: برای مخزن‌هایی که به آن‌ها اعتماد کامل ندارم یا کارهای طولانی بدون نظارت، حالت clone. وقتی کنار ایجنت کار می‌کنم و می‌خواهم تغییرات را همان لحظه در ویرایشگر ببینم، حالت مستقیم.

شبکه

ترافیک خروجی به‌صورت deny-by-default کنترل می‌شود و فقط مقصدهای مجاز باز هستند. UDP به‌طور پیش‌فرض بسته است و ICMP همیشه.

sbx policy ls                                # قوانین فعال
sbx policy log                               # درخواست‌های اخیر و دلیل مسدود شدنشان
sbx policy allow network api.example.com     # اجازه به یک دامنه
sbx policy deny network example.org          # مسدود کردن یک دامنه
sbx policy rm network --resource example.org # حذف یک قانون
sbx policy reset                             # پاک کردن همه قوانین سفارشی

با --sandbox <name> قانون فقط روی یک Sandbox اعمال می‌شود. درخواستی که مسدود شود پاسخ HTTP 403 می‌گیرد و متن پاسخ می‌گوید کدام قانون جلویش را گرفته؛ ایجنت معمولاً همین متن را برایتان گزارش می‌کند.

یک نکته که مستندات خود Docker هم رویش تأکید دارد: سیاست balanced شامل wildcardهای گسترده‌ای مثل *.googleapis.com است که خیلی بیشتر از سرویس‌های AI را پوشش می‌دهند. یک بار sbx policy ls را کامل بخوانید و هرچه لازم ندارید حذف کنید.

پورت‌ها: سرویسی که داخل Sandbox اجرا می‌شود از میزبان در دسترس نیست مگر پورتش را منتشر کنید. سرویس باید روی 0.0.0.0 گوش بدهد، نه فقط 127.0.0.1:

sbx ports blog --publish 8080:3000
sbx ports blog --unpublish 8080:3000

برای رسیدن به سرویسی که روی localhost میزبان اجرا می‌شود (مثلاً یک API محلی روی پورت 3000)، داخل Sandbox از host.docker.internal:3000 استفاده کنید. سیاست شبکه هم باید localhost:3000 را مجاز کرده باشد.

کلیدها و secretها

کلیدها روی میزبان ذخیره می‌شوند (در keychain سیستم‌عامل) و proxy آن‌ها را هنگام خروج درخواست اضافه می‌کند:

sbx secret set anthropic                              # برای همه Sandboxها
sbx secret set openai --sandbox blog                  # فقط برای یک Sandbox
sbx secret set openai --oauth                         # ورود OAuth به‌جای کلید
echo "$ANTHROPIC_API_KEY" | sbx secret set anthropic  # از stdin
sbx secret set anthropic --ref 'op://Private/Anthropic/api-key'  # از 1Password
sbx secret set github --command 'gh auth token'       # از خروجی یک دستور

کلید API را با -e ANTHROPIC_API_KEY=... به Sandbox ندهید. -e متغیر را واقعاً داخل VM قرار می‌دهد و ایجنت، یا هر کدی که اجرا می‌کند، می‌تواند آن را بخواند. کل فایده تزریق از طریق proxy همین است که مقدار واقعی هیچ‌وقت وارد VM نشود.

دو نکته که اول گیجم کرد:

  • روی سرور لینوکس بدون محیط گرافیکی (و بعضی نصب‌های WSL)، Secret Service در دسترس نیست و sbx کلیدها را در یک فایل با مجوز 0700 ذخیره می‌کند. هر کسی که آن فایل را بتواند بخواند، کلیدها را دارد.
  • داخل Sandbox، gh auth status می‌گوید وارد نشده‌اید، اما git push کار می‌کند، چون proxy اعتبار GitHub را در لایه شبکه اضافه می‌کند. اگر push با خطای احراز هویت شکست خورد، روی میزبان sbx secret set github را اجرا کنید.

منابع و سفارشی‌سازی

sbx run --cpus 4 --memory 8g claude ~/my-project

--memory به‌طور پیش‌فرض نصف حافظه میزبان است (بین 512 مگابایت و 32 گیگابایت) و --cpus به‌طور پیش‌فرض همه هسته‌ها را می‌دهد. وقتی چند Sandbox هم‌زمان دارید، کم کردنشان ارزش دارد.

اگر هر بار همان ابزارها را داخل Sandbox نصب می‌کنید، یک template بسازید. ایمیج‌های پایه در docker/sandbox-templates روی Docker Hub هستند (مثلاً تگ claude-code)، روی اوبونتو ساخته شده‌اند و با کاربر agent اجرا می‌شوند:

FROM docker/sandbox-templates:claude-code
USER root
RUN apt-get update \
 && apt-get install -y --no-install-recommends hugo \
 && rm -rf /var/lib/apt/lists/*
USER agent

ایمیج را بسازید، در یک registry قرار دهید و با -t (یا --template) استفاده کنید:

sbx run -t <registry>/<image>:<tag> claude ~/my-project

روش دیگر kitها هستند (sbx run claude --kit ./my-kit/) که هنوز آزمایشی‌اند. دستورهای نصب یک kit داخل Sandbox با دسترسی root اجرا می‌شوند و منبعشان به‌طور پیش‌فرض فقط Docker Hub است.

Sandbox جلوی چه چیزهایی را نمی‌گیرد

Sandbox میزبان را از ایجنت جدا می‌کند، اما هر چیزی را که خودتان به آن بدهید در اختیارش است:

  • در حالت مستقیم، ایجنت فایل‌های واقعی شما را تغییر می‌دهد، از جمله فایل‌هایی که بعداً خودشان اجرا می‌شوند: git hookها، تنظیمات CI، taskهای IDE، Makefile، اسکریپت‌های package.json و تنظیمات پروژه‌ای ایجنت‌ها. git hookها داخل .git/ هستند و در git diff دیده نمی‌شوند.
  • مقصدهای شبکه مجاز، به‌خصوص wildcardهای گسترده، هنوز مسیری برای بیرون فرستادن داده‌اند.
  • سرورهای MCP محلی (stdio) روی خود میزبان و با مجوزهای شما اجرا می‌شوند، نه داخل Sandbox.
  • skillهای مشترک با دسترسی readwrite: یک Sandbox می‌تواند skillی را تغییر دهد که Sandbox دیگری از آن استفاده می‌کند.
  • داخل VM ایجنت همه‌کاره است و هرچه نصب کند تا sbx rm می‌ماند.

بعد از هر جلسه در حالت مستقیم، قبل از اجرای هر چیزی روی میزبان، این‌ها را نگاه می‌کنم:

git status
git diff --stat
ls -la .git/hooks/
git diff -- .github/ Makefile package.json .vscode/ .claude/

چند نکته کوتاه

  • ایجنت می‌داند داخل Sandbox است؟ از خودش بپرسید. در Claude Code وسط کار: /btw are you running in a sandbox?
  • تنظیمات ~/.claude میزبان داخل Sandbox نیست. hookها و تنظیمات سطح کاربر روی میزبان می‌مانند؛ فقط تنظیمات سطح پروژه (مثلاً پوشه .claude/ داخل مخزن) دیده می‌شوند. skillها را می‌شود با sbx skills به اشتراک گذاشت.
  • paste کردن تصویر (Ctrl+V) به‌طور پیش‌فرض خاموش است. با sbx settings set clipboard.imagePaste true روشن می‌شود، ولی در آن لحظه پروسه داخل Sandbox تصویر clipboard میزبان را می‌خواند.
  • متغیرهای محیطی ماندگار داخل Sandbox: در Sandboxهای من فایل /etc/sandbox-persistent.sh پیش از هر دستور shell خوانده می‌شود و جای خوبی برای exportهاست، مثلاً تنظیم NVM_DIR بعد از نصب nvm. اسکریپت‌های completion (مثل bash_completion در nvm) را آنجا اضافه نکنید: چون فایل پیش از هر دستور اجرا می‌شود، همه دستورها بی‌صدا و بدون خروجی شکست می‌خورند.
  • وقتی چیزی کار نمی‌کند، sbx diagnose مشکلات رایج نصب را بررسی می‌کند.

جمع‌بندی

Docker Sandbox اجرای ایجنت با --dangerously-skip-permissions را از یک ریسک به یک انتخاب معقول تبدیل می‌کند: kernel جدا، Docker جدا، شبکه با allowlist و کلیدهایی که از میزبان بیرون نمی‌آیند. در عوض، هر Sandbox یک VM کامل با daemon خودش است و حالت مستقیم هنوز به ایجنت اجازه می‌دهد فایل‌های پروژه را عوض کند. اگر فقط یک کار از این نوشته انجام می‌دهید: برای مخزن‌هایی که نمی‌شناسید از --clone استفاده کنید و sbx policy ls را یک بار کامل بخوانید.

منابع

پست های مشابه