vast.ai で RTX 5090 を借りて ローカル LLM を動かす|2026-08 版
第 10 章

Jupyter・Instance Portal・ポート公開

ブラウザから使う。外に穴を開ける仕組みと、安全に閉じる方法

この章で分かること

ブラウザから使う入口は 3 本ある

インスタンスの実体は Linux の Docker コンテナ(= アプリと必要なソフトを一式まとめて箱に詰め、その箱ごと動かす仕組み)です。箱の中で Web アプリを起動しても、それだけでは手元のブラウザから見えません。箱の中と外をつなぐ道を自分で選ぶ必要があります。

道は 3 本あります。そしてどれが使えるかは、インスタンスを作るときの 2 つの選択で決まり、どちらも作成後には変えられません。1 つ目が launch mode(起動モード)で、Jupyter が入るかどうかと、vast が自動で開く内部ポートがこれで決まります。2 つ目が載せる Docker イメージで、Instance Portal が出るかどうかは launch mode ではなく「イメージが vastai/base-image 系かどうか」で決まります。この 2 つを混同すると「SSH モードにしたから Portal が出ない」のような誤った切り分けをしてしまいます。

launch mode(CLI のフラグ)自動で開く内部ポートブラウザからの入口
Jupyter(--jupyter)8080(+ SSH 用に 22)Jupyter 本体と Jupyter Terminal
SSH(--ssh)22無し。ssh -L で手元に引く
Entrypoint(--args / --entrypoint)無し(自分で -p を宣言)イメージが自分で用意したものだけ

表の SSH(= 遠くのコンピューターに安全にログインする仕組み。第 9 章)と Jupyter(= ブラウザ上で Python のコードを 1 かたまりずつ実行できるノートブック環境)は、どちらも起動時に vast がインスタンスへ注入するもので、Docker イメージが元から持っているものではありません。だから Entrypoint モードを選ぶと、どちらも入らずアクセス手段がゼロになります。

公式は最初は Jupyter モードを推奨しています(アクセスが最も簡単だから)。推奨テンプレートも通常は Jupyter モードで起動します。さらに vastai/base-image 系のイメージを積んだテンプレートでは、インスタンスカードの Open ボタンを押すと Instance Portal という案内ページが最初に出ます。

3 本の道は「速いか遅いか」ではなくどこを通って箱の中に入るかで違います。とくに決定的なのは「新しく穴を開ける道」と「すでにある道の中を通る道」の差です。ここを取り違えると、自分だけが使うつもりの WebUI を世界中から開ける状態で放置することになります。

手元 通り道 1 台のインスタンス 手元のブラウザ Portal の Cloudflare トンネル 穴を開けずに作れる -p で開けた公開ポート 共有 IP 上のランダム番号 ssh -L のトンネル SSH の 22 番の中を通る 内部 8080(WebUI) 内部 8000(API) 内部 22(sshd) HTTPS HTTP SSH トークン付き 直結 穴は増えない 3 本とも行き先は同じコンテナ。違うのは新しい穴を開けるかどうか
図 10-1 右の大きな箱が 1 台のインスタンスで、中の小さい箱がその中で待ち受けているアプリです。真ん中の 3 本のうち、インスタンスに新しい穴を開けるのは 2 本目(-p)だけです。枠を強調した 3 本目は、SSH がすでに使っている 22 番の中を通るので穴が増えません。

外部ポート番号は自分では決められない

ポート(= 1 台のコンピューターの中で通信の宛先を区別する番号。IP アドレスが住所なら、ポートは部屋番号)の扱いが、vast.ai では独特です。以下、アプリがある番号で通信を待っている状態を listen(= その部屋番号宛ての通信を待ち受けている)と書きます。インスタンスは完全なインターネットアクセスを持ちますが、一般に自分専用の IP アドレスを持ちません。IP アドレスはマシンやインスタンスの間で共有され、開いている内部ポートは 1 つずつそのマシンの(多くは共有の)公開 IP 上のランダムな外部ポートに割り当てられます。

コンテナの中 vast の割り当て 外から使うアドレス 内部ポート 8081 アプリが listen 内部ポート 22 sshd が listen vast がランダムな 外部ポートを割り当てる 番号は指定できない 1 インスタンス 64 個まで 65.130.162.74:33526 開くと 8081 に届く 65.130.162.74:20000 ssh -p 20000 で入る -p 8081:8081 自動で開く 結果の番号 同じ規則 外部ポート番号は自分で決められないので、起動後に必ず確認する
図 10-2 左の 8081 と右の 33526 は別の番号です。SSH の 22 番も同じ規則で扱われ、だから vast.ai の SSH は必ず 22 番以外のポートに繋ぎます。IP アドレスも自分専用ではありません。

ポートを開く 2 つの書き方

1 つはテンプレートの docker options 欄(または CLI の --env)に -p を書く方法。もう 1 つは Docker イメージの EXPOSE 命令で、これは自動的にポート要求へマップされます。どちらも SSH の 22 番・Jupyter の 8080 番に追加で効きます。

ここで書ける docker のオプションは環境変数(-e)・ホスト名(-h)・ポート(-p)の 3 種類だけです。それ以外の docker run オプションは無視されます。ただし公式ドキュメントは、テンプレートの Docker Options 欄について別の箇所で「この欄が受け付けるのはポートと環境変数だけ」とも書いており、-h がこの欄で通るかどうかは記述が食い違っています。この章で必要なのは -p と -e だけなので、-h は使わないでください。

vastai create instance <OFFER_ID> --image your-image:latest \
  --env "-p 8000:8000 -p 8080:8080" --disk 50

<OFFER_ID> には vastai search offers で見つけた offer の ID を入れます。--env の中身が docker options で、この例では内部の 8000 番と 8080 番を開いています。UDP を開けたいときだけ -p 8082:8082/udp のように末尾に /udp を付けます(LLM を動かすだけなら出番はありません)。開けるのは 1 インスタンスあたり合計 64 ポートまでです。

割り当てられた番号を調べる

インスタンスのロードが終わったら、インスタンス上部の IP Port Info ボタンのポップアップを開きます(公式ドキュメント内では Open Ports / IP/Ports という呼び方も出てきます。同じボタンです)。表示は PUBLIC_IP -> INTERNAL_PORT の形で、たとえば 65.130.162.74:33526 -> 8081/tcp なら「65.130.162.74:33526 を開けば内部の 8081 番に届く」という意味です。CLI やコンテナの中からも取れます。

vastai show instance <INSTANCE_ID> --raw | jq '.ports'
echo $VAST_TCP_PORT_8081

<INSTANCE_ID> はインスタンスカードに出ている数字です。VAST_TCP_PORT_<内部ポート> という環境変数が内部ポート 1 つにつき 1 個自動で作られ、値が外部ポート番号です(UDP なら VAST_UDP_PORT_<内部ポート>)。ただし SSH モード / Jupyter モードでは自分で設定したカスタム環境変数が SSH・tmux(= 接続が切れても中の作業が生き残るターミナルの多重化ツール。vast は既定でこの中に繋ぎます)・Jupyter のセッションから既定で見えません。vast.ai が公開しているイメージでは自動処理されますが、他人のイメージを使うときは onstart スクリプト(= インスタンスの起動時に 1 回だけ自動実行される shell スクリプト。テンプレートの on-start 欄か CLI の --onstart-cmd で渡す)で次の 1 行を書き出します。

env | grep _ >> /etc/environment

公式はポートマッピングのテストを強く推奨しています。インスタンスの中で最小の Web サーバを立て、外部アドレスをブラウザで開くだけです。ファイルの一覧が出れば成功です。

python -m http.server 8081

外部と内部を同じ番号にしたいときは 70000 番以上を使います(-p 70000:70000)。ただしこの挙動の説明は公式ドキュメント内で食い違っていて、「外部=内部になる」とも「内部が一致するランダムな外部ポートになる」とも書かれています。どちらであっても困らないように、実際の番号は必ず $VAST_TCP_PORT_70000 か IP Port Info で確認してください。公式も identity ポートは「本当に必要なときだけ、控えめに使え」と書いています。

インスタンスカードの Open ボタンと Jupyter ボタンの行き先を変える

ブラウザからの入口は最終的に「インスタンスカードのボタンを押す」動作に落ちてきます。カードのメインボタンが Open になっていればロード完了で、押すとブラウザからアクセスできます(ロード完了を表す表示にはもう 1 つ Connect があり、こちらは押すと SSH の接続情報が出ます。Loading はイメージのダウンロード中、Connecting は Docker は動いているが接続が未検証、という意味です)。そして Open ボタンと Jupyter ボタンがどのポートを開きに行くかは、環境変数で付け替えられます。自分で入れた WebUI をワンクリックで開きたいときに使います。

環境変数何が変わるか書き方の例
OPEN_BUTTON_PORTOpen ボタンの行き先を、指定した内部ポートに対応する外部ポートにする-e OPEN_BUTTON_PORT=7860
JUPYTER_PORTJupyter ボタンの行き先を、指定した内部ポートにする-e JUPYTER_PORT=8081
JUPYTER_TOKENJupyter ボタンが使うアクセストークンを自分で決める-e JUPYTER_TOKEN=<好きな文字列>
JUPYTER_SERVER_ROOTJupyter の画面に見えるルートディレクトリ。ここより上には遡れないvast が自動で設定する

<好きな文字列> は自分で決めるパスワード相当の文字列です。-e はどれもテンプレートの docker options 欄か CLI の --env に書きます。OPEN_BUTTON_PORT が指すのは「指定した内部ポートに対応する外部ポート」なので、その内部ポートを -p で開けていなければ対応する外部ポートが存在せず、ボタンの行き先も決まりません。後述する PORTAL_CONFIG についても、公式は「そのポートはテンプレートに追加済みでなければならない」と明記しています。ボタンの付け替えとポートの宣言はセットで行ってください。なお JUPYTER_SERVER_ROOT は vast が自動で設定する値で、「Jupyter のファイル一覧で上のフォルダに移動できない」の答えがこれです。

Jupyter — proxy 版と direct HTTPS 版

Jupyter は、公式が最初に選ぶことを推奨している入口です。Google Colab の local runtime を経由するより、Jupyter を直接動かすほうが堅牢でエラーが起きにくい、とまで書かれています。

既定は proxy 版で、vast の proxy サーバを経由します。ホスト側にオープンポートがあるかどうかに関係なく動く単純な仕組みで、欠点は大きいファイルの送受信が遅いことだけです。もう 1 つが direct HTTPS 版で、Jupyter を選ぶと出る Jupyter direct HTTPS のチェックボックスで有効にします。インスタンスへ直結するのでファイル転送が速くなりますが、オープンポートを持たないマシンが検索結果から自動的に除外されます。2026-08-24 時点の 1×RTX 5090 は direct_port_count(= ホストのルータで開いているポートの数)の中央値が 98 なので、5090 でこれが問題になることはまずありません。

CLI から作るときはチェックボックスの代わりに --jupyter と --direct を並べます。--direct は Jupyter と SSH の両方を direct 接続にするフラグです。候補を探す段階で direct_port_count>=1 を条件に入れておくと、direct を張れないマシンが最初から出てきません。

vastai search offers 'gpu_name=RTX_5090 num_gpus=1 direct_port_count>=1 rentable=true'
vastai create instance <OFFER_ID> --image <IMAGE> --disk 50 --jupyter --direct

<OFFER_ID> は 1 行目の検索結果から選んだ offer の ID、<IMAGE> は使う Docker イメージ名(第 7 章)です。検索の gpu_name は CLI ではアンダースコア区切り(RTX_5090)で書きます。1 行目の検索は無料です。2 行目でインスタンスを作ると契約が成立し、イメージのダウンロード(Loading)が終わって動き出した時点から GPU の時間課金が始まります(Loading 中は課金されません)。2026-08-24 時点の 1×RTX 5090 の中央値は 0.4037 USD/時、1 USD = 150 円として約 61 円/時です。

direct HTTPS には証明書のインストールが要る

direct HTTPS では vast が自己署名証明書(= 公的な認証局ではなく発行元自身が署名した証明書。手元の OS に登録していないブラウザは警告を出す)を使います。証明書は jvastai_root.cer から落とせます。多くの OS ではファイルをダブルクリックするとインストールウィザードが始まります。

OS入れていないとどうなるか入れたあとの反映範囲
macOSOS が Jupyter のページを開かせてくれない可能性がある(押し通せない)稼働中のインスタンスにも即座に反映
Windows(Chrome の設定から)プライバシーエラー。Advanced → Proceed で押し通せる再起動は不要だが新規インスタンスのみ。既存には警告が出続ける
Windows(証明書ファイルから)同上マシンの再起動が必要
Linux同上(押し通せる)Chrome の Authorities タブへインポート

macOS の手順は、証明書をダブルクリックして Login キーチェーンに入れ、Keychain Access で Vast.ai Jupyter のエントリを開き、Trust の When using this certificate を Always Trust に変えるだけです。Windows で Chrome の設定から入れる場合は、Settings → Privacy and security → Security → Manage device certificates → Import と進み、保存先に Trusted Root Certification Authorities を選びます。

Jupyter Terminal と Jupyter の落とし穴

Jupyter モードで起動したインスタンスには Jupyter Terminal が付いてきます。ブラウザの中で動く素直なターミナルで、セッションが永続します。インスタンスカードの SSH ボタン(SSH connections インターフェース)からアクセスします。Windows で SSH クライアントの設定に詰まった人の逃げ道として最短です。

Jupyter を使う上で必ず踏む罠が 2 つあります。1 つ目、Jupyter の「削除」はファイルをゴミ箱に移すだけで、ディスクの空きは増えません。完全に消すには Jupyter のターミナルで捨てます。

rm -r ~/.local/share/Trash

2 つ目、Jupyter のアップロード・ダウンロードは大きいファイル向けに最適化されていません。モデルのような重いファイルは scp -P(第 9 章)やクラウドストレージからの wget を使い、複数ファイルは zip にまとめてから落とします。Jupyter Lab なら Shift + クリックで複数選択してダウンロードできます。

転送にはお金もかかります。2026-08-24 時点の 1×RTX 5090(64 件)の中央値は inet_down_cost が 0.0026 USD/GB、inet_up_cost が 0.0039 USD/GB です。計算例: 10 GB を落として 10 GB を上げても 0.065 USD。同じ時間 GPU を動かすほうがずっと高く、GPU 時間は中央値 0.4037 USD/時(1 USD = 150 円として約 61 円/時)です。この章の作業で怖いのは通信費ではなく、閉じ忘れと止め忘れです。

Instance Portal — トークン付きのリンクを配る

Instance Portal は vastai/base-image 系のイメージで Open ボタンを押したときに最初に出るアプリで、役割は 2 つです。インスタンスの中で動いている Web アプリへのリンクを並べることと、それらのサービスの前に認証レイヤーを置いて、正しいトークンを持たない人のアクセスを防ぐこと。さらにポートを露出させずにサービスへのトンネルを作れます。この認証レイヤーの実体が Caddy(= サービスの手前に立って外からの通信を受け取り、中で動いているアプリへ取り次ぐ Web サーバ。base image に組み込まれていて、Web サービスの TLS 暗号化も自動でやります)です。

Open を押した直後のローディング中に起きているのはこれです。インスタンスのオープンポート 1 本ごとに、セキュアな Cloudflare トンネルが 1 本ずつ作られ、ブラウザがそのリンクに到達できるかテストされます。トンネルとは、インスタンスの中のサービスに、公開ポートを直接叩かせずに外から届かせるための中継路のことです。トンネルの URL は https://four-randomly-selected-words.trycloudflare.com のようにランダムに選ばれた 4 単語のサブドメインになります。ポート 1111 のトンネルが到達可能になった時点で、Instance Portal はそのリンクにリダイレクトして UI を出します。準備に時間がかかりすぎるときは http://ip_address:port_1111 でも UI が出ます。

ランディングページの大きな青い Launch Application ボタンは、トンネルが使えれば trycloudflare.com のリンク、まだ無ければ IP アドレス直のリンクを開きます。どちらの場合もリンクにはセキュアトークンが付加されます。Advanced Connection Options から全接続手段を確認できます。ただしこの既定のトンネルは quick トンネル(= 既定で自動的に張られる、URL が毎回変わるトンネル。自分では何も用意しなくてよい代わりに URL を固定できません)で、公式に「ベストエフォート(常に利用できるとは限らない)」と注記されています。確実な HTTPS が要るなら direct 接続か Jupyter の証明書に頼ります。

localhost 1111 11111 / Instance Portal : : : : hostname 外部ポート 内部ポート URL パス 表示名 待ち受け先 中継先 外からの接続 Caddy が 0.0.0.0:1111 で待ち受ける localhost:11111 Instance Portal 本体 1111 番へ 中継する 外部と内部が同じ番号なら Caddy は中継せず、リンクだけを作る
図 10-3 上の 1 行が PORTAL_CONFIG の 1 エントリです。2 番目と 3 番目の数字だけが下の中継を決めていて、残りの 3 つは表示のための情報です。2 番目は公式の呼び名が「external port」ですが、実体はコンテナの中の番号です(次の節で詳述)。

PORTAL_CONFIG — どのアプリをどのポートで見せるか

Instance Portal の初期設定は環境変数 PORTAL_CONFIG で決まります。アプリ同士は | で区切り、各アプリのオプションは : で区切ります。

位置フィールド意味
1hostnameアプリを待ち受けさせるインターフェース。Docker インスタンスでは常に localhost
2external portプロキシする外部ポート。テンプレートに -p で追加済みでなければならない
3local port動いているアプリが実際に listen しているポート
4URL pathリンクを開くときのパス。多くは /。ディープリンクに使える
5Application NamePortal の画面に出る表示名

肝は 2 番目と 3 番目の関係です。両者が違えばリバースプロキシ(= 外から来た通信を中のアプリへ取り次ぐ役。ここでは先ほどの Caddy)が 0.0.0.0:外部ポート で待ち受け、localhost:内部ポート へ中継します。同じ番号なら Caddy は中継せず、Instance Portal がリンクだけを作ります。既定値では Jupyter が localhost:8080:18080:/:Jupyter(中継あり)、Jupyter Terminal が localhost:8080:8080:/terminals/1:Jupyter Terminal(中継なしのリンクだけ)になっているのがこの使い分けです。1 つのアプリに複数のリンクを定義するときは、ポートが一致しないエントリは最初の 1 つだけにします。1 つのアプリを二重にプロキシすることはできません。

ここで 1 つ、名前のせいで必ず混乱する点を潰しておきます。PORTAL_CONFIG の 2 番目が「external port」と呼ばれていますが、これはインターネットから見える番号ではありません。コンテナの中で Caddy が待ち受ける番号、つまりこの章の前半でずっと「内部ポート」と呼んできたものです。だから公式も「そのポートはテンプレートに -p で追加済みでなければならない」と書いています。-p 1111:1111 で 1111 番を開け、それが vast によってランダムな本当の外部ポートへ割り当てられ、そこに PORTAL_CONFIG の設定が乗る、という三段構えです。

この設定は初回ブート時に処理されて /etc/portal.yaml が作られます。稼働中のインスタンスでこのファイルを編集すればアプリを足せますが、起動後に追加したアプリは最初は到達できないので、インスタンスの reboot が必要です。テンプレート自体を改造せず中身を足したいときは PROVISIONING_SCRIPT(= 起動時に取ってきて実行させる shell スクリプトの URL を環境変数で渡す仕組み。onstart と違って文字数制限がありません)から作り直します。次が公式の書き方です。

rm -f /etc/portal.yaml
export PORTAL_CONFIG="localhost:1111:11111:/:Instance Portal|localhost:1234:11234:/:My Application"
supervisorctl reload

3 行の意味はこうです。1 行目で既存の設定ファイルを消し、2 行目で新しい PORTAL_CONFIG を環境変数として置き、3 行目で Supervisor(= base image が使っている「決められたプロセス群を起動して監視し続ける常駐ソフト」。Instance Portal や Caddy もこれに面倒を見てもらっています)に設定を読み直させます。My Application の部分が Portal の画面に出る表示名になります。

Tunnels ページ — 開け忘れたポートを後から救う

インスタンスを起動した後で「7860 番を使うソフトを入れたが、テンプレートでそのポートを設定していなかった」という事態は必ず起きます。この救済が Tunnels ページです。上部の入力欄に http://localhost:7860 と入れて青い Create New Tunnel ボタンを押すと、そのポート用のトンネルが作られます(直後は少し待ちます)。Manage ボタンで既存のトンネルを停止したり、新しい URL が欲しいときにリフレッシュできます。インスタンスを作り直さずに済む最大の手段で、-p を稼働中のインスタンスに後から足す手順は公式ドキュメントに載っていません。

隣の Instance Logs ページは /var/log/portal/ の .log ファイルをライブ表示します。Download Logs で /var/log/ 配下を zip で落とせるので、起動に失敗したときの調査はここから始めます。

固定 URL が欲しいとき

既定の quick トンネルは URL が毎回変わります。自分のドメインの決まったサブドメインを使いたいなら named tunnel です。無料の Cloudflare Zero Trust アカウントとそこに紐づけたドメインを用意し、トンネルのトークンを環境変数 CF_TUNNEL_TOKEN として渡します(アカウントの Environment Variables かテンプレートに設定。稼働中なら /etc/environment に書いて reboot)。設定されていれば Launch Application は named tunnel を指します。同じトークンを複数の稼働中インスタンスで使い回すとリンクが壊れます。インスタンスごとに別のトークンを用意してください。

穴を開ける前に、開けない道を検討する

ここまでの事実を並べると、判断は単純になります。-p で開いたポートは共有公開 IP 上のランダムな外部ポートに割り当てられ、公式のテスト手順が「ブラウザで開けばファイル一覧が見える」と言っている以上、それは第三者のブラウザからも開けるということです。ポート番号がランダムであることはセキュリティではありません。ドキュメントもそれを安全機構として説明していません。

そのサービスを誰が開くか 先に決める ssh -L のトンネル ポートを開けない Instance Portal のトンネル トークン付きリンク -p で直接公開 トークン認証を必ず付ける 自分だけが使う 他の人にも見せる コードから叩く 自分の端末だけ リンクを持つ人だけ 到達できる全員 露出が狭い 露出が広い
図 10-4 下の線は「どこまで見えるか」の軸です。左端に寄せられるなら寄せるのが原則で、右へ行くほど認証を自分で用意する責任が増えます。

既定の道は ssh -L

公式は ssh -L によるローカルポートフォワードを「しばしば見落とされる SSH の機能」「公開インターフェースにポートを開けることの安全な代替で、すべてのデータは SSH 接続の中を運ばれる」と説明しています。SSH ボタンが出す既定の接続コマンドにも -L 8080:localhost:8080 が最初から入っています。

ssh -p <SSH_PORT> root@<HOST> -L 8080:localhost:8080 -L 5000:localhost:5000

<SSH_PORT> と <HOST> はインスタンスカードの SSH ボタン、または vastai ssh-url <ID> の出力(ssh://root@ssh5.vast.ai:33426 の形)から取ります。-L 8080:localhost:8080 は「手元の 8080 番へのアクセスをリモートの 8080 番に転送する」という意味で、必要な数だけ並べられます。繋いだあとは手元のブラウザで http://localhost:8080 を開くだけです。Open WebUI・llama-server・vLLM のような「自分だけが使う」もの(第 12〜15 章)は、これで全部足ります。

どうしても開けるなら、トークンを必ず付ける

公開が必要なときは、base image 系テンプレートの認証に乗るのが最短です。Direct Access では Basic 認証(= ブラウザがユーザー名とパスワードを聞いてくる、最も素朴な認証)が既定で有効で、ユーザー名は vastai、パスワードは環境変数 OPEN_BUTTON_TOKEN の値です。API アクセスは Bearer トークン(= リクエストのヘッダーに合言葉を載せる方式)になります。

echo $OPEN_BUTTON_TOKEN
curl -s http://<INSTANCE_IP>:<EXTERNAL_PORT>/v1/models \
  -H "Authorization: Bearer <OPEN_BUTTON_TOKEN>"

<INSTANCE_IP> と <EXTERNAL_PORT> は IP Port Info で見た値、<OPEN_BUTTON_TOKEN> は 1 行目のコマンドで表示された文字列です。テンプレートに -e OPEN_BUTTON_TOKEN=1 を渡すとイメージ側がトークンを生成します。なお WEB_ENABLE_HTTPS と WEB_ENABLE_AUTH という変数で HTTPS と認証を切れますが、初心者が触る理由はありません。

閉じ方も先に決めておきます。トンネルは Tunnels ページの Manage から停止できます。-p で開けた穴を稼働中に閉じる手段は公式に記載がないので、実務ではアプリを止める・トンネルを止める・使い終わったら destroy するの 3 つで露出時間を短くします。公式の機密ワークロード向けチェックリストも「認証情報をインスタンスに保存しない」「終わったら即座に destroy する」を挙げています。destroy はストレージ課金も止めるので(第 16 章)、安全とお金の答えが一致します。

つまずきポイント

-p 8081:8081 と書いたから、外からも 8081 番で見えると思っていた

見えません。内部の 8081 番は、そのマシンの共有公開 IP 上のランダムな外部ポートに割り当てられます。IP Port Info の 65.130.162.74:33526 -> 8081/tcp の左側が実際のアクセス先で、コンテナの中からは $VAST_TCP_PORT_8081 で取れます。-p 70000:70000 の identity 指定でも、公式ドキュメント自体が挙動を 2 通りに書いているので番号は必ず実測してください。

ポート番号がランダムなら、他人には見つからないと思っていた

ランダム性は認証ではありません。公式のテスト手順は「python -m http.server 8081 を立てて外部アドレスをブラウザで開けばファイル一覧が見える」です。自分のブラウザから開けるものは、他人のブラウザからも開けます。--host 0.0.0.0 で LLM サーバを立てる公式サンプルは、そのポートを -p で開けた瞬間に世界へ公開されることを意味します。認証を付けるか、-p を使わず ssh -L にしてください。

Jupyter でファイルを削除したのに、ディスクの空きが増えない

Jupyter の削除はゴミ箱への移動です。ディスクの割り当てはインスタンス作成時に固定されて後から増やせないので、空きが戻らないと詰みます。Jupyter のターミナルで rm -r ~/.local/share/Trash を実行してください。大きいファイルの出し入れ自体も Jupyter は苦手なので、scp -P かクラウドストレージからの wget に切り替えます。

Windows で証明書を入れたのに、警告が消えない

反映範囲が OS と手順で違います。Chrome の設定から入れた場合は「これ以降に作る新規インスタンス」にしか効かず、既存のインスタンスでは警告が出続けます(公式が太字で注記)。証明書ファイルから入れる Windows の一般手順ではマシンの再起動が必要です。macOS だけは稼働中のインスタンスにも即座に反映されます。

理解度チェック

Q1. -p 8081:8081 で開いたポートに、手元のブラウザからはどのアドレスでアクセスしますか?

マシンの公開 IP と、vast が割り当てたランダムな外部ポートの組み合わせです。IP Port Info に 65.130.162.74:33526 -> 8081/tcp と出ていれば 65.130.162.74:33526 が入口です。コンテナ内なら $VAST_TCP_PORT_8081、CLI なら vastai show instance <ID> --raw | jq '.ports' でも取れます。

Q2. Instance Portal の Open を押した直後のローディング中、何が起きていますか?

オープンポート 1 本ごとに Cloudflare のセキュアトンネルが 1 本ずつ作られ、ブラウザから到達できるかテストされています。ポート 1111 のトンネルが通った時点で、その trycloudflare.com のリンク(ランダムな 4 単語のサブドメイン)へリダイレクトして UI が出ます。遅すぎるときは http://ip_address:port_1111 でも UI が開きます。

Q3. インスタンスを起動した後に 7860 番で待つソフトを入れました。ポートは開けていません。作り直しは必要ですか?

不要です。Instance Portal の Tunnels ページで http://localhost:7860 を入力して Create New Tunnel を押せばトンネルが作られます。自分だけで使うなら ssh -L 7860:localhost:7860 でも届きます。逆に、稼働中のインスタンスへ -p を後から足す手順は公式ドキュメントにありません。

Q4. macOS で Jupyter の direct HTTPS に繋がりません。最初に確認することは?

証明書です。vast は自己署名証明書を使っており、macOS では未インストールだと OS がページを開かせてくれない可能性があります(Windows / Linux は警告を押し通せます)。jvastai_root.cer を入れ、Keychain Access の Vast.ai Jupyter を Always Trust にしてください。macOS は稼働中のインスタンスにも即座に反映されます。

Q5. PORTAL_CONFIG で外部ポートと内部ポートに同じ番号を書くと、何が変わりますか?

Caddy が中継をしなくなり、Instance Portal はリンクだけを作ります。番号が違うときは Caddy が 0.0.0.0:外部ポート で待ち受けて localhost:内部ポート へ中継します。同番号が有用なのは、Jupyter モードの Jupyter のように Supervisor の管理外で動いているものへリンクを張りたい場合です。

まとめ

次章からは、この入口を使って実際に LLM を動かします。まずはモデルの入手です。Hugging Face からの落とし方、利用申請が必要な gated モデル、そしてディスクをどれだけ食うかを押さえます。