テーマを切り替える

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

Easton editorial illustration: code bundle entering an isolated Docker sandbox and exiting through a guarded preview-URL portal

"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 rulesandbox123.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 を起動します。

  1. DNS が Traefik に解決されます(*.preview.example.com の wildcard DNS が必要)
  2. Traefik はその sandbox の route rule を見つけますが、container は stopped です
  3. Traefik catch-all が request を制御プレーンの wake handler に転送します
  4. 制御プレーンが sandbox container を起動し、warming page を返します
  5. 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=true label があるか
  • 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

    ステップ 1: 隔離モデルを決める

    信頼できる社内チームなら単一 host の Docker で始めます。不特定ユーザーや任意コードを扱うなら microVM、別 host、Kubernetes を選びます。
  2. 2

    ステップ 2: 制御プレーンを用意する

    sandbox のメタデータ、ライフサイクル操作、reaper、wake-on-request を持つ小さな Go service を用意します。
  3. 3

    ステップ 3: Traefik の discovery を設定する

    `exposedByDefault: false` で Docker provider を有効にし、公開したい sandbox container だけに labels を付けます。
  4. 4

    ステップ 4: 安定した preview URL を割り当てる

    `*.preview.example.com` のような wildcard DNS を使い、`{sandbox_id}.preview.example.com` を対象 container port へ route します。
  5. 5

    ステップ 5: workspace を永続化する

    各 sandbox を `SANDBOXED_DATA_DIR/workspaces/` または同等の host directory に保存し、`docker stop` で user files が消えないようにします。
  6. 6

    ステップ 6: リソース制限を入れる

    各 sandbox に memory、CPU、PIDs の上限を設定します。1 つの暴走 build が host 全体を止めないようにします。
  7. 7

    ステップ 7: 本番入口を閉じる

    Docker API を公開しません。API auth、TLS、preview link の access control、network separation、監査ログを追加します。
  8. 8

    ステップ 8: image と registry の運用を決める

    よく使う image を事前に pull し、必要なら Docker Hub に login します。高頻度作成には private registry や cache を使います。

FAQ

Dev Sandbox と通常の Docker Compose は何が違いますか?
Compose は固定された service 群を起動する道具です。Dev Sandbox の制御プレーンは、要求に応じて環境を作成、起動、停止、復帰、削除し、それぞれに安定した preview URL を渡します。外部 product backend から管理できる状態も記録します。
最初から Kubernetes を使わない理由は何ですか?
複数 node の scheduling、高可用性、標準 network policy、platform governance が必要なら Kubernetes が向いています。信頼できる社内チームで product loop を検証する段階なら、単一 Docker host で十分なことが多いです。
Docker container の隔離で不特定ユーザーの任意コードを実行できますか?
その用途では強い境界として扱うべきではありません。Docker socket を使う制御プレーンは信頼できる利用者向けです。不特定ユーザーの任意コードは microVM、専用 VM、gVisor、Kata、Firecracker、少なくとも tenant ごとに分けた host へ移してください。
preview URL は HTTPS 必須ですか?
ローカルの `*.localhost` 検証は HTTP で始められます。公開 preview domain では、token、form、業務データを扱う可能性があるため HTTPS を使うべきです。wildcard certificate を使うと sandbox ごとに証明書を発行する手間を減らせます。
idle stop 後に files は消えますか?
workspace が永続 host directory に保存されていれば消えません。`docker stop` は resource を解放しますが files は残ります。destroy と purge は別操作にし、container だけを消すのか workspace も消すのかを明確にしてください。
Docker Hub の rate limit は影響しますか?
影響します。sandbox を頻繁に作ると image pull が増えます。本番では必要に応じて Docker Hub に login し、よく使う image を pre-warm し、private registry や image cache を検討してください。

9分で読めます · 公開日: 2026年6月5日 · 更新日: 2026年7月14日

コメント

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

Easton BlogEaston Blog