セルフホスト Dev Sandbox:Docker と Go で preview URL 付き環境を作る

"sandboxed README は Go 制御プレーン、Docker、Traefik、SQLite、preview URL、idle stop、本番 hardening の境界を説明しています。"
"Docker の resource constraints 文書は、明示的に設定しない限り container に CPU や memory の制限がないことを説明しています。"
"Docker Sandboxes の文書は、microVM、独立 Docker daemon、network isolation、credential isolation をより強い security model として示しています。"
"Traefik Docker provider は Docker labels から route 設定を取得し、Host rule で container service へ転送できます。"
PR ごとに独立した preview 環境を作るなら、よくある選択肢は Vercel や Netlify です。ただ、cost を抑えたい、data を private network から出したくない、infra を自分で握りたい、という条件なら、単一 host の Docker と Go 制御プレーンでも代替できます。各 sandbox は独立した preview URL を持ち、resource には上限があり、security boundary も明示できます。Kubernetes も multi-node も不要です。
判断表 — どの条件でどの方式を使うか
「セルフホスト preview 環境」が必要になったとき、多くの人はまず shell script で docker run を並べるか、いきなり Kubernetes を考えます。先に判断表で切り分けます。
| 場面 | 推奨方式 | 理由 |
|---|---|---|
| 社内 team が 10 人未満で同時 preview し、trust boundary が team 内にある | 単一 host Docker + Go 制御プレーン | resource density を制御しやすく、構成が単純で、K8s cluster を運用しなくてよい |
| 社内 team が 20 人を超えて同時 preview する、または multi-node HA が必要 | K8s + Namespace isolation | 単一 host では足りず、node 間 scheduling と rolling upgrade が必要 |
| 外部ユーザーや不信頼コード実行(autonomous agent) | microVM(Docker Sandboxes / Firecracker) | Docker socket は host root 相当の権限であり、不信頼 workload と混在させられない |
| 単純な static preview で永続化が不要 | shell script + random port | 動くことは動くが、preview URL は安定せず、resource limit と security boundary が弱い |
判断基準は 3 つです。
team size:単一 host は 10 人以内の同時 preview に向いています。概算は sandbox 1 個あたり 512 MB RAM + 0.5 CPU。16 GB memory の host なら最大で 20 個前後の sandbox です。この密度を超えたら、K8s scheduling か microVM で node を分ける必要があります。
trust boundary:社内 member や信頼できる user なら Docker container で動かせます。ただし、外部の不特定ユーザーや autonomous agent に任意コードを実行させるなら、Docker socket 方式は安全ではありません。Docker 公式の Sandboxes は microVM isolation を使い、各 sandbox が専用 Docker daemon、filesystem、network を持ちます。host environment を信頼できない場面に向いています。
upgrade path:最初は単一 host Docker で始め、要件と resource density を検証します。同時実行が単一 host を超えたら、または multi-node HA が必要になったら K8s へ移ります。trust boundary が「社内 team」から「外部 user」に変わったら microVM へ移します。
tastyeffectco/sandboxes の README は、単一 host Docker 方式が AI app-builder、agent platform、coding playground に向いていると説明しています。これは microVM level の隔離ではありません。Go 制御プレーンが単一 host の Docker 上で container を作成し、preview URL を公開する方式です。
アーキテクチャ分解 — 制御プレーンの中核 component
単一 host Docker の preview 環境は、docker run を 1 回実行するだけでは足りません。lifecycle、route registration、resource cleanup を管理する制御プレーンが必要です。tastyeffectco/sandboxes の architecture は 6 つの module に分かれます。
Go 制御プレーン(sandboxd):container 内で動き、host の Docker socket と data directory を mount します。Docker CLI 経由で sandbox container の lifecycle を管理します。作成、起動、停止、削除です。すべての sandbox metadata は SQLite に保存され、source of truth になります。
Docker socket mount:Docker daemon にアクセスする入口です。sandboxd は /var/run/docker.sock を mount することで container を作成・管理する権限を得ます。ここが architecture 全体の権限境界です。Docker socket を mount した制御プレーンは host に対して強い権限を持ちます。
Traefik labels registration:各 sandbox container の起動時に、制御プレーンは Docker labels で Traefik route 設定を注入します。Traefik は reverse proxy として labels から route rule を見つけ、*.preview.example.com の request を該当 container に転送します。
SQLite metadata storage:各 sandbox には unique ID と対応 directory があります。metadata は SQLite に保存されます。workspace は SANDBOXED_DATA_DIR/workspaces/ 配下に置き、sandbox ごとに subdirectory を作って source code、config、artifact を保存します。
idle reaper と pressure reaper:idle reaper は sandbox container の idle time を確認し、threshold を超えたら container を停止して RAM を解放します。pressure reaper は host memory pressure を監視し、memory が厳しくなったときに一部 sandbox を停止して host OOM を避けます。この 2 つの reaper が resource cleanup の中核です。
wake path:idle reaper によって sandbox container が停止されたあと、preview URL への最初の access で起こします。Traefik の catch-all が request を制御プレーンに渡し、制御プレーンが container を起動して warming page を返します。container が ready になったら request を転送します。
最小構成は、Go 制御プレーン container、Docker socket mount、Traefik、SQLite、idle reaper です。local quick start には Docker Engine と Compose plugin が必要です。
Preview URL の実装
preview URL の要点は random port ではなく、安定した domain です。各 sandbox は独立した {sandbox_id}.preview.example.com を持ちます。
Traefik Docker provider の設定
Traefik は Docker provider によって container labels から route 設定を見つけます。設定例です。
# traefik.yml
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
exposedByDefault: false は、明示的に labels が付いた container だけを Traefik が検出する、という意味です。
Host rule と Docker labels
制御プレーンは sandbox container を作成するときに labels を注入します。例:
labels:
- "traefik.enable=true"
- "traefik.http.routers.sandbox123.rule=Host(`sandbox123.preview.example.com`)"
- "traefik.http.routers.sandbox123.entrypoints=websecure"
- "traefik.http.services.sandbox123.loadbalancer.server.port=3000"
Host rule は sandbox123.preview.example.com への request をその container に route します。loadbalancer.server.port は container 内の application が listen している port を指定します。
wake-on-request path
idle reaper によって sandbox container が停止されたあと、preview URL への最初の access が wake flow を起動します。
- DNS が Traefik に解決されます(
*.preview.example.comの wildcard DNS が必要) - Traefik はその sandbox の route rule を見つけますが、container は stopped です
- Traefik catch-all が request を制御プレーンの wake handler に転送します
- 制御プレーンが sandbox container を起動し、warming page を返します
- container が ready になったあと、Traefik は以降の request を container に直接転送します
catch-all の要点は、すべての sandbox route より低い priority の fallback router を置くことです。
labels:
- "traefik.http.routers.catch-all.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.preview.example.com`)"
- "traefik.http.routers.catch-all.priority=1"
- "traefik.http.routers.catch-all.service=wake-service"
sandbox route に match しない request は catch-all に落ち、制御プレーンが wake logic を処理します。
Security boundary — Docker socket の権限
Docker socket を mount することは、制御プレーンに host root 相当の力を渡すことです。これがこの architecture の security baseline です。社内 team と信頼できる user には向きますが、不信頼コード実行には向きません。
Docker socket の risk
Docker 公式文書は、daemon には attack surface があると説明しています。Docker API を不安全に公開すると、remote non-root user が host root access を得る可能性があります。/var/run/docker.sock を mount した container は Docker CLI によって container の作成、変更、削除ができます。host filesystem や network に access する container を作ることもできます。
つまり次の意味になります。
- 制御プレーン container は host に対して強い権限を持つ
- 制御プレーンと不信頼 workload を同じ host で混在させない
- sandbox container 内の code も、制御プレーンに影響できるなら host に間接的な影響を与えうる
不信頼 scenario の境界
単一 host Docker + Go 制御プレーンが向く場面:
- 社内 team member の preview 環境
- 信頼できる user 向け coding playground
- AI app-builder や agent platform の internal validation environment
向かない場面:
- 外部の不特定ユーザーによる任意コード実行
- host を信頼できない autonomous agent の本番実行環境
- 強い隔離が必要な multi-tenant platform
trust boundary が「社内 team」から「外部 user」に変わったら、Docker 公式 Sandboxes や Firecracker microVM へ移るべきです。Docker Sandboxes の security model は hypervisor isolation、独立 network、独立 Docker daemon、独立 filesystem、credential isolation を含みます。各 sandbox は host Docker daemon を共有する container ではなく、完全な microVM です。
Production hardening checklist
production deployment では次の境界を補います。
- network isolation:制御プレーンと sandbox container は専用 network で動かし、business network と混在させない
- API authentication:local quick start は認証なしの場合があります。本番では token などの認証を有効にする
- minimum exposure:preview URL は Traefik reverse proxy で公開し、Docker API port を直接公開しない
- TLS:preview URL には wildcard TLS certificate を使い、平文通信を避ける
- logs and monitoring:制御プレーンの API request と container lifecycle event を記録し、alert につなげる
さらに強い security boundary が必要なら、AI Agent sandbox の実践 guide で gVisor、Firecracker、Kubernetes の境界方式を比較します。
Resource limits — memory / CPU / PIDs
Docker container は default では resource constraint を持ちません。制限を付けないと、1 つの sandbox container が host RAM や CPU を使い切り、他の sandbox や host process に影響します。multi-tenant preview 環境では、container ごとの hard limit が最低条件です。
Docker の default behavior
Docker 公式文書は、container が kernel scheduler の許す範囲で host resource を使えると説明しています。--memory や --cpus を明示しない限り、container は空いている host resource を奪えます。
memory hard limit
--memory は container が使える memory の上限を設定します。例:
docker run --memory="512m" --memory-swap="512m" sandbox-image
--memory-swap は memory + swap の上限です。--memory-swap が --memory と同じなら、container は swap を使いません。
memory limit を超えると OOM killer が container process を殺すことがあります。host OOM が起こると、他の container や host process にも影響します。
CPU share limit
--cpus は container が使える CPU 数を設定します。例:
docker run --cpus="0.5" sandbox-image
container は最大 0.5 CPU 分の compute capacity だけを使えます。複数 sandbox が同時に動くとき、CPU share limit は 1 つの container が全 CPU を奪うのを防ぎます。
Process count limit
--pids-limit は fork bomb を防ぐために使います。例:
docker run --pids-limit=100 sandbox-image
container は最大 100 process まで作成できます。上限を超えると fork() は失敗します。
Compose configuration example
Docker Compose では deploy.resources.limits に設定します。
services:
sandbox:
image: sandbox-image
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
pids: 100
Compose の deploy.resources は Docker Swarm mode で有効です。単一 host Docker では --memory、--cpus、--pids-limit を手で渡すか、docker-compose --compatibility で実行します。
制御プレーンは sandbox container の作成時にこれらの limit parameter を注入すべきです。user の手動設定に依存しないほうが安全です。
Operations — image cache と Docker Hub rate limit
多くの sandbox を高頻度で作成・破棄すると、image pull が bottleneck になります。Docker Hub には pull rate limit と abuse rate limit があり、account type や plan によって policy が変わります。本番では、各 sandbox の image pull を毎回 public Docker Hub に任せる設計にしないほうがよいです。
Docker Hub rate limit
Docker 公式文書は、anonymous user、authenticated user、team account で pull rate limit が異なると説明しています。上限を超えると pull request は拒否されます。具体的な数字は policy とともに変わるため、本文では固定値を書かず、Docker Hub usage and limits を確認します。
multi-sandbox では次が問題になります。
- sandbox を高頻度で作ると、container start ごとに image pull が発生する
- idle reaper で停止したあと再起動すると、また image が必要になることがある
- 同じ image が複数 sandbox で繰り返し pull される
Image pre-warming と cache strategy
production environment では次の対策が必要です。
Image pre-warming:制御プレーン起動前に、よく使う image を local に pull しておきます。sandbox 起動時の pull 待ち時間を減らせます。
Private registry:よく使う image を private registry(Harbor、AWS ECR、GCP Artifact Registry など)に push します。制御プレーンは public Docker Hub ではなく private registry から pull します。
Docker Hub login:Docker Hub から pull する必要がある場合は、authenticated account を使って適切な pull allowance を得ます。Docker は production で anonymous pull に頼らず login することを推奨しています。
Image cache:Docker daemon は pull 済み image layer を cache します。ただし sandbox container を頻繁に削除・再作成する場合、cleanup が layer を消しすぎないようにします。
Internal registry acceleration
host が private network にある場合は、enterprise network の Docker pull timeout troubleshooting を参考に registry mirror や proxy を設定します。image pull は本番 platform の一部として扱います。
Troubleshooting checklist — preview URL に到達できない
preview URL が開かないときは、この 5 steps で確認します。
1. DNS は Traefik を指しているか
wildcard DNS 設定を確認します。*.preview.example.com の A record または CNAME は、Traefik が動く host IP を指しているべきです。
判定には dig または nslookup を使います。
dig sandbox123.preview.example.com
返ってくる IP は Traefik host IP である必要があります。他の address なら DNS から直します。
2. Traefik は container labels を検出しているか
Traefik Docker provider の設定と container labels を確認します。
判定には Traefik dashboard または logs を使います。
docker logs traefik-container | grep "sandbox123"
Traefik logs に sandbox123 の route rule が出ているはずです。出ていない場合は次を確認します。
traefik.enable=truelabel があるかexposedByDefault: falseの設定が正しいか- Traefik が Docker socket を正しく mount しているか
3. container は起動しているか
sandbox container の state を確認します。
判定には docker ps を使います。
docker ps | grep sandbox123
container が stopped なら、idle reaper が停止したか、wake path が restart に失敗した可能性があります。preview URL に access したとき、制御プレーンの wake handler が container を起動して warming page を返すべきです。wake path が失敗する場合は制御プレーン logs を確認します。
4. application の listen address
container 内 application の listen address と port を確認します。
判定には container に入って listen port を見ます。
docker exec sandbox123 netstat -tuln
application は 127.0.0.1:3000 ではなく 0.0.0.0:3000 で listen すべきです。Docker 公式文書は、127.0.0.1 または ::1 に bind された port は Docker host からしか access できず、外部 request が届かないと説明しています。
application が localhost にだけ listen している場合は、application config を変えるか、適切な network mode で container を動かします。
5. Port binding check
Traefik の設定 port と container application port が一致しているか確認します。
Traefik labels では次のようになっているかもしれません。
- "traefik.http.services.sandbox123.loadbalancer.server.port=3000"
container 内 application は 3000 port で listen している必要があります。Traefik が 3000 を指し、application が 8080 で listen しているなら request は失敗します。
port が一致しない場合は、Traefik labels または application config を修正します。
Conclusion
単一 host Docker と Go 制御プレーンで、セルフホスト preview 環境を作れます。各 sandbox は独立した preview URL を持ち、resource limit を設定でき、security boundary も明示できます。ただし適用条件はあります。社内 team、信頼できる user、小規模同時実行が前提です。trust boundary が外部の不特定 user に広がる場合や、同時実行が単一 host を超える場合は、microVM または K8s へ移ります。
中核 module を振り返ると、判断表は方式選択を速くし、architecture breakdown は制御プレーン、Traefik、SQLite、reaper の協調を示します。Preview URL は Traefik Host rule と Docker labels に依存します。security boundary では Docker socket が host root 相当であることを明示します。resource limit は multi-tenant 環境の最低条件です。image cache は Docker Hub rate limit に備える運用であり、troubleshooting checklist は preview URL が開かないときの切り分けに使えます。
次の進め方です。
- 社内 team の小規模 preview → 単一 host Docker + Go 制御プレーンで resource density を検証する
- 外部 user や high-risk scenario → Docker 公式 Sandboxes または Firecracker microVM へ移る
- self-hosted CI Runner → GitHub Actions self-hosted runner 実践 guide を参考に private infra を組み立てる
- preview 環境内の application deployment → Next.js Docker self-hosting 実践を参考に sandbox 内で application を動かす
セルフホスト Dev Sandbox MVP の作り方
単一 Docker host で preview URL 付き sandbox を検証し、内測前に必要な境界を入れる手順。
⏱️ 目安時間: 4 時間
- 1
ステップ 1: 隔離モデルを決める
信頼できる社内チームなら単一 host の Docker で始めます。不特定ユーザーや任意コードを扱うなら microVM、別 host、Kubernetes を選びます。 - 2
ステップ 2: 制御プレーンを用意する
sandbox のメタデータ、ライフサイクル操作、reaper、wake-on-request を持つ小さな Go service を用意します。 - 3
ステップ 3: Traefik の discovery を設定する
`exposedByDefault: false` で Docker provider を有効にし、公開したい sandbox container だけに labels を付けます。 - 4
ステップ 4: 安定した preview URL を割り当てる
`*.preview.example.com` のような wildcard DNS を使い、`{sandbox_id}.preview.example.com` を対象 container port へ route します。 - 5
ステップ 5: workspace を永続化する
各 sandbox を `SANDBOXED_DATA_DIR/workspaces/` または同等の host directory に保存し、`docker stop` で user files が消えないようにします。 - 6
ステップ 6: リソース制限を入れる
各 sandbox に memory、CPU、PIDs の上限を設定します。1 つの暴走 build が host 全体を止めないようにします。 - 7
ステップ 7: 本番入口を閉じる
Docker API を公開しません。API auth、TLS、preview link の access control、network separation、監査ログを追加します。 - 8
ステップ 8: image と registry の運用を決める
よく使う image を事前に pull し、必要なら Docker Hub に login します。高頻度作成には private registry や cache を使います。
FAQ
Dev Sandbox と通常の Docker Compose は何が違いますか?
最初から Kubernetes を使わない理由は何ですか?
Docker container の隔離で不特定ユーザーの任意コードを実行できますか?
preview URL は HTTPS 必須ですか?
idle stop 後に files は消えますか?
Docker Hub の rate limit は影響しますか?
9分で読めます · 公開日: 2026年6月5日 · 更新日: 2026年7月14日
Docker シリーズ: 導入、ネットワーク、エラー、運用ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。



コメント
GitHubアカウントでログインしてコメントできます