sandbox_start
goal を持つ新しいボックスを起動します。リポジトリが公開するサービスをそれぞれの名前で宣言でき、環境、シークレット、サイズ、ツールチェーンも選べます。ボックスの id、URL、受け取ったサービスを返します。
| 入力 | 出力 |
|---|---|
name、goal、services[{name, port, targetPort, web, fromBox, fromPort, version, containerPort, env}]、externalBaseUrl、environment、secrets(名前の一覧)、size、toolchains、orientation、idleTimeoutMin、restore、waitForCapacitySec(goal は必須、ほかは任意) |
id、arch、sceneUrl、webUrl、takeoverUrl、status、orientation、orientationNote(横向きに切り替わったときだけ)、startedFrom(snapshot)、size、vcpus、memoryGb、受け取った services(web サービスには url 付き)、受け取った secrets、toolchains、idleTimeoutMin、toolchainsNote(toolchains に常に使える言語を書いたときだけ)、environment、アカウントに IP 許可リストがあるときの boxUrlAccess、next |
name:このボックスの用途を、人が読める言葉で書きます("会員センターの返金フロー")。アプリはこれをボックスのタイトルとして表示します。指定しないと人にはボックス ID しか見えません。goal(必須):このボックスがなぜあるのか、どうなれば完了かを、人が読む言語で 1〜2 文で書きます("会員センターの返金ボタンで返金が台帳に記録されるようにする。ブラウザで返金が最後まで通れば完了")。アプリはこれをボックスのカードとページに表示し、sandbox_statusとsandbox_listも返すので、この会話が閉じられても別の会話が作業を引き継げます(別の会話が残したボックスを引き継ぐ)。最大 500 文字で、それより長いと 500 文字で切られます。指定しないか空白だけだと失敗します:goal is required: one or two sentences, in the language the person reads, on why this box exists and what done looks like. The person sees it on the box's card in their app, and whoever picks the box up if this conversation is closed reads it in sandbox_status. Call sandbox_start again with goal set.REST(POST /v1/boxes)ではgoalは任意です。orientation:AI の画面とレビューのスクリーンショットをportrait(390 × 844)またはlandscape(1280 × 800)にします。省略するとアカウント設定を使い、初期値は縦向きです。この設定はボックスごとに保持され、後でアカウント設定を変えても既存のボックスは変わりません(動いているボックスは REST のPUT /v1/boxes/{id}/orientationで切り替えます)。画面でデスクトップ向けのレイアウトを見せるならlandscapeにしてください。Chromium のウィンドウは最小 500 ピクセル幅で、縦向きの画面にちょうど収める引数はsandbox_execにあります。縦向きを指定した(またはアカウント設定が縦向き)のにボックスのイメージが画面を回転できない場合、ボックスは代わりに横向きで起動し、結果のorientationNoteがそう伝えます。ページは 1280 × 800 のウィンドウで描かれ、スマホサイズの画面にはイメージが更新されてから新しいボックスが必要です。restore:アイドルの規則で止められ、凍結コピーがまだ残っているボックスの id です。コピーは停止から 24 時間残ります(sandbox_statusのrestorableUntil、またはall: trueを付けたsandbox_list)。新しいボックスを起動する代わりに、その同じボックスを凍結した時点のまま戻します。/work、動いていたプログラム、id、URL もそのままで、nextもそう伝えます。goalはいつもどおり渡してください。ほかの設定はボックス自身のものが使われ、渡しても無視されます。つないでいたスマホは残らないので、新しく attach してください。凍結コピーが残るのはアイドルの規則で止まったボックスだけで、sandbox_stopで止めたものは戻せません(409 と、起動し直す方法が返ります)。戻すには新しいボックスと同じく、プランのボックス上限に空きとクレジットが必要です。その時点でどのホストにも空きがなければ、ボックスは凍結した状態で戻り、次にボックスに作用する呼び出しで起きます。size:1(既定)、2、4、8 のいずれかの単位(1 単位 2 vCPU・8 GB メモリ・40 GB ディスク)。ボックスの生存期間中は固定で、クレジットは比例して消費します。ボックスはどれも x86_64(amd64)の Debian 12 で、結果にもarch: "amd64"と出ます。arm64 のイメージについてはsandbox_execを参照してください。1 は Web フロントエンドと、Node、Python、Go のたいていの作業に足ります。Android、Gradle、重い docker compose(1 つのボックスで複数のサービスをイメージとしてビルドするなど)には 2 以上を使います。toolchains:java-17、java-21、android、dotnet、rust、flutter(Web と Android。androidも有効になります)、esp32(ESP32 向け PlatformIO。Arduino と ESP-IDF、コンパイルのみ)、python-3.12、python-3.13(その Python が PATH の先頭でpython、python3、pipになります。選ばなければpython3は Debian の 3.11 で、Pillow、numpy、boto3 が入っています)。すべてのコマンドで有効になります。Go、Node、Python 3.11、PHP と Composer、MySQL・Postgres・S3 のテスト用サーバー(psbx-testdb)、psql、redis-cli、mysql、ImageMagick、PowerShell 7、Azure CLI と GitHub CLI、clang、cmake は選ばなくても常に使えます。toolchainsにこれらの言語(go、node、python)を書いても無視され、結果のtoolchainsNoteにその旨が出ます。services:リポジトリがボックス上で公開するサービス。それぞれname、呼び出し側が接続するport、任意のtargetPortとwebを持ちます。- ボックスが起動した時点から、各名前はボックス内に専用のアドレス(
198.18.0.0/15から割り当て)を持ち、ボックスの DNS はプロセスにもコンテナにもそのアドレスで名前を答えます。配線は不要です。name:portは、プロセスが待ち受けるtargetPort(省略時はport)に届きます。呼び出し側はname:portに接続したまま、プロセスはtargetPortで待ち受けます。既定のブリッジ、ユーザー定義の Docker ネットワーク、docker composeのコンテナも同じようにこれらの名前に届きます。ボックスの規則はボックス自身のプログラムにもすべてのコンテナネットワークにも適用され、ボックス内の Docker の DNS はボックスのリゾルバーに転送します。 - プロセスは
0.0.0.0で待ち受ける必要があります。127.0.0.1だけで待ち受けるプロセスには、自分の URL(下のsceneUrlを参照)からは届きますが名前では届きません。コンテナはtargetPortを公開してください(compose のports:、docker runの-p <targetPort>:<コンテナ内のポート>)。 - 同じ
targetPortを共有する名前は 1 つのプロセスで、sandbox_wireでは一緒に切り替わります。名前ごとにアドレスがあるので、portが同じでtargetPortが違う名前は別のサービスです。api.acme.internal:8080とbilling.acme.internal:8080は、8080 と 8081 で待ち受ける 2 つのプロセスにそれぞれ届きます。 - ボックスのポート 80 と 9095 は boxd のもの(80 はシーンの入口、9095 はその API)で、どのプロセスも待ち受けられません。呼び出し側がポート 80 で接続するサービスは
targetPortを宣言します。{ "name": "api.acme.internal", "port": 80, "targetPort": 8080 }なら、呼び出し側はapi.acme.internal:80(またはhttp://api.acme.internal/)に接続し、プロセスは 8080 で待ち受けます。targetPortがないと 400 で失敗します:port 80 in the box is boxd's scene entry; add targetPort, the port your process listens on — callers keep dialing <name>:80。ポート 9095 も同じで、targetPort自体も 80 や 9095 にはできません。それ以外のポートは 443 などの特権ポートも含めて使えます(コマンドは root で動きます)。ポート 80 にtargetPortが要るのはボックス内のサービスだけです。ポート 80 で登録した環境のアドレス(内部ロードバランサー)と、ポート 80 で宣言したリンク({ "name": "api.test.internal", "port": 80, "fromBox": "<A>", "fromPort": 8080 })はそのまま使えます。どの名前もボックス内に専用のアドレスを持ち、ボックス自身のポート 80 とぶつからないからです。80 で宣言も登録もしていない名前(別のポートで宣言したサービスやリンク、別のポートだけで登録した環境のホスト)にポート 80 で接続すると、ボックスのプログラムからもコンテナからも 502nothing listens on <name>:80 in this box: that name is declared on another port. Dial the port it is declared on, or declare it on port 80 with targetPort, the port your process listens onになります。localhost:80は今も boxd のシーンの入口で、sceneUrlと同じく最初に宣言したサービスに届きます。2026-09-24 20:30 UTC より前に起動したボックスでは、そういう名前に 80 で接続すると最初に宣言したサービスに届きます。誰も宣言していないほかのポートでは、接続は拒否されるか、ボックスでそのポートを待ち受けているものに届きます。名前のポート 9095 は boxd の API に届き、404 を返します。 - 名前はホスト名(小文字の英数字、
-、.)か、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10の内側のプライベート IPv4 アドレスまたは範囲(例:172.16.0.0/16)です。ボックス内(コンテナも含む)からその範囲のそのポートへの接続は、ボックス内のそのサービスのtargetPortに届きます(externalモードではexternalBaseUrlへの転送)。範囲は、ホスト名ではなく自分のレジストリから得た IP に直接つなぐ呼び出し側のためのもので、専用のアドレスは持たず、ここでしか宣言できません。 web: trueは、人がブラウザで開いて製品をそのまま使える各サービスを示します。API、データベース、JSON しか見せないものには付けません。web サービスはそれぞれ専用の URL を持ち、アプリの「使う」ボタンはその最初のもの(下のwebUrl)を開きます。fromBox(と任意のfromPort)を付けると、この項目はこのボックス内のサービスではなく、自分の別のボックスへのリンクになります。このボックスのname:portがボックスfromBoxのfromPortに届きます。別のボックスへのリンクを参照してください。version(と任意のcontainerPort)を付けると、自分のプロセスの代わりに、公開したバージョンをその名前で動かします。ボックスがイメージを pull してバックグラウンドで起動します。公開したバージョンを動かすを参照してください。
- ボックスが起動した時点から、各名前はボックス内に専用のアドレス(
externalBaseUrl:変更していない HTTP サービスがある場所。http(s)://host[/path]で、クエリや認証情報は含められません。例: ステージングのゲートウェイ。ボックスにこれがあると、宣言したすべてのサービスはexternalモードで始まります。プロセスからもコンテナからも、name:portへの各 HTTP リクエストがパスはそのまま、Host を書き換えてexternalBaseUrlに転送されます。ポート 80 で宣言したサービスも同じです。ボックス上で直接動くプログラムはlocalhost:<port>と127.0.0.1:<port>でも転送先に届きます。ただし 80 と 9095(boxd のもの。localhost:80はシーンの入口)と、boxモードのサービスのプロセスが待ち受けているポートは除きます。ボックス自身の IP や172.17.0.1のそのポートからは届かないので、名前で接続してください。boxd はサービスのポートを占有しないので、自分のプロセスはいつでもそのtargetPortで起動でき、その後sandbox_wireで名前をそちらに切り替えます。任意。environment:自分の環境(sandbox_environmentsで一覧できます)。ボックスはそのプライベートアドレス(データベース、Redis、内部サービス。ネットワーク内のコネクタ経由)に接続でき、各サービスの設定を/work/.sbx/env/<サービス>.envと.shとして受け取り、externalBaseUrlを指定しなければ環境のものを使います(同じくexternalで始まります)。servicesに宣言したホスト名が環境のアドレスでもある場合(ポートは問いません)、その名前はボックスを指し、reachableから外れます。宣言した範囲に入り同じポートの環境の IP アドレスも同様です。環境の各ホスト名もボックス内に専用のアドレスを持つので、ポート 80 のアドレス(内部ロードバランサーなど)もほかと同じように使え、自分のプロセスが環境のアドレスと同じポート番号で待ち受けることもできます。結果のenvironmentには、サービスごとのkeysCountとnotable(まず確かめるべき接続・モード・権限の設定。全部のkeysはsandbox_environmentsと env ファイルにあります)、接続ごとのrttMs、同じ環境で稼働中の自分の他のボックスactiveBoxesが入ります。アカウントに環境があるのにenvironmentなしで起動すると、nextがそれらの名前を挙げます。ボックスは後から環境につなげないからです。環境を参照してください。secrets:sandbox_secretsの名前から、ボックスが割り当てられた時点で環境変数として注入するものを選びます。列挙しないものは注入されません。あとからsandbox_secretsで追加できます。idleTimeoutMin:使用(ボックスに作用するツール呼び出しか復帰。ボックスの状態を参照)がこの分数ない場合に、凍結中かどうかにかかわらず、ボックスを完全に停止します(/workは消えます)。指定すると既定の期限の代わりになります。ボックスを起こしておくもの(background: trueで起動したコマンドは最後に使われてから 1 時間まで、人の引き継ぎ、別のボックスからのリンク接続)があると、それが終わるまで停止は後回しになります。この時間は最後に使われた時刻から数えます。人がボックスの URL を使ってもリセットされません。その人のリクエストがボックスを起こしておくのは最後に使われてから 2 時間までで、そのあとボックスは凍結し、期限を過ぎているのですぐに完全に停止します。ページの読み込みで凍結したボックスが復帰すれば使用として数え、数え直しになります。0 または省略:凍結したボックスは最後に使われてから 24 時間で停止します。sandbox_statusとsandbox_listのstopsAtは、それまでに使われなければこの規則がボックスを完全に止める時刻です。その 30 分前からは、そのボックスのツール結果の最後に毎回予告が付きます(ボックスの状態)。idleTimeoutMinの上限は 10080 分(7 日)です。大きい値は 10080 に制限され、結果に説明が付きます。凍結したボックスもその間はアカウントの同時ボックス数に数えられます。いずれの場合も、ボックスは 10 分アイドルで凍結されます(ボックスの状態を参照)。使われ続けているボックスに最長稼働時間はありません。startedFrom:snapshot:ボックスは microVM のホスト上でスナップショットから復元され、数秒で使えます。どのボックスも microVM で動き(sandbox_statusのplacement: "microvm")、アイドルになると凍結されます。sceneUrl:https://<id>-<key>.box.parallelsandbox.com。webの有無にかかわらず、最初に宣言したサービスを開きます。boxモードならボックス内の127.0.0.1:<targetPort>、externalモードならexternalBaseUrlです。サービスを宣言していなければ 503 を返します。ボックスが止まるまで有効で、凍結しても変わりません。services[].url:web: trueで宣言したサービスはそれぞれ専用の URL を持ち、同じ仕組みでそのサービスを開きます。ボックスが止まるまで有効です。最初に宣言したサービスがwebなら、そのurlはsceneUrlそのもの、https://<id>-<key>.box.parallelsandbox.comで、ほかの web サービスはhttps://<id>-<key>-<targetPort>.box.parallelsandbox.comです。webのないサービスにはurlがありません。sandbox_statusと REST のボックス一覧(GET /v1/boxes)も同じ URL を返します。boxd がサービスごとの URL より古いボックス(それが出る前に起動したもの)は何も返さず、使えるのはsceneUrlだけです。webUrl:webを付けた最初のサービスのurl。なければ返りません。ボックスが止まるまで有効です。つまり最初に宣言したサービスがwebならsceneUrl、そうでなければ最初のwebサービスの-<targetPort>の URL です。アプリの「使う」ボタンはこれを開くので、servicesの順番は関係ありません。sandbox_reviewのカードが開くのは、webUrlではなく、その呼び出しがopenで指定した入口です。ボックスの外から届くのは、
webを付けたサービスと、sceneUrl経由の最初に宣言したサービスだけです。-<port>のポートがwebのないサービスのものなら 404 で、サービスのないポートと同じです。ポートを順に試しても、隠れたサービスは見つかりません。これらの URL にはランダムな key が入っているので、ボックス id から組み立てることはできません。
sandbox_startまたはsandbox_statusが返すものをそのまま使ってください。key が違う URL は、存在しないボックスと同じ 404 になります。これらの URL の前にログインはありません。URL の key が唯一の保護です。URL を持つ人は誰でもそのサービスを使え、それを通じてそのサービスが届くものすべてにも届きます。環境につながったボックスのフロントエンドは、開いた人の代わりに dev のデータベースや内部サービスを呼びます。それを許してよい相手にだけ共有してください。チャットアプリは貼られた URL を取りに行ってプレビューを作ります。プレビューのサービスはページを受け取り、その取得はボックスの利用として数えられ、HTML を求める取得はページの読み込みと同じく凍結中のボックスを復帰させます。ボックスが止まると URL は使えなくなり(503
box is terminated)、凍結中は 503 を返します。ボックスの URL はボックスごとに専用のホストを持つので、ID プロバイダーに前もって登録できません。ボックスでのサインイン(SSO、OAuth)には、チームがローカルで使うログイン方法でボックスのコピーを動かすか、ボックスが動いている間だけ、その正確な URL を dev クライアントの許可リダイレクト URI に加えます。
https://*.box.parallelsandbox.com/*のようなワイルドカードは決して登録しないでください。すべてのアカウントのボックスがこのドメインを共有しています。ボックスの URL でのサインインを参照してください。これらの URL を通ったリクエストは
Host: 127.0.0.1:<targetPort>でプロセスに届き、公開ホストはX-Forwarded-Host、スキーム(https)はX-Forwarded-Proto、クライアントはX-Forwarded-Forに入ります。相対パスのリダイレクトはそのまま動き、Hostから絶対 URL や Cookie のドメインを組み立てるアプリは転送ヘッダーを信頼するよう設定してください(Express:app.set('trust proxy', true))。これらの URL で開いたページは人のブラウザ、つまりボックスの外で動きます。そのコードが別オリジンの API を呼ぶ場合、リクエストがボックス内の API に届くのは、API のサービスに
webがあり(ページにはそのサービスのurlを渡す必要があります)、API がページのオリジンに CORS で応じる場合だけです。より簡単なのは、フロントエンドの dev server に、ボックス内の API の名前へ転送する同一オリジンのプロキシを置く方法です。例:Vite のserver: { proxy: { '/api': 'http://api.acme.internal:8080' } }。ブラウザはページ自身のオリジンの/api/...を呼び、ボックス内で動く dev server がそれを/apiの接頭辞ごとapi.acme.internal:8080に転送します。接頭辞を残すか外すかは dev に合わせてください(チーム構成)。next:次の手順のヒント(1 行)。
同時に動かせるボックス数は Free 3、Pro 5、Max 20 です。凍結中のボックスも数に入り、この上限はアカウント単位で、すべての接続で共有します。上限に達すると sandbox_start は 429 plan <plan> allows <n> boxes at once and <m> are running or frozen; frozen boxes still hold a slot, so list them with sandbox_list (GET /v1/boxes) and sandbox_stop one you no longer need. で失敗し、そのうち何台が凍結中か、誰も使わなければアイドルルールが最初に止めるのはどれで、いつかが続きます。どのボックスが枠を使っているかは sandbox_list でわかります。
新しいボックスを置けるホストがないときは、sandbox_start は 503 no box capacity right now: every host is full and more is being started. Try sandbox_start again in about 5 minutes; a box of size 4 or 8 can take a few minutes longer. No box was created by this call. で失敗します。すぐに別のホストが起動します。望まない限り size を小さくする必要はありません。ParallelSandbox がボックスのイメージを切り替えた直後の約 3 分間は、代わりに the hosts are switching to a new box image, which takes about 3 minutes と返ります。
waitForCapacitySec(0〜1800)は、すぐ失敗せずに呼び出しの中で空きを待ちます。全ホストが満杯、ホストがイメージを切り替え中、またはプランの同時ボックス数が上限のとき、sandbox_startは 15 秒ごとに再試行して進捗(No room for the box yet: … Waited 45s of 5m0s; trying again in 15s.)を送り、ボックスが起動した時点でそれを返します。それまではボックスはありません。待てる長さは、クライアントが 1 回の呼び出しで待てると宣言した時間が上限です。stdio アダプターはホストのツールタイムアウト(PSBX_TOOL_TIMEOUT_SEC、既定 60 秒で 25 秒待てます。上げ方はsandbox_review)を宣言し、何も宣言しない接続は最大 25 秒です。待っても空かなかったときは、どれだけ待ってなぜやめたかをエラーで伝えます。- 呼び出しの結果が届かなかったとき(接続が切れた、またはクライアントが呼び出しを取り消して「中断」と表示した)、stdio アダプターがボックスを探します。結果が戻らなかった場合は、この会話がその
nameとgoalでさっき起動したボックスを返答で伝えるか、起動しなかったと伝えます。中断された後、同じnameとgoalで次にsandbox_startを呼ぶと、2 台目を起動せずに中断された呼び出しが起動したボックスを返します(それでも別に起動したいときはもう一度呼びます)。ほかのツールの結果の末尾にもそのボックスを知らせる一文が付きます。
すべてのツールとトピックはツールリファレンスに並んでいます。