> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GPUStack 推論服務部署

> 在單台 GPU 主機上以 Docker Compose 同時啟動 GPUStack 的 server 與 worker，透過 Cloudflare Tunnel 對外提供 OpenAI 相容的 LLM 推論 API，主機不開放任何入站埠號，並涵蓋 NVIDIA 驅動、模型載入與認證設定流程。

export const ArchFlow = ({nodes = [], lang = "zh"}) => {
  const t = lang === "en" ? {
    flow: "Data flow",
    hint: "Click any stage to see what it does",
    stage: "Stage"
  } : {
    flow: "資料流",
    hint: "點任一階段看它做什麼",
    stage: "階段"
  };
  const safe = Array.isArray(nodes) ? nodes : [];
  const [active, setActive] = useState(0);
  if (safe.length === 0) return null;
  const idx = Math.min(active, safe.length - 1);
  const current = safe[idx];
  const css = `
  .af-root{--af-bg:#FAF8F3;--af-surface:rgba(191,117,81,0.06);--af-surface2:rgba(0,0,0,0.025);--af-border:rgba(0,0,0,0.09);--af-text:#2b2722;--af-dim:#6f6a62;--af-faint:#8a8378;--af-rail:rgba(0,0,0,0.12);--af-accent:#bf7551;border:1px solid var(--af-border);border-radius:14px;background:var(--af-bg);color:var(--af-text);overflow:hidden;}
  .dark .af-root{--af-bg:#1b1a18;--af-surface:rgba(207,138,104,0.09);--af-surface2:rgba(255,255,255,0.03);--af-border:rgba(255,255,255,0.08);--af-text:#e7e3da;--af-dim:#a8a299;--af-faint:#8a8378;--af-rail:rgba(255,255,255,0.13);--af-accent:#cf8a68;}
  .af-head{padding:13px 18px 11px;display:flex;align-items:center;gap:9px;flex-wrap:wrap;border-bottom:1px solid var(--af-border);}
  .af-head-ic{color:var(--af-accent);flex-shrink:0;}
  .af-head-t{font-size:12px;font-weight:700;letter-spacing:.6px;color:var(--af-dim);text-transform:uppercase;}
  .af-head-h{font-size:12px;color:var(--af-faint);}
  .af-grid{display:flex;gap:0;align-items:stretch;}
  .af-list{flex:1 1 0;min-width:0;padding:12px 12px 16px;}
  .af-node{display:flex;align-items:stretch;gap:10px;width:100%;text-align:left;background:transparent;border:none;cursor:pointer;padding:0;color:inherit;font:inherit;-webkit-tap-highlight-color:transparent;}
  .af-railcol{width:22px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;}
  .af-dot{width:9px;height:9px;border-radius:50%;margin-top:14px;background:var(--af-faint);opacity:.5;flex-shrink:0;transition:background .15s,box-shadow .15s,opacity .15s;}
  .af-line{width:2px;flex:1 1 0;background:var(--af-rail);min-height:10px;}
  .af-body{flex:1 1 0;min-width:0;padding:9px 12px;margin:2px 0;border-radius:9px;border:1px solid transparent;transition:background .15s,border-color .15s;}
  .af-node:hover .af-body{background:var(--af-surface2);}
  .af-node-on .af-body{background:var(--af-surface);border-color:rgba(191,117,81,.30);}
  .af-node-on .af-dot{background:var(--af-accent);opacity:1;box-shadow:0 0 0 4px rgba(191,117,81,.15);}
  .af-label{font-size:14.5px;font-weight:550;line-height:1.45;}
  .af-node-on .af-label{color:var(--af-accent);}
  .af-detail{width:300px;flex-shrink:0;border-left:1px solid var(--af-border);padding:16px 18px;background:var(--af-surface2);}
  .af-detail-k{font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:var(--af-accent);margin-bottom:7px;}
  .af-detail-l{font-size:16px;font-weight:600;margin-bottom:9px;line-height:1.35;}
  .af-detail-d{font-size:13.5px;line-height:1.65;color:var(--af-dim);}
  @media (max-width:640px){.af-grid{flex-direction:column;}.af-detail{width:auto;border-left:none;border-top:1px solid var(--af-border);}}
  `;
  return <div className="af-root">
      <style>{css}</style>
      <div className="af-head">
        <svg className="af-head-ic" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="8" height="8" x="3" y="3" rx="2" /><path d="M7 11v4a2 2 0 0 0 2 2h4" /><rect width="8" height="8" x="13" y="13" rx="2" /></svg>
        <span className="af-head-t">{t.flow}</span>
        <span className="af-head-h">{t.hint}</span>
      </div>
      <div className="af-grid">
        <div className="af-list">
          {safe.map((n, i) => <button key={i} type="button" className={"af-node" + (i === idx ? " af-node-on" : "")} onClick={() => setActive(i)} onMouseEnter={() => setActive(i)}>
              <div className="af-railcol">
                <div className="af-dot" />
                {i < safe.length - 1 && <div className="af-line" />}
              </div>
              <div className="af-body">
                <div className="af-label">{n.label}</div>
              </div>
            </button>)}
        </div>
        <div className="af-detail">
          <div className="af-detail-k">{t.stage} {idx + 1} / {safe.length}</div>
          <div className="af-detail-l">{current.label}</div>
          <div className="af-detail-d">{current.detail}</div>
        </div>
      </div>
    </div>;
};

`GPUStack` · `LLM 推論` · `Cloudflare Tunnel` · `Docker` · `NVIDIA`

## 專案概述

把一台 GPU 主機架成 LLM、VLM、Embedding、Rerank 的推論服務。GPUStack v2.1.2 的 server 與 worker 以 Docker Compose 啟動，對外透過 Cloudflare Tunnel 曝露，主機不需開放任何入站埠號。對外介面走 OpenAI 相容格式，原本用 openai SDK 的程式幾乎不用改就能接上。

## 核心功能

* server 與 worker 兩個容器，`docker compose up` 一次帶起
* Cloudflare Tunnel 對外，主機防火牆不開入站埠
* 兩組 hostname 分流：API 用 CF Access Service Token，Web UI 用 Email 白名單
* OpenAI 相容端點（`/v1/chat/completions`），可直接套 openai SDK
* 模型從 Web UI 部署，來源支援 HuggingFace、Ollama 或本地路徑

## 專案架構

<ArchFlow
  nodes={[
{ label: "外部用戶端", detail: "透過 HTTPS 連到 Cloudflare，不直接接觸主機。" },
{ label: "Cloudflare Edge", detail: "TLS 終止與 Access 驗證：API hostname 走 Service Token，Web UI hostname 走 Email 白名單。" },
{ label: "Cloudflare Tunnel", detail: "outbound-only 連線，cloudflared 從內部主動連到 Cloudflare，主機防火牆不開任何入站埠。" },
{ label: "gpustack server", detail: "Web UI 與 gateway，接收推論請求並路由到 worker。" },
{ label: "gpustack worker", detail: "掛 NVIDIA runtime，管理推論容器的生命週期與排程。" },
{ label: "推論容器", detail: "GPUStack 依模型動態產生的 llama.cpp 或 vLLM 容器，以 host network 執行，直接使用 GPU。" }
]}
/>

server 與 worker 走內部 bridge 網路溝通，推論容器以 host network 掛在 worker 上，外部只接觸得到 Cloudflare 那一層。

## 快速開始

<Steps>
  <Step title="前置依賴（全新主機才需要）">
    ```bash theme={null}
    sudo bash scripts/prereq.sh
    ```

    確認 GPU、安裝 Docker Engine 與 NVIDIA Container Toolkit、建立資料目錄。
  </Step>

  <Step title="設定環境變數">
    ```bash theme={null}
    cp .env.example .env
    ```

    填入 admin 密碼、server/worker 共用 Token、HuggingFace Token、Cloudflare Tunnel Token。
  </Step>

  <Step title="設定 Cloudflare Tunnel">
    Zero Trust → Tunnels 建立 Tunnel，Public Hostname 的 service URL 指向容器名稱 `http://gpustack:80`，不是 localhost。
  </Step>

  <Step title="啟動並確認">
    ```bash theme={null}
    docker compose up -d
    docker compose logs -f gpustack
    ```
  </Step>
</Steps>

## 注意事項

<Warning>
  走 Service Token 的 API hostname，其 Public Hostname 的 Access 欄位不要綁任何 Application。綁了的話 cloudflared 會在 origin 端再驗一次，把 Service Token 的 JWT 丟掉，CF edge 回 502。`.env` 含密碼與各種 Token，不要提交版本控制。
</Warning>

## 實際應用面

適合在實驗室或沒有公網 IP 的主機上自架 LLM/VLM 推論，對外提供程式化 API 給其他服務呼叫，同時不想開防火牆入站埠、也不想另租雲端 GPU 的情境。

## 相關連結

* GitHub：[felimet/gpustack-api](https://github.com/felimet/gpustack-api)
* GPUStack 官方文件：[docs.gpustack.ai](https://docs.gpustack.ai)
