Skip to content

Docker 的 Tailscale sidecar:Jellyfin、Immich、Vaultwarden 共用寫法

用 tailscale/tailscale sidecar 容器把 Jellyfin、Immich、Vaultwarden 接進 tailnet:network_mode 共用寫法、TS_AUTHKEY/TS_STATE_DIR/TS_SERVE_CONFIG 與 state volume,以及各服務的埠號與必要設定

Updated View as Markdown

要把 Docker 裡的服務接進 tailnet,最乾淨的寫法是 sidecar:每個服務旁邊跑一個 tailscale/tailscale 容器,服務容器用 network_mode: service:<sidecar> 共享它的網路。這樣服務不用改任何程式碼、也不用開對外埠,就能以 MagicDNS 名稱在 tailnet 上被存取,再用 Tailscale Serve 掛 HTTPS。這篇以 Jellyfin、Immich、Vaultwarden 三個常見服務示範同一個模式。

環境變數(TS_AUTHKEY、TS_STATE_DIR、TS_SERVE_CONFIG 等)的細節、userspace/kernel 模式與 state volume 的重要性,都寫在 Docker 官方映像檔 那篇,這裡只列與 sidecar 相關的部分,不重複說明。

本文依 2026 年 10 月的 Tailscale 官方文件與 Jellyfin、Immich、Vaultwarden 官方文件整理,以 Tailscale v1.104(Docker Hub 映像檔穩定版 v1.102.5)為準。各服務的埠號都是官方預設值、跨版本穩定。本文未在實機跑過下述 compose,屬文件整理。

共用寫法

三個服務的 sidecar 骨架完全一樣,只有服務名、埠號和 serve.json 的代理目標不同:

services:
  # Tailscale sidecar:只負責把這個服務接進 tailnet
  ts-jellyfin:
    image: tailscale/tailscale:v1.102.5
    hostname: jellyfin
    environment:
      - TS_AUTHKEY=tskey-auth-xxxx
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_AUTH_ONCE=true
      - TS_SERVE_CONFIG=/config/serve.json
      - TS_USERSPACE=false
    volumes:
      - ts-jellyfin-state:/var/lib/tailscale
      - ./jellyfin-config:/config
    devices:
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - net_raw
    restart: unless-stopped

  # 服務容器:共享 sidecar 的網路,自己不開對外埠
  jellyfin:
    image: jellyfin/jellyfin
    network_mode: service:ts-jellyfin
    volumes:
      - ./jellyfin-data:/config
      - ./media:/media:ro
    restart: unless-stopped

volumes:
  ts-jellyfin-state:

三個關鍵:

  • network_mode: service:ts-jellyfin 讓 Jellyfin 走 sidecar 的網路,Jellyfin 自己不用 ports:——tailnet 上的存取走 sidecar 的 Serve 443,實體區網完全看不到它。
  • TS_STATE_DIR 一定要掛 volume(否則每次重啟變新節點),TS_AUTH_ONCE=true 避免每次重新登入。
  • sidecar 命名用 ts-<服務名>,讓它和服務容器一眼對應;state volume 也各自獨立(ts-jellyfin-state),別讓多個 sidecar 共用同一個 state。

第三方映像檔(jellyfin、vaultwarden、immich 等)也建議釘版本 tag,理由同 Tailscale 映像檔——latest 會在半夜自己升版。下面的範例為了簡潔,部分用了 latest/release,正式部署時換成你驗證過的具體版本。

Serve 的設定檔(serve.json)也是同一套,只改代理埠。下面三個範例各附一份。

Jellyfin:單一容器

Jellyfin 的 HTTP 埠是 8096。serve.json 代理到它:

{
  "TCP": { "443": { "HTTPS": true } },
  "Web": {
    "${TS_CERT_DOMAIN}:443": {
      "Handlers": { "/": { "Proxy": "http://127.0.0.1:8096" } }
    }
  }
}

Jellyfin 是單一容器、單一埠,最簡單。hostname: jellyfin 之後就能用 https://jellyfin.tailxxxxx.ts.net 存取。

Immich:多服務,全部走同一個 netns

Immich 由多個容器組成:server(對外埠 2283)、machine-learning、database(Postgres)、redis。多個容器要共享 sidecar 的網路時,讓它們全部用 network_mode: service:ts-immich(同一個 netns)、彼此用 localhost 連線——compose 的服務名 DNS 在借 netns 之後是解析不到的:

services:
  ts-immich:
    image: tailscale/tailscale:v1.102.5
    hostname: immich
    environment:
      - TS_AUTHKEY=tskey-auth-xxxx
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_AUTH_ONCE=true
      - TS_SERVE_CONFIG=/config/serve.json
      - TS_USERSPACE=false
    volumes:
      - ts-immich-state:/var/lib/tailscale
      - ./immich-config:/config
    devices:
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - net_raw
    restart: unless-stopped

  immich-server:
    image: ghcr.io/immich-app/immich-server:release
    network_mode: service:ts-immich
    environment:
      DB_HOSTNAME: localhost
      DB_USERNAME: immich
      DB_PASSWORD: postgres
      DB_DATABASE_NAME: immich
      REDIS_HOSTNAME: localhost
      IMMICH_MACHINE_LEARNING_URL: http://127.0.0.1:3003
    volumes:
      - immich-library:/usr/src/app/upload
    depends_on:
      - database
      - redis
    restart: unless-stopped

  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    network_mode: service:ts-immich
    volumes:
      - model-cache:/cache
    restart: unless-stopped

  database:
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    network_mode: service:ts-immich
    environment:
      POSTGRES_USER: immich
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: immich
    volumes:
      - immich-pgdata:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: valkey/valkey:9
    network_mode: service:ts-immich
    restart: unless-stopped

volumes:
  ts-immich-state:
  immich-library:
  model-cache:
  immich-pgdata:

serve.json 代理到 server 的 2283:

{
  "TCP": { "443": { "HTTPS": true } },
  "Web": {
    "${TS_CERT_DOMAIN}:443": {
      "Handlers": { "/": { "Proxy": "http://127.0.0.1:2283" } }
    }
  }
}

四個容器都走 network_mode: service:ts-immich(同一個 netns),彼此用 localhost 連線:DB 是 5432、Redis 是 6379、machine-learning 是 3003,與 server 的 2283 不衝突。這是 Tailscale 官方維護的 ScaleTail 對多服務 App 採用的模式,也是借 netns 下唯一可靠的做法。對外只靠 sidecar 的 Serve 443,四個服務都不開 ports:。

Immich 的完整官方 compose 還有很多細項(資料庫帶 vector 擴充、immich-machine-learning 的 GPU 設定等),上面是為了示範 sidecar 骨架而精簡的版本,實際部署以 Immich 官方 compose 為準。

Vaultwarden:記得多帶 DOMAIN

Vaultwarden 在容器內聽 80 埠,而且必須設定 DOMAIN 環境變數(例如 https://vw.tailxxxxx.ts.net),否則產生的登入/註冊網址會是錯的(http://localhost 之類),導致從外面無法正常使用:

services:
  ts-vaultwarden:
    image: tailscale/tailscale:v1.102.5
    hostname: vaultwarden
    environment:
      - TS_AUTHKEY=tskey-auth-xxxx
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_AUTH_ONCE=true
      - TS_SERVE_CONFIG=/config/serve.json
      - TS_USERSPACE=false
    volumes:
      - ts-vaultwarden-state:/var/lib/tailscale
      - ./vw-config:/config
    devices:
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - net_raw
    restart: unless-stopped

  vaultwarden:
    image: vaultwarden/server:latest
    network_mode: service:ts-vaultwarden
    environment:
      - DOMAIN=https://vw.tailxxxxx.ts.net
    volumes:
      - vw-data:/data
    restart: unless-stopped

volumes:
  ts-vaultwarden-state:
  vw-data:

serve.json 代理到 80:

{
  "TCP": { "443": { "HTTPS": true } },
  "Web": {
    "${TS_CERT_DOMAIN}:443": {
      "Handlers": { "/": { "Proxy": "http://127.0.0.1:80" } }
    }
  }
}

Vaultwarden 有兩個跟 Serve 有關的細節:

  • DOMAIN 要填 Serve 的最終網址(含 https:// 前綴)。Bitwarden 用戶端(瀏覽器擴充、手機 App)會用這個網址連線,填錯就會連不上。
  • 瀏覽器擴充、App 需要 WebSocket 通知,Vaultwarden 新版已把 WebSocket 併進同一個 HTTP 埠(80),所以 serve.json 只代理 80 就夠了,不用另開 3012。

啟動順序與 healthcheck

sidecar 和服務之間通常不需要特別的啟動順序——服務連不上 tailnet 時只是暫時連不到,不會掛掉。但如果要講究,可以:

  • 讓服務容器 depends_on: [ts-xxx],確保 sidecar 先起。
  • 在 sidecar 加 TS_ENABLE_HEALTH_CHECK=true,用 /healthz 當健康檢查,讓上游的反向代理或編排系統等到節點有 tailnet IP 再放流量進來。

一般家用只要 restart: unless-stopped 就夠了,不需要特別處理。

小結

服務 對外埠 進 tailnet 的容器 必要設定
Jellyfin 8096 全部(單一容器) 無
Immich 2283 全部(server/DB/Redis/ML 同 netns) 以 localhost 連線、不開對外埠
Vaultwarden 80 全部(單一容器) DOMAIN 要填 Serve 的 HTTPS 網址

同一個模式可以套到任何 Docker 服務:加一個 ts-<服務名> sidecar、服務容器改 network_mode: service:ts-<服務名>、serve.json 指到服務埠,就完成了。更多現成範例見 ScaleTail 與 docker-guide-code-examples。

參考資料

Navigation

Type to search…

↑↓ navigate↵ selectEsc close