SSD Nodes Learn 8GB 記憶體 — 每年 $66
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-01

Docker Compose 架設 Arr 堆疊與硬連結設定教學

透過單一 Docker Compose 檔案部署 Prowlarr、Sonarr、Radarr 與 qBittorrent。本指南教您正確配置磁碟掛載路徑,確保硬連結功能正常運作,避免因檔案複製導致 VPS 磁碟空間迅速耗盡。

您將建置的內容

Docker Compose arr 堆疊包含四個管理媒體庫的容器:用於索引器設定的 Prowlarr、用於影集的 Sonarr、用於電影的 Radarr,以及作為下載客戶端的 qBittorrent。它們透過 Compose 網路以服務名稱相互通訊,並共用主機上的一個資料夾樹狀結構。安裝過程很簡短。決定此堆疊是能穩定運作多年還是每週都需要維護的關鍵在於磁碟區配置,因此本指南大部分內容都在探討此部分。

此堆疊不會自動為您尋找內容。Prowlarr 僅存放您新增的索引器,而您使用哪些索引器由您自行決定,並需承擔相應的法律責任。本指南涵蓋基礎架構:使用者、路徑、權限、容器網路,以及驗證運作狀態的檢查方式。

如果您從未撰寫過 Compose 檔案,請先閱讀 VPS 的 Docker Compose 基礎知識。本篇教學假設您的伺服器上執行 docker compose version 時已有輸出結果。

硬連結為何會失效,以及其關鍵影響

當 Sonarr 完成下載後,會將檔案匯入您的媒體庫。若下載資料夾與媒體庫資料夾位於同一個檔案系統,匯入動作會以硬連結(hardlink)方式執行:即建立指向磁碟中相同資料的第二個名稱。此過程不佔用額外空間,也不耗費時間。Torrent 軟體可繼續透過舊名稱進行做種,而您的媒體伺服器則可讀取新名稱的檔案。

若兩個資料夾位於不同的檔案系統,核心(kernel)將無法建立該連結。此時 Sonarr 會退而求其次執行複製。一個 40 GB 的影集季節現在會佔用 80 GB 磁碟空間,並耗費數分鐘的輸入與輸出時間;匯入日誌會記錄硬連結失敗,並改為複製檔案。在磁碟空間有限的 VPS 上,這就是使用者為何會在幾週內耗盡空間的原因。

陷阱在於:在容器內部,綁定掛載(bind mount)屬於檔案系統邊界。若將 /mnt/data/torrents 掛載為 /downloads,並將 /mnt/data/media 掛載為 /tv,即便兩者皆位於同一個主機磁碟上,Sonarr 仍會視其為兩個獨立的掛載點,並拒絕跨越它們建立連結。官方 LinuxServer.io 映像檔文件已明確指出:使用獨立的 /downloads/tv 路徑會犧牲硬連結功能。

解決方案是僅使用單一掛載點。每個存取媒體的容器都應掛載同一個磁碟區 /mnt/data:/data,且它們使用的所有路徑皆應為該磁碟區內的子資料夾。單一掛載點、單一檔案系統,即可正常運作硬連結。

建立使用者、群組與資料夾

容器會以數值使用者 ID 寫入檔案,該 ID 由 PUIDPGID 設定。請使用您自己的帳號,以便透過 SSH 讀取並編輯這些檔案,而無需使用 sudo

id -u
id -g

在全新的 Ubuntu VPS 上,兩者通常都會顯示 1000。現在請建立目錄樹。將其放置在存放媒體的磁碟上,並確保整個目錄樹位於該磁碟中。

sudo mkdir -p /mnt/data/torrents/movies /mnt/data/torrents/tv
sudo mkdir -p /mnt/data/media/Movies /mnt/data/media/Shows
sudo chown -R 1000:1000 /mnt/data
sudo chmod -R 775 /mnt/data

在繼續之前,請確認它們確實位於同一個檔案系統:

df --output=source,target /mnt/data/torrents /mnt/data/media

兩行輸出必須顯示相同的來源裝置。若為兩個不同的裝置,則無論容器設定為何,硬連結 (hardlinks) 都將無法運作。

媒體庫資料夾特意命名為 MoviesShows。如果您已經執行 Jellyfin 作為媒體伺服器,請將 /mnt/data/media 掛載至 Jellyfin 作為 /media,其媒體庫將會位於 /media/Movies/media/Shows,這與該指南的設定完全一致。

環境檔案

將各伺服器不同的數值保留在 .env 中,並放置於 Compose 檔案旁。

mkdir -p ~/arr && cd ~/arr

編寫 ~/arr/.env

PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/data

TZ 設定為您所在的時區,例如 Europe/Berlin。arr 應用程式會依據該時區排程任務並標記日誌行,若數值錯誤,後續檢視日誌時將會造成混淆。

Compose 檔案

請寫入 ~/arr/docker-compose.yml

services:
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ./config/prowlarr:/config
    ports:
      - 127.0.0.1:9696:9696
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ./config/sonarr:/config
      - ${DATA_ROOT}:/data
    ports:
      - 127.0.0.1:8989:8989
    restart: unless-stopped

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ./config/radarr:/config
      - ${DATA_ROOT}:/data
    ports:
      - 127.0.0.1:7878:7878
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - WEBUI_PORT=8080
      - TORRENTING_PORT=6881
    volumes:
      - ./config/qbittorrent:/config
      - ${DATA_ROOT}:/data
    ports:
      - 127.0.0.1:8080:8080
      - 6881:6881
      - 6881:6881/udp
    stop_grace_period: "10s"
    restart: unless-stopped

該檔案中有四個項目正在執行實際工作。

${DATA_ROOT}:/data 在三個存取媒體的容器中皆相同。Prowlarr 不會使用此設定,因為 Prowlarr 從不開啟媒體檔案。

每個網頁連接埠皆綁定至 127.0.0.1,因此 Docker 僅會在 loopback 位址上發布該連接埠。若使用單純的 8989:8989,則會在所有介面上發布,而 Docker 自身的防火牆規則會使流量直接繞過 ufw 的 deny 規則。此行為常令使用者感到意外,詳情請參閱 為何 Docker 發布連接埠會繞過 ufw

連接埠 6881 特意在所有介面上發布。這是 torrent 的監聽連接埠,必須能夠接收來自對等節點的連線。請使用 sudo ufw allow 6881 開放此連接埠;若您不熟悉此指令,請參閱 VPS 的 ufw 防火牆基礎知識

各應用程式的設定目錄皆為獨立,僅共用媒體磁碟區。請在首次啟動前建立這些目錄,以確保其擁有者為您的使用者帳號而非 root:

mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose ps

這四項服務皆應讀取 running。截至 2026 年 7 月,這些映像檔發布於 lscr.io,且 latest 標籤會跟隨當前的穩定版本。若您希望自行決定升級時機而非自動更新,請改為鎖定特定版本標籤。

安全地存取網頁介面

由於連接埠僅在 loopback 介面上監聽,目前尚未對外開放。請從您的本機透過 SSH 轉發這些連接埠:

ssh -L 9696:127.0.0.1:9696 -L 8989:127.0.0.1:8989 \
    -L 7878:127.0.0.1:7878 -L 8080:127.0.0.1:8080 you@your-server

現在,在瀏覽器輸入 http://127.0.0.1:8989 即可存取伺服器上的 Sonarr。若需永久存取,請將此堆疊部署在 為多個應用程式配置 TLS 憑證的 Traefik 後方,或透過 您自行架設的 WireGuard VPN 存取伺服器。這些應用程式皆不應僅憑自身的登入頁面就直接暴露於公用網際網路中。

qBittorrent 在首次啟動時會產生一組隨機管理員密碼,並將其輸出至容器日誌中。請讀取該密碼,並在網頁介面中進行變更:

docker compose logs qbittorrent | grep -i password

若未變更密碼,系統會在每次重新啟動時產生新的隨機密碼,屆時您將必須再次查看日誌。

設定各應用程式的路徑

在 qBittorrent 中,開啟 Options,接著進入 Downloads,將預設儲存路徑設為 /data/torrents。請將未完成下載的資料夾保留在相同目錄樹下,例如 /data/torrents/incomplete。若下載完成的位置不在 /data 內,則無法將檔案硬連結(hardlink)至媒體庫。

在 Sonarr 中,開啟 Settings,接著進入 Media Management,並新增根目錄 /data/media/Shows。在 Radarr 中,根目錄則為 /data/media/Movies。這些路徑皆為容器內部的路徑。主機路徑 /mnt/data/media/Shows 將會被拒絕,因為該目錄從容器的角度來看並不存在。

在 Sonarr 與 Radarr 中,皆開啟 Settings,接著進入 Download Clients,並新增 qBittorrent。主機為 qbittorrent,連接埠為 8080。服務名稱可作為主機名稱使用,因為 Compose 會將所有 4 個容器置於同一個網路中,並提供內部 DNS (domain name system) 服務。此處請勿使用 localhost:在 Sonarr 容器內部,localhost 代表的是 Sonarr 本身。

請將 Remote Path Mappings 留空。該功能旨在將下載客戶端回報的路徑,轉換為 arr 應用程式可識別的路徑。透過單一共享的 /data 掛載點,兩個容器對所有路徑的認知已達成一致,這也是此配置值得採用的第二個原因。

將 Prowlarr 連接到 Sonarr 與 Radarr

Prowlarr 會將索引器定義推送至其他應用程式,因此您只需設定一次索引器,無需重複操作。此過程需要從各應用程式取得 API (application programming interface) 金鑰。

在 Sonarr 中,開啟 Settings,接著進入 General,並複製 API key。在 Prowlarr 中,開啟 Settings,接著進入 Apps,新增一個 Sonarr 應用程式,並填寫三個欄位。Prowlarr Server 為 http://prowlarr:9696。Sonarr Server 為 http://sonarr:8989。API Key 則是您剛才複製的值。按下 Test。若結果顯示為綠色,代表 Prowlarr 已透過 Compose 網路成功連線至 Sonarr。請對 http://radarr:7878 的 Radarr 重複上述步驟。

若出現連線被拒絕的紅色錯誤訊息,通常代表服務名稱錯誤或缺少 http:// 前綴。請確認該名稱可從容器內部解析:

docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989

HTTP 狀態碼可證明網路路徑正常。名稱解析錯誤則證明服務名稱有誤。

驗證硬連結是否生效

在確認連結計數(link count)之前,請勿預設設定已正確運作。匯入一個項目後,請比較下載檔案與媒體庫檔案:

stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkv

第一個數字為 inode,第二個數字為連結計數。若檔案已成功建立硬連結,兩處將顯示相同的 inode,且連結計數為 2。若顯示兩個不同的 inode,且連結計數皆為 1,則表示 Sonarr 執行了檔案複製,此時匯入日誌會顯示硬連結失敗。

同時請監控磁碟狀態。執行匯入時,df -h /mnt/data 的數值應幾乎沒有變動,因為硬連結僅新增名稱,並不會複製實際資料。

實際故障原因

匯入時出現權限錯誤,表示容器的 user id 無法寫入該程式庫資料夾。錯誤訊息為 Access to the path ... is denied。請使用 ls -ln /mnt/data/media 檢查擁有者 id 是否與您的 PUID 相符,並請記住,目錄必須具備執行位元 (execute bit),容器才能進入該目錄。

若檔案顯示擁有者為 root,表示容器在主機目錄建立前即已啟動,導致 Docker 以 root 身分建立該目錄。請停止堆疊 (stack)、chown 該目錄,然後重新啟動。

從 qBittorrent 刪除種子後發現程式庫檔案消失,表示匯入方式為複製而非硬連結,或者您刪除的是資料而非種子項目。若使用真正的硬連結 (hardlink),移除其中一個名稱不會影響另一個,因為只有當連結計數歸零時,資料才會被釋放。

若磁碟空間消耗速度快於新增媒體的速度,即為複製問題所導致的最嚴重後果。在購買更多儲存空間前,請先執行上述的 stat 檢查。

此堆疊對 VPS 的需求

這三款 arr 應用程式相當輕量。它們會輪詢索引器、寫入小型 SQLite 資料庫並重新命名檔案。擁有 2 GB RAM 的伺服器即可順暢執行這四個容器。負載來源於其他地方。下載客戶端在處理大型 torrent 時會佔滿磁碟 I/O,而若在同一台機器上進行影片轉碼,媒體伺服器將會耗盡 CPU 資源。請將媒體檔案存放於具備實際傳輸效能的儲存空間中;若伺服器還需執行其他重要任務,請務必對下載客戶端設定頻寬限制。

FAQ

因為從容器的角度來看,來源與目的地位於不同的檔案系統。兩個獨立的掛載點(例如 /downloads/tv)即使皆來自同一個主機磁碟,仍會被視為兩個檔案系統。請將單一父目錄掛載為 /data 至每個容器中,並將下載目錄與媒體庫置於其中,即可實現硬連結。請使用 stat -c '%i %h %n' 檢查兩個檔案,確認它們具有相同的 inode 與 2 的連結計數。

我應該使用什麼 PUID 與 PGID?

請使用擁有媒體目錄之主機帳號的數值 ID,您可以透過 id -uid -g 取得。在全新的 Ubuntu VPS 上,這通常皆為 1000。堆疊中的每個容器都必須使用相同的數值對,否則會導致某個應用程式寫入的檔案,其他應用程式無法修改。變更數值後,請使用 docker compose up -d --force-recreate 重建容器,並使用 chown -R 修復現有檔案的權限。

我需要將這些網頁介面暴露在網際網路上嗎?

不需要,且強烈建議不要這樣做。請在 Compose 檔案中將每個發布的連接埠綁定至 127.0.0.1,然後透過 SSH 通道、VPN 或反向代理伺服器存取這些介面;反向代理伺服器應負責終止 TLS (transport layer security) 並增加額外的驗證機制。直接發布這些連接埠的風險比預期更高,因為 Docker 會插入自己的防火牆規則,導致 ufw deny 規則無法阻擋該流量。

我在哪裡可以找到 qBittorrent 的密碼?

LinuxServer.io 映像檔會在啟動記錄中為 admin 使用者列印一組臨時密碼。請執行 docker compose logs qbittorrent | grep -i password 查看該記錄,然後在 Options 與 Web UI 選項中設定永久密碼。在您設定自己的密碼之前,每次重新啟動容器都會產生新的臨時密碼。

Jellyfin 可以使用相同的資料夾嗎?

可以,這正是此配置方式的目的。將 /mnt/data/media 掛載至媒體伺服器作為 /media,其媒體庫將位於 /media/Movies/media/Shows,而 Sonarr 與 Radarr 則透過 /data/media 寫入相同的目錄。請給予媒體伺服器相同的 PUIDPGID,以確保它能讀取由 arr 堆疊所寫入的檔案。

#sonarr#radarr#prowlarr#docker-compose#self-hosting