環境: 自分の dev につなぐ

変更したサービスだけをボックスで動かし、それ以外はすでにある dev をそのまま使いたい。同じデータベース、同じ Redis、触っていない内部サービス。環境はそのための機能です。

環境は dev(または staging)一式を表し、次の 3 つでできています。

  • 接続: 自分のネットワーク内で動かすコネクタ。コネクタから ParallelSandbox へ外向きに接続するので、ファイアウォールで受信を許可する必要はありません。ボックスが接続できるのは、接続に登録した host:port だけです。1 つの環境に複数の接続を持て、ネットワーク(VPC、オフィス)ごとに 1 つ作ります。それぞれに専用のトークンとアドレス一覧があります。
  • サービス設定: この環境での各サービスの環境変数一式。AWS ECS から取り込むか .env をアップロードします。ボックス内では /work/.sbx/env/<サービス>.env と .sh になります。
  • externalBaseUrl: 変更していない HTTP サービスの URL(任意)。sandbox_start の同名パラメータと同じです。

エージェントが environment を指定してボックスを起動すると、ボックス内で db.internal:5432 に接続するとコネクタ経由であなたのデータベースにつながります。コンテナからも同じです。変更したサービスは dev で動いているときとまったく同じ設定で起動できます。

ボックスがアドレスにつながる仕組み

  1. 起動時に、ボックスは環境のすべての接続からアドレスを受け取ります。各ホスト名はボックス内に専用のアドレス(198.18.0.0/15 から割り当て)を持ち、ボックスの DNS はそのアドレスで答えます。その host:port(IP アドレスなら IP:port)への接続は、boxd がそのアドレス用に開く入口に転送されます。boxd はアドレス自身のポートでは待ち受けないので、ポート 80 のアドレス(内部ロードバランサーなど)もほかと同じように使え、ボックス内のプログラムが同じポート番号で待ち受けてもぶつかりません。ボックスで自分の Postgres を 5432 で動かしても、localhost:5432 は自分のもの、db.internal:5432 はこれまでどおりあなたのネットワークに届きます。
  2. ボックス内のプログラムが接続すると、ParallelSandbox はその正確な host:port を登録している接続(ネットワーク)に接続を渡し、その接続のコネクタがあなたのネットワーク内でつなぎます。2 つの接続が同じアドレスを登録している場合は、名前の順で先の接続が使われます。各アドレスは、そこに届く接続に登録してください。
  3. 名前はコネクタがあなたのネットワーク内で解決するので、プライベート DNS(Route 53 のプライベートホストゾーン、Cloud Map、オフィスの DNS)はサービスから使うときと同じように動きます。
  4. 既定のブリッジ、ユーザー定義の Docker ネットワーク、docker compose のコンテナも、ボックス自身のプログラムと同じようにこれらのアドレスに届きます。ボックスの規則は両方に適用され、ボックス内の Docker の DNS はボックスのリゾルバーに転送します。
  5. 通信は最初から最後まで素の TCP です。サービスへの TLS は途中で終端されないので、証明書は dev と同じように扱われます。
  6. ボックスが接続を通して送るデータは、そのボックスの外向き通信(box_egress、クレジット)として数えます。あなたのネットワークから返ってくるデータは計量しません。
  7. アドレスはパブリックなホストでもかまいません(例:api.partner.example:443)。ボックスはコネクタを通してそこに届くので、通信はあなた自身のネットワークから、そのパブリック IP で出ます。決まった IP アドレスしか受け付けない外部サービスにはこの方法で届きます。ボックス自身のインターネットへの通信は、動いているホストのパブリック IP から出るので、ホストごとに違い、ホストの入れ替わりで変わります(その他の事実)。

ボックスの services に宣言した名前が環境のアドレスでもある場合、その名前はボックスを指します(ボックスから使うを参照)。同じ host:port の自分の別のボックスへのリンクも、そのアドレスより優先されます(別のボックスへのリンク)。あなたのネットワークからボックスに接続を開く手段はありません。dev に残るサービスは dev 上のものを呼び続けます。

登録していないものには届きません。その現れ方はそれぞれ違います(ボックスで確認済み):

  • 登録したプライベート IP アドレスに、登録していないポートで接続すると、登録していないものと同じく応答がありません。接続はタイムアウトし、private-endpoints.log にも何も書かれません。
  • 登録したホストやボックスで宣言した名前に、登録も宣言もしていないポートで接続すると、ボックス内にとどまります。ボックスでそのポートを 0.0.0.0 で待ち受けるものが応答し、何もなければ接続はすぐに拒否されます。
  • 登録していない(宣言もしていない)ホスト名は、ParallelSandbox のリゾルバーが引きます。答えるのは公開 DNS だけで、あなたのプライベートゾーンは知りません:
    • あなたのプライベート DNS にだけある名前(Cloud Map、Route 53 のプライベートホストゾーン、オフィスの DNS、プライベート API Gateway の <api-id>.execute-api.<region>.amazonaws.com)は解決されません:Could not resolve host、no such host、NXDOMAIN。それに届く接続に、ダイヤルされるポート(API Gateway なら 443)と一緒に登録すると、コネクターがあなたのネットワークで解決します;
    • 公開 DNS がプライベート IP に解決する名前(RDS のエンドポイントや internal-….elb.amazonaws.com のロードバランサーなど)は解決され、接続は登録していないプライベート IP と同じくタイムアウトします。届かせるには登録してください;
    • 公開された名前は通常どおり解決され、インターネットに直接出ます。
  • 登録した IP アドレスはボックスの中で捕まえられます。boxd の規則が、その IP とポートへの接続を、ボックスのプログラムからでもコンテナからでも、その接続の入口に送ります。
  • 登録していないプライベート IP アドレスは応答しません。接続はタイムアウトし、private-endpoints.log にも何も書かれません。
  • ポート 80 は扱いが違います。ポート 80 で登録したアドレス自体はほかと同じように使えますが、登録したホストや宣言した名前のうち、ポート 80 では登録も宣言もしていないものに 80 で接続すると、boxd のシーンの入口に届き、502 nothing 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 が返ります。2026-09-24 20:30 UTC より前に起動したボックスでは、最初に宣言したサービスに届きます。

設定

環境はエージェントが REST(下記)で用意し、結果を sandbox_environments で読みます。専用の画面はありません。自分たちのネットワークで人が必要なのは 2 つだけです。コネクタを動かすことと、AWS を使う場合のロール作成です。順番は次のとおりです。

  1. 環境を作る: POST /v1/environments に dev などの名前を渡します。エージェントはボックス起動時にこの名前を使います。
  2. ネットワークごとに接続を追加: POST /v1/environments/{env}/connections。応答に docker run の 1 行 runCommand が入っています。トークンが表示されるのはこの一度だけです(再発行のときにもう一度)。そのネットワーク内で、データベースやサービスに接続できるマシンで実行してください(後述の「コネクタ」)。数秒で sandbox_environments にその接続が online と出ます。
  3. 接続ごとにアドレスを設定: PUT /v1/environments/{env}/connections/{id}/endpoints に一覧全体を送ります。1 件が host:port で、サービス設定での書き方どおりにします。例: mydb.xxxx.ap-northeast-1.rds.amazonaws.com:5432、cache.internal:6379、10.0.1.5:8080。1 行に 1 つ host:port を、サービス設定での書き方どおりに入力します。例: mydb.xxxx.ap-northeast-1.rds.amazonaws.com:5432、cache.internal:6379、10.0.1.5:8080。サービス設定を取り込んだら、GET /v1/environments/{env}/suggested-endpoints が設定内で見つかったアドレスを返します。対象は値の中の host:port、URL(ポートがなければスキームの既定:postgres 5432、mysql 3306、redis 6379、mongodb 27017、amqp 5672、http 80、https 443)、そして対応する *_PORT がある *_HOST 変数のうち、ホストがプライベート IPv4(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)か、.internal、.local、.lan、.corp、.svc、.cluster.local、.rds.amazonaws.com、.cache.amazonaws.com、.es.amazonaws.com、.mq.amazonaws.com、.docdb.amazonaws.com、.redshift.amazonaws.com で終わるものです。それ以外、たとえば内部ロードバランサーの internal-*.elb.amazonaws.com や自社ドメインのプライベートゾーンは手で追加します。変更するかもしれないサービスの内部名も登録してください。変更しないボックスは dev のものに届き、sandbox_start でその名前を宣言したボックスはそれを引き取ります。
  4. サービス設定: AWS から取り込む(POST .../services/import)か、.env をアップロード(PUT .../services/{name})します。どちらも後述します。

REST で設定する

ここまでのことはすべて https://api.parallelsandbox.com の REST で、Authorization: Bearer <トークン> を付けて呼びます。トークンはアカウントの psbx_ API キー、または接続済みクライアントの OAuth アクセストークン(1 時間で失効。キーの入手方法はREST)です。MCP ツール sandbox_environments は読み取り専用で、環境、接続、到達できるアドレスを一覧します。作成と変更は REST です。{env} は環境の名前、{id} は GET /v1/environments/{env} にある接続の id です。設定の値は書き込み専用で、読むと変数名だけが返ります。

リクエスト 本文 返り値
GET /v1/environments { "environments": [...] }。それぞれ下と同じ形
POST /v1/environments { "name": "dev" }、任意で externalBaseUrl。名前は小文字の英字、数字、ハイフンで 1〜32 文字 環境:name、externalBaseUrl、awsRoleArn、awsRegion、connections[](id、name、online、sessions、lastSeenAt、connectorVersion、endpoints)、services[]
GET、PUT、DELETE /v1/environments/{env} PUT:externalBaseUrl、awsRoleArn、awsRegion のどれでも(ロールにはリージョンが必要。"" で消去) 環境。DELETE は { "ok": true }
POST /v1/environments/{env}/connections { "name": "office" } connection、token、runCommand(コネクタの docker run の行)。トークンが出るのはここと再発行のときだけ
POST /v1/environments/{env}/connections/{id}/token 新しい token と runCommand。古いトークンはすぐ無効になり、それを使うコネクタは切断される
DELETE /v1/environments/{env}/connections/{id} { "ok": true }
PUT /v1/environments/{env}/connections/{id}/endpoints { "endpoints": [{ "host": "mydb.internal", "port": 5432 }] }。一覧全体で、最大 100 件 接続
GET /v1/environments/{env}/suggested-endpoints { "endpoints": [...] }。サービス設定の中に見つかったアドレス
GET /v1/environments/{env}/services { "services": [...] }。name、source(dotenv または aws-ecs)、keys、notable(まず確かめるべき接続・モード・権限の設定。sandbox_environments と同じ)、sourceRef(aws-ecs ならその ECS サービスが動かしていた image 付き)、syncedAt
PUT /v1/environments/{env}/services/{name} { "dotenv": "KEY=value\n…" } サービス
POST /v1/environments/{env}/services/import { "region", "cluster", "service" }。タスクにコンテナが複数あれば container、name の既定は service サービス
POST /v1/environments/{env}/services/{name}/sync ECS から取り込み直したサービス
DELETE /v1/environments/{env}/services/{name} { "ok": true }
GET、PUT、DELETE /v1/aws PUT:{ "roleArn", "region" } 取り込みに使う読み取り専用ロール。信頼させる externalId と、CloudFormation でワンクリック作成する quickCreateUrl 付き
GET /v1/aws/ecs/services?region=… そのロールから見える ECS サービス

たとえば、環境 1 つ、接続 1 つ、サービス 1 つの設定を作る例:

A=https://api.parallelsandbox.com/v1; H="Authorization: Bearer $PSBX_TOKEN"   # API キーか OAuth アクセストークン
curl -s -H "$H" -X POST $A/environments -d '{"name":"dev"}'
curl -s -H "$H" -X POST $A/environments/dev/connections -d '{"name":"aws"}'    # token と runCommand はこの 1 回だけ
curl -s -H "$H" -X PUT $A/environments/dev/connections/<id>/endpoints -d '{"endpoints":[{"host":"mydb.internal","port":5432}]}'
jq -Rs '{dotenv: .}' api.env | curl -s -H "$H" -X PUT $A/environments/dev/services/api --data-binary @-

コネクタ

docker run -d --name parallelsandbox-connector --restart=always \
  -e PSBX_CONNECTOR_TOKEN=psbx_conn_... \
  public.ecr.aws/b2n6a1j1/connector:latest
環境変数 説明
PSBX_CONNECTOR_TOKEN 必須。接続を追加したときのトークン
PSBX_URL ParallelSandbox の API。既定は https://api.parallelsandbox.com
PSBX_SESSIONS ParallelSandbox へ同時に張る接続数。既定 2、1〜8
PSBX_ALLOW 任意。このコネクタで許可する接続先(カンマ区切り): db.internal:5432,*.svc.local:*,10.0.0.0/8:*
  • 接続先に届く場所で動かしてください。サービスと同じ VPC、Kubernetes クラスタ、ホストなどです。AWS なら、サービスと同じサブネットとセキュリティグループで Fargate タスク 1 つの ECS サービスにするのが手軽です(コマンドはチーム構成にあります)。
  • 外向きに api.parallelsandbox.com:443(TLS 上の WebSocket)へ接続するだけです。HTTP プロキシがある場合は通常どおり HTTPS_PROXY を設定してください。
  • 同じトークンで複数動かせます。どれか 1 つが接続中ならボックスから接続できます。ParallelSandbox の新バージョン公開時、コネクタは自動で新しいマシンに移ります。新しい接続は新しいマシンを使い、すでに開いている接続は古いマシンが停止するまで(最長 1 時間)そのまま使えます。データベースの接続プールは自動で再接続します。
  • 接続できる範囲は接続に登録したアドレスで決まり、まず ParallelSandbox 側で制限されます。PSBX_ALLOW を使うと自分の側でも制限できます。
  • コネクタはコンテナイメージ(public.ecr.aws/b2n6a1j1/connector、linux/amd64 と linux/arm64)としてだけ配布していて、単体のバイナリはダウンロードできません。Docker のないホストでは、Podman など別の OCI ランタイムで同じ -e 変数を付けて動かすか、ECS や Kubernetes で動かしてください。
  • トークンが漏れたときや管理者が変わるときは、POST /v1/environments/{env}/connections/{id}/token を呼びます。古いトークンはすぐに無効になり、動いているコネクタは切断されるので、新しいトークンで再起動してください。

規模と冗長化

  • ネットワークごとに 1 つのコネクタで、誰が起動したかにかかわらずアカウントのすべてのボックスに対応できます。ボックスがあなたのアドレスに開く接続は、それぞれコネクタのセッション(ParallelSandbox への WebSocket 接続)の中のストリームで、別の接続にはなりません。
  • PSBX_SESSIONS(既定 2、1〜8)は 1 つのコピーが開いておくセッションの数です。2 つ以上あれば、ParallelSandbox の再デプロイ中も別のセッションが残ります。
  • 冗長化には、同じトークンで別のホストや別のアベイラビリティゾーンにもう 1 つコピーを動かします。ParallelSandbox はすべてのコピーのセッションを 1 つにまとめて使います。新しいボックスの接続は、それを受けた ParallelSandbox のマシンが持つセッション、次に別のマシンが持つセッション、最後にデプロイで移動中のセッションを使います。どのコピーも接続に登録したすべてのアドレスに届く必要があります。選ばれたコピーが接続先に届かないと、別のコピーを試さずにボックスへすぐ 502 が返ります。
  • コネクタはストリームと接続先の間でバイトを写すだけなので、いちばん必要なのはネットワークです。AWS では Fargate タスクの CPU とネットワークのメトリクスを CloudWatch で見て、高ければ CPU を増やすか、コピーをもう 1 つ動かしてください。
  • ボックスが凍結または停止すると、ParallelSandbox はその接続を 1 分ほどで閉じるので、コネクタのストリームを占有し続けることはありません。
  • 環境とコネクタは ParallelSandbox では無料です。ボックスが接続を通して送るデータはそのボックスの外向き通信として数えます(クレジット)。コネクタ自身の計算資源やネットワーク(Fargate タスク、NAT ゲートウェイ、オフィスのホスト)は、あなたのクラウドやデータセンターの請求になります。

AWS からサービス設定を取り込む

ParallelSandbox は、あなたが許可した読み取り専用ロールで ECS サービスの現在のタスク定義を読みます。environment はそのまま、secrets が参照する SSM パラメータと Secrets Manager の値(:json-key を含む)は解決して取り込みます。S3 上の environmentFiles は読めないため、「取り込めなかったもの」に表示されます。設定は、別の名前を付けない限り ECS サービスの名前で保存され、その名前が /work/.sbx/env/<サービス>.env の <サービス> になります。

S3 の environmentFiles のファイルも使うには、その内容を .env として別の名前(例:api-files)でアップロード(PUT .../services/{name})し、サービスに両方のファイルを渡します。ECS と同じくタスク定義自身の値が優先されるよう、そちらを先に置きます:docker run --env-file /work/.sbx/env/api-files.env --env-file /work/.sbx/env/api.env ...、または . api-files.sh && . api.sh。取り込んだサービスと同じ名前でアップロードすると、取り込んだ設定が置き換わります。

AWS アカウントを接続します(アカウントごとに 1 回、REST で)。

  1. GET /v1/aws が、ロールが信頼すべき externalId と quickCreateUrl を返します。AWS アカウントを持つ人がそれを開くと、パラメータ入力済みのスタック作成画面が AWS コンソールに出るので、そのまま作成します。自分でロールを作る場合は、arn:aws:iam::580360261327:root を信頼し、条件 sts:ExternalId にその externalId を指定します。権限は ecs:ListClusters、ecs:ListServices、ecs:DescribeServices、ecs:DescribeTaskDefinition、ssm:GetParameters、secretsmanager:GetSecretValue と、SecureString や独自 KMS キーで暗号化したシークレット用の kms:Decrypt です。テンプレート: https://parallelsandbox-releases.s3.ap-northeast-1.amazonaws.com/releases/aws/readonly-role.yaml
  2. PUT /v1/aws に { "roleArn": "<スタックの Outputs の RoleArn>", "region": "<サービスのリージョン>" } を送ります。ParallelSandbox が実際に一度サインインし、失敗した場合はエラーで理由を返します。

その後、GET /v1/aws/ecs/services?region=… でロールから見えるサービスを一覧し、POST /v1/environments/{env}/services/import({ "region", "cluster", "service" })で 1 つ取り込みます(読めなかったものは sourceRef.skipped に入ります)。新しいタスク定義をデプロイしたら、POST /v1/environments/{env}/services/{name}/sync でもう一度読み込みます。

設定値は暗号化して保存され、ボックスが割り当てられた時点でそのボックスにだけ渡されます。エージェントに見えるのは変数名だけです。

ECS 以外で動くサービスの設定

ほかの場所(Kubernetes、VM、サーバー上の docker compose)で動くサービスは、設定を KEY=value の行にして PUT /v1/environments/{環境}/services/{サービス} に { "dotenv": "KEY=value\n…" } として送ります。ボックスはインポートしたものと同じく /work/.sbx/env/<サービス>.env と .sh として受け取ります。ファイルは設定が実際にある場所から作ります。たとえば Deployment の env と参照している ConfigMap や Secret、systemd ユニットの EnvironmentFile、compose の env_file です。アップロードした設定は同期されないので、設定が変わったらもう一度アップロードしてください。

ボックス内の AWS 権限

サービスの AWS 権限は環境変数ではなく、AWS が実行中のマシンに渡す ID(ECS の task role)であることがほとんどです。サービスをボックスに移すとその ID はなくなるので、S3 や SQS を使う機能は失敗します。

環境にはボックス用のロールを指定できます。OIDC を使う、GitHub Actions や Vercel が AWS につなぐのと同じ方式です。

  1. GET /v1/environments/{env} が、この環境の ID(awsSubject、つまり tenant:<アカウント>:env:<環境>)と、ロールをワンクリックで作る CloudFormation リンク awsBoxRoleQuickCreateUrl を返します。
  2. AWS でロールを作り、ボックスに必要な権限を与えてから、PUT /v1/environments/{env} に { "awsRoleArn": "<ロール ARN>", "awsRegion": "<リージョン>" } を送ります。
  3. それ以降に起動したボックスでは AWS_CONTAINER_CREDENTIALS_FULL_URI(ECS と同じ、ボックス内の認証情報アドレス)、AWS_REGION、AWS_DEFAULT_REGION が設定済みです。AWS の SDK と CLI がそれを読み、認証情報の取得と更新を自分で行います。コードの変更は不要です。コンテナも docker run --env-file /work/.sbx/env/<サービス>.env で起動すれば同じです。ボックスで aws sts get-caller-identity を実行すると assumed-role/<ロール>/psbx-<ボックス id> が表示されます。

ParallelSandbox はボックスに 10 分間有効な ID を署名し、ボックスがそれを AWS で 1 時間有効な認証情報に交換します。長期の鍵はどこにも存在せず、ParallelSandbox はあなたのアカウントで動ける権限を一切持ちません。ロールを削除するか信頼ポリシーを変えればすぐ無効になり、ロールは指定した環境だけを信頼します。

ボックスで自社の ECR からイメージを pull するには(変更していないサービスの dev で動いている版など)、ロールに ecr:GetAuthorizationToken と、対象リポジトリへの ecr:BatchCheckLayerAvailability、ecr:GetDownloadUrlForLayer、ecr:BatchGetImage が必要です(AWS 管理ポリシー AmazonEC2ContainerRegistryReadOnly に 4 つとも含まれます)。そのうえでボックスで aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com を実行し、docker run --env-file /work/.sbx/env/<サービス>.env … <image> で動かします。権限がなければ、ログインは AccessDeniedException … is not authorized to perform: ecr:GetAuthorizationToken で失敗します。

ボックスから使う

sandbox_environments {}
sandbox_start { "name": "返金フローの改修", "goal": "dev のデータベースとサービスを使って api の返金フローを改修する。api から行った返金が dev に記録されれば完了", "environment": "dev", "services": [{ "name": "api", "port": 8080 }] }

sandbox_start の結果に environment が加わります。reachable はボックスから接続できるプライベートアドレス、connectorOnline はコネクタが接続中かどうか(接続が複数ある場合は、どれか 1 つでも接続中かどうか)、connections[] は接続ごとの name、online、sessions、lastSeenAt、connectorVersion、reachable(その接続を通るアドレス。ボックスが自分で動かす名前は除く)、services は各サービスの設定ファイルのパスです。止まっている接続があれば、next がその接続とアドレスを挙げます。どれも接続していなければ、コネクタがオフラインだと伝えます。sandbox_status も同じ environment を返し、呼ぶたびに読み直すので、ボックスの起動後に止まった接続もわかります。

環境に externalBaseUrl があると、ボックスはそれを引き継ぎ、宣言したすべてのサービスは external モードで始まります。name:port への HTTP は externalBaseUrl に転送されます。ボックスで動かすサービスは box に切り替えてください。boxd はそのポートを占有しないので、プロセスがすでに動いているかどうかは関係ありません。

sandbox_wire { "id": "<id>", "service": "api", "mode": "box" }

変更したサービスを設定付きで起動します。

sandbox_exec { "id": "<id>", "cmd": "cd api && docker build -t api . && docker run -d --name api --env-file /work/.sbx/env/api.env -p 8080:8080 api", "note": "dev の設定で api をビルドして起動する" }
sandbox_exec { "id": "<id>", "cmd": "cd api && . /work/.sbx/env/api.sh && go run ./cmd/api", "background": true, "note": "dev の設定で api を動かす" }
  • .env は docker run --env-file の形式で値はそのまま、.sh は export KEY='VALUE' で直接動かすプログラム用です。複数行の値は .sh にだけ入ります。ボックスで 1 つの値だけ変えたい場合は、--env-file の後ろに -e KEY=value を付けます。後ろのものが優先されます。

  • どの設定が大事か:1 つのサービスの設定は名前が 100 を超えることもあります。sandbox_start と sandbox_status はサービスごとに全部の名前ではなく keysCount と notable を並べます。connection は環境そのものを指す設定(データベース変数が DATABASE_URL でないときはここで見つかります)、mode(SERVER_MODE、STAGE、*_BYPASS)は dev の値で、認証やチェックを切っていることがあり、その経路のテストが間違った理由で通ることがあります。permission(管理者リスト、allowlist)は本番ではなく dev 自身のリストです。テストがモードに左右されるときは -e や export で上書きします。全部の名前は sandbox_environments にあります。

  • フロントエンドには、どのバックエンドを呼ぶかの独自の切り替え(CUBELV_ENV=dev、VITE_API_URL、STAGE)があることが多く、誰かがフロントエンドを環境のサービスとして登録していない限り、どのサービスの設定にも入っていません。プロジェクトの README で dev につなぐ方法を確かめ、開発サーバーの起動やビルドのときに設定してください。さもないとテストしているページは本番と話します。

  • 直接動かすプログラムでは、.sh を読み込んでから、ボックスで変えたい値をそのコマンドだけ、または export で上書きします。

    . /work/.sbx/env/api.sh && DATABASE_URL="$(psbx-testdb url)" ./bin/backfill
    

    .env を読み込まないでください(set -a; . api.env)。値が引用符で囲まれていないので、空白や引用符を含む値ではシェルがエラーで止まり、$ を含む値は何も言わずに変わってしまいます。

  • version で宣言した公開バージョンには、それを公開したときのサービスの設定ファイル /work/.sbx/env/<サービス>.env が、何も指定しなくても渡されます(公開したバージョンを動かす)。

  • docker compose の env_file は値の中の $ を展開します。値に $ が含まれる場合は docker run --env-file を使うか、.sh を source してから environment: で渡してください。

  • ボックス内のプログラムやコンテナは、いつものホスト名のまま接続できます。設定の変更は不要です。名前はボックス内でそれぞれのアドレスに解決され、boxd が各接続を ParallelSandbox とコネクタ経由でつなぎます。

  • 変更したサービスをほかのサービスが内部ホスト名で呼び出す場合(ルーターが api.svc.local:8080 に転送するなど)は、sandbox_start でその名前を services に宣言してください: [{ "name": "api.svc.local", "port": 8080 }]。port は呼び出し側が接続するポートです。プロセスが別のポートで待ち受けるならそれを targetPort に書きます。内部ロードバランサーのように呼び出し側がポート 80 で接続する場合は必須です: [{ "name": "internal-lb.svc.local", "port": 80, "targetPort": 8080 }]。ボックス内ではその名前がコネクタではなくボックス自身を指し、reachable にも含まれません。変更していないものはそのまま dev につながります。dev で動いている呼び出し側は dev のものを呼び続けるので、テストに必要なら呼び出し側もボックスで動かしてください。あとから sandbox_wire(自分のプロセスなら port、公開したバージョンなら version)で宣言した名前は、sandbox_status → health.features に take-over があるボックス(2026-09-25 12:40 UTC 以降に起動したものはすべて)では、その場で引き取られます。アドレスはそのままで、コネクタを通って開いていた接続は閉じられ、プログラムはボックスのコピーに接続し直します。古いボックスではそうした名前はコネクタ経由のままなので、そこでは sandbox_start で宣言してください(リンクや環境のアドレスを引き取る)。

  • 設定付きで起動した変更サービスは、dev の本物のデータベースにつながります。マイグレーションも書き込みもそこに入ります。テストには psbx-testdb up <名前> がボックス内にまっさらな Postgres 16 を起動し、接続情報を /work/.sbx/testdb/<名前>.env に書きます(DATABASE_URL、TEST_DATABASE_URL など、export の行。--as 変数名 でもう 1 つ増やせます)。オプションはチーム構成にあります。127.0.0.1 で待ち受けるので、コンテナからは --network host が必要です。サービスを専用のデータベースで動かすなら、コンテナで 1 つ起動し、--env-file の後ろで設定を上書きします(-e DATABASE_URL=…)。コマンドはチーム構成にあります。dev のデータを書き込みの心配なく読むには psbx-ro-psql <サービス> を使います。そのサービスの Postgres 設定(postgres:// のものが 1 つならそれ、または指定した変数)で、default_transaction_read_only と 60 秒の statement_timeout を付けて接続し、出力の接続文字列とパスワードを伏せます:psbx-ro-psql market-data -c 'select count(*) from quotes'。うっかりを防ぐもので、書こうとする人は防げません(SET で外せます)。psbx-testdb up --redis は同じ要領でテスト用の Redis を(REDIS_URL)、psbx-testdb up --s3 は S3 互換サーバーを起動します(AWS_ENDPOINT_URL、キー、AWS_REGION、S3_BUCKET)。

  • 呼び出す側がホスト名ではなく、自分のレジストリから得た IP に直接つなぐ場合(ゲートウェイが Redis の名簿から worker の IP を取るなど)は、services にアドレス範囲を宣言します: [{ "name": "172.16.0.0/16", "port": 9090 }]。ボックス内(コンテナも含む)からその範囲のそのポートへの接続は、ボックス内のそのサービスの targetPort に届きます。ほかのポートはそのままです。プライベートアドレスのみ: 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10。宣言できるのは sandbox_start だけです。同じポートでその範囲に入る環境のアドレスは、コネクタを経由しなくなります。

チーム構成に、2 つのネットワーク、複数の変更サービス、Sentry、変更したサービスを別のボックスから使うところまでを通した例があります。

トラブルシューティング

  • sandbox_environments:環境ごとの reachable、connectorOnline(どれか 1 つの接続が接続中なら true)、connections[](接続ごとの online、sessions、lastSeenAt、connectorVersion と、その接続が運ぶアドレス)。まず connections[] を見てください。connectorOnline が true のままでも、止まっている office はここでわかります。ボックスが使っている環境なら、sandbox_status → environment.connections[] でも同じものが見られます。1 つのネットワークの最後の確認は、今でもボックスからそのネットワークのアドレスの 1 つに本物のクライアント(curl、pg_isready、psql、redis-cli はボックスに入っています。TCP 接続だけなら boxd が先に受け付けるので必ず成功します)で接続し、health.privateEndpoints でそのアドレスを見ます。
  • sandbox_status の health.privateEndpoints: アドレスごとに、boxd がボックス内で開く入口(localIp、localPort)、現在の接続数、開いた接続数、失敗回数、最後の失敗の理由。
  • /work/.sbx/logs/private-endpoints.log: 接続に失敗するたびに 1 行。
  • コマンドが開いた環境アドレスへの接続が失敗すると、sandbox_exec の結果に privateEndpointErrors と privateEndpointHint が付きます。health.privateEndpoints やログと同じ理由を、そのコマンドの間の分だけ並べます。プログラム自身には接続のリセットしか見えないからです。
  • コネクタ経由が遅い:ボックスがそこを通して開く接続は、自分のネットワーク内の経路に加えて、少なくとも connections[].rttMs(ParallelSandbox からコネクタまでの往復。ハートビートごとに測ります)だけ余分にかかります。経由して測った時間は互いの比として比べ、ネットワーク内で測った時間とは比べないでください。
  • 403: そのアドレスが環境のどの接続にも登録されていない状態です。PUT /v1/environments/{env}/connections/{id}/endpoints で登録し直してください。ボックスの起動後に追加したアドレスは、そのボックスには一切知られていません(ボックス内にその名前のアドレスがない)。新しいボックスを起動してください。
  • 社内の名前(プライベート API Gateway の <api-id>.execute-api.<region>.amazonaws.com など)が Could not resolve host(no such host、NXDOMAIN)になる:その名前は環境に登録されておらず、パブリック DNS も知りません。それに届く接続に、ポートと一緒に追加してください。PUT .../endpoints はリスト全体を置き換えるので、GET /v1/environments/{env} で今のリストを取り、新しいアドレスを加えて送り返します。そのあと新しいボックスを起動してください。
  • 503: そのアドレスを登録している接続のコネクタがオフラインで、メッセージにその接続の名前が入ります(the connector "office" of this environment is offline; …)。コネクタのログ(docker logs parallelsandbox-connector、または ECS タスクのログ)を確認してください。ボックス内のクライアントには、接続がいったん受け付けられてからリセットされたように見えます。
  • 502: コネクタから接続先に届きません。コネクタのマシンとそのアドレスの間の DNS、ルート、セキュリティグループを確認してください。
  • ボックスが復帰した直後のエラー:boxd は、凍結前から開いていたあなたのアドレスへの接続を、復帰後 15 秒ほどのうちに閉じます。プログラムは、いつまでも応答しない接続ではなくエラーを受け取ります。データベースの接続プールはたいてい自分で接続し直します。長く接続を保つクライアントは再試行が必要です。

制限

  • TCP のみです。アドレスはポート付きの正確なホスト名か IPv4 で、ワイルドカードや範囲は使えません。
  • 1 つの接続に最大 100 アドレス、1 つのボックスにすべての接続を合わせて最大 200 アドレスです。
  • ボックスは割り当て時に環境のアドレス、設定、externalBaseUrl、AWS ロールを受け取ります。その後に追加したアドレスや変更した設定は、新しいボックスにだけ反映されます。アドレス、接続、環境の削除は即時に反映され、起動中のボックスからも接続できなくなります。