公司不想繼續付 Microsoft Teams 或 Webex 的帳號訂閱費?其實只要有一台能跑 Docker 的主機和一個網域,就可以自架一套功能對標 Google Meet 的開源視訊會議系統。這篇文章記錄從零到上線的完整過程,包含踩過的坑和解法。
為什麼選這套
這次用到兩個開源專案的組合:
- La Suite Meet:法國政府(DINUM)官方開源的視訊會議系統,底層用 LiveKit 做 WebRTC,前後端是 React + Django,MIT 授權,法國公務體系正式在用。
- VoidAuth:輕量的自架 SSO / OIDC 身分驗證服務。Meet 本身不管帳號密碼,把登入委派給 OIDC 提供者,VoidAuth 就是扮演這個角色。
成本上,除了電費跟網域費,其他都是免費的。
這篇適合誰
- 有一台能跑 Docker 的主機(實體機或 VPS 都行)
- 有自己的網域,可以設定 DNS
- 對 Linux 指令不陌生,但不需要是專家
架構概覽
這篇用的架構:
- 一台 Docker 主機(
192.168.1.102):跑 Meet、LiveKit、VoidAuth、資料庫等所有容器 - 一台 Caddy 反向代理主機:對外提供 HTTPS,自動申請 Let's Encrypt 憑證
- 三個對外網域(以
sao-x.com為例,換成你自己的)
Caddy 在另一台機器
如果你的 Caddy 跟 Docker 是同一台主機,把所有「另一台主機 IP」改成 127.0.0.1 即可,架構會更簡單。
前置作業:DNS 設定
先把這三個子網域指到你的公網 IP:
meet.sao-x.com A 你的公網IP
auth.sao-x.com A 你的公網IP
livekit.sao-x.com A 你的公網IP不要掛 Cloudflare 代理
這幾個子網域不要開啟 Cloudflare 的橘色雲朵(Proxy)。免費方案會把閒置超過 100 秒的 WebSocket 直接斷掉,導致視訊會議連線不穩定。使用 DNS only(灰色雲朵)即可。
安裝流程
為什麼要分兩階段
有個先天的「雞生蛋」問題:Meet 需要 VoidAuth 給的 OIDC Client ID/Secret,但這組 ID/Secret 必須先把 VoidAuth 跑起來、登入後台手動建立才能拿到。所以拆成兩個階段。
Stage 1:先跑起 VoidAuth
下載安裝腳本後執行:
chmod +x install-meet-voidauth.sh
./install-meet-voidauth.sh stage1腳本會問你幾個問題,有預設值的直接 Enter 就好:
在 VoidAuth 建立 OIDC Client
登入後台後,建立一個給 Meet 用的 OIDC Client:
| 欄位 | 值 |
|---|---|
| 名稱 | 任意,例如 meet |
| Redirect URI | https://meet.sao-x.com/api/v1.0/callback/ |
| Grant Type | Authorization Code |
建立後拿到 Client ID 和 Client Secret,記下來備用。
Stage 2:部署 Meet 全家桶
./install-meet-voidauth.sh stage2這次會問你 OIDC Client ID/Secret(貼上 Stage 1 拿到的)、資料庫密碼、SMTP(沒有就全 Enter 跳過)、管理員帳密。
腳本會自動:啟動所有容器 → 跑 DB migrate → 建立管理員帳號。
完成後把 Meet 和 LiveKit 加到 Caddy:
meet.sao-x.com {
reverse_proxy 192.168.1.102:8083
}
livekit.sao-x.com {
reverse_proxy 192.168.1.102:7880
}最關鍵的最後一步
路由器 Port Forwarding — 最常被漏掉
視訊/音訊的媒體流走 WebRTC,使用者瀏覽器必須能直接連到 LiveKit 容器的 7881/7882,不能走 Caddy 反代。
登入你的路由器後台,新增 Port Forwarding 規則:
- 外部
7881/TCP→192.168.1.102:7881 - 外部
7882/UDP→192.168.1.102:7882
沒做這步的症狀:網頁能開、能登入、能進會議室,但畫面一直轉圈、看不到對方、console 噴 ping timeout。
用 canyouseeme.org 測試 7881 有沒有真的對外開放,確認沒問題就完成了。
踩過的坑(排錯速查)
這是整個過程踩過的坑,整理成一個速查表:
開放 API 給 Bot / 第三方整合
Meet 有自己的 Application JWT 系統,讓外部程式能以特定使用者身分呼叫 API,不需要走瀏覽器登入。
啟用 External API
echo "EXTERNAL_API_ENABLED=true" >> env.d/common
echo "APPLICATION_JWT_SECRET_KEY=$(openssl rand -hex 32)" >> env.d/common每行必須獨立
用 echo 一次寫一行,不要把兩個變數寫在同一行。Django 沒辦法解析 TrueAPPLICATION_JWT_SECRET_KEY=xxx 這種粘在一起的值,會直接掛掉。
重啟 backend 後,去 Admin 後台 → Applications → 新增,填入名稱、選擇允許的 scopes 和使用者網域。
用 Application 換 JWT Token
curl -X POST https://meet.sao-x.com/api/v1.0/resource-server/token/ \
-H "Content-Type: application/json" \
-d '{
"client_id": "你的ClientID",
"client_secret": "你的ClientSecret",
"email": "代理的使用者email",
"scope": "rooms:create rooms:list"
}'拿到 token 後,用 Authorization: Bearer <token> header 呼叫 /api/v1.0/resource-server/rooms/ 等端點。
對外 Port 總整理
| Port | 協定 | 在哪台 | 開放對象 | 用途 |
|---|---|---|---|---|
| 80 | TCP | Caddy 主機 | 全網 | HTTP(Let's Encrypt 簽證 + 轉址) |
| 443 | TCP | Caddy 主機 | 全網 | HTTPS(所有網域對外入口) |
| 3000 | TCP | Docker 主機 | 僅 Caddy IP | VoidAuth 反代 |
| 8083 | TCP | Docker 主機 | 僅 Caddy IP | Meet 反代 |
| 7880 | TCP | Docker 主機 | 僅 Caddy IP | LiveKit 信令反代 |
| 7881 | TCP | Docker 主機 | 全網+路由器轉發 | WebRTC 媒體(ICE over TCP) |
| 7882 | UDP | Docker 主機 | 全網+路由器轉發 | WebRTC 媒體(主要影音傳輸) |
真正暴露在公網的只有 Caddy 的 80/443,以及 Docker 主機的 7881/7882(需路由器 Port Forwarding)。其餘 port 用 ufw 限制只對 Caddy 主機的內網 IP 開放。
維護
升級到新版本:
cd ~/meet-stack
# 先看官方 CHANGELOG 有無特殊步驟
docker compose -f docker-compose.meet.yaml -f docker-compose.meet.override.yaml pull
docker compose -f docker-compose.meet.yaml -f docker-compose.meet.override.yaml up -d
docker compose -f docker-compose.meet.yaml -f docker-compose.meet.override.yaml \
run --rm backend python manage.py migrate建議釘版本
範例 compose 檔預設用 latest tag,建議正式上線後把 image 版本號釘死(例如 lasuite/meet-backend:1.17.0),避免自動升級到不相容的新版。
新增使用者:在 VoidAuth 後台的 Invitations 頁面建立邀請連結,發給對方自助註冊。管理員則從 https://meet.sao-x.com/admin 直接操作。