Docker Compose 部署 arr 堆疊與硬連結設定教學
透過單一 Docker Compose 檔案部署 Prowlarr、Sonarr、Radarr 與 qBittorrent。本指南說明如何正確設定掛載點以啟用硬連結,避免磁碟空間因檔案重複複製而耗盡,確保容器間的檔案系統路徑一致。
您正在建構的內容
Docker Compose arr 堆疊由四個容器組成,用於管理媒體庫:Prowlarr 負責索引器設定、Sonarr 負責影集、Radarr 負責電影,而 qBittorrent 則作為下載客戶端。它們透過 Compose 網路以服務名稱進行通訊,並在主機上共用同一個資料夾樹狀結構。安裝過程很短。決定此堆疊是能穩定運作多年,還是每週都出現問題的關鍵在於 Volume 配置,因此本指南大部分內容都在探討此部分。
此堆疊不會自動為您尋找內容。Prowlarr 僅存放您新增的索引器,至於使用哪些索引器由您自行決定,並需承擔相應的法律責任。本指南涵蓋基礎架構:使用者、路徑、權限、容器網路,以及驗證運作狀態的檢查方式。
如果您從未編寫過 Compose 檔案,請先閱讀 VPS 的 Docker Compose 基礎知識。本篇假設您的伺服器上執行 docker compose version 已經能輸出相關資訊。
為何硬連結會失效,以及這為何是關鍵所在
當 Sonarr 完成下載後,會將檔案匯入您的媒體庫。若下載資料夾與媒體庫資料夾位於同一個檔案系統,匯入動作會以硬連結(hardlink)方式執行:這相當於為磁碟上的同一份資料建立第二個名稱。此過程不佔用額外空間,也不耗費時間。當您的媒體伺服器讀取新檔案時,Torrent 客戶端仍可透過舊名稱繼續進行做種(seeding)。
若這兩個資料夾位於不同的檔案系統,核心(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,並將它們使用的所有路徑設為該磁碟區內的子資料夾。統一掛載點,統一檔案系統,硬連結即可正常運作。
建立使用者、群組與資料夾
容器會以 PUID 與 PGID 設定的數值使用者 ID 寫入檔案。請使用您自己的帳號,以便透過 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) 都將無法運作。
媒體庫資料夾特意命名為 Movies 與 Shows。如果您已經在使用 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 會在所有介面上發布,且 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 連線至伺服器。這些應用程式皆不應僅憑自身的登入頁面就直接暴露於公用網際網路。若您選擇使用反向代理,且希望統一管理帳號,而非分別維護四個應用程式的登入資訊,Authentik 可提供自架單一登入 (SSO) 功能,並透過 Traefik 的 forward auth 機制強制執行於每個請求。
qBittorrent 在首次啟動時會產生一組隨機管理員密碼,並輸出至容器日誌。請讀取該密碼,隨後在網頁介面中進行變更:
docker compose logs qbittorrent | grep -i password若未變更密碼,系統會在每次重新啟動時產生新的隨機密碼,屆時您將必須再次查看日誌。
Set the paths inside each application
In qBittorrent, open Options, then Downloads, and set the default save path to /data/torrents. Keep the incomplete-downloads folder inside the same tree, such as /data/torrents/incomplete. A download that finishes anywhere outside /data cannot be hardlinked into the library.
In Sonarr, open Settings, then Media Management, and add the root folder /data/media/Shows. In Radarr the root folder is /data/media/Movies. These are paths inside the container. The host path /mnt/data/media/Shows is rejected, because that directory does not exist from the container's point of view.
In both Sonarr and Radarr, open Settings, then Download Clients, and add qBittorrent. The host is qbittorrent and the port is 8080. The service name works as a hostname because Compose puts all four containers on one network with an internal DNS (domain name system) service. Do not use localhost here: inside the Sonarr container, localhost is Sonarr.
Leave Remote Path Mappings empty. That feature exists to translate a path the download client reports into a path the arr application can see. With one shared /data mount, both containers already agree on every path, which is the second reason this layout is worth the effort.
將 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:8989HTTP 狀態碼代表網路路徑正常。若出現名稱解析錯誤,則代表服務名稱有誤。
驗證硬連結是否生效
在確認連結計數(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 資源。請將媒體檔案存放在具備實際傳輸效能的儲存空間,若伺服器還需執行其他重要任務,請務必限制下載客戶端的頻寬。請為這些額外服務另外編列資源預算,不要假設系統仍有剩餘空間:例如 自架 AFFiNE 工作區 包含四個容器與一個後端資料庫,在 2 GB 的機器上,它會佔用絕大部分的記憶體。並非每個額外服務都如此耗費資源:像 自架 openGym 健身追蹤器 這類單一用途的服務,只要為其配置獨立的 TLS,並在存入一年的訓練紀錄前確認資料庫檔案位置,即可與其他服務共存。任何包含網頁應用程式、Postgres 資料庫與背景工作佇列的服務,其資源需求都接近 AFFiNE 的水準,因此在匯入資料導致系統崩潰前,請先評估 自架 Chatwoot 客服系統 是否適合放在此伺服器,或應獨立部署。突發性工作負載需要更謹慎評估,因為導致系統衝突的是其峰值負載而非平均負載:若您考慮 自架 OneCLI 並為每位使用者提供沙盒化代理,請務必參考其發布的規格需求,並對照 qBittorrent 全速運作時的實際剩餘資源,而非僅參考 free -h 在閒置狀態下顯示的數據。
FAQ
為什麼 Sonarr 會複製檔案而不是建立硬連結 (hardlink)?
因為從容器的角度來看,來源與目的地位於不同的檔案系統。即使兩者皆來自同一顆主機磁碟,兩個獨立的 bind mount(例如 /downloads 與 /tv)仍會被視為兩個檔案系統。請將單一父目錄掛載為容器內的 /data,並將下載目錄與媒體庫置於其中,即可建立硬連結。請使用 stat -c '%i %h %n' 檢查兩個檔案,確認它們具有相同的 inode 且連結計數 (link count) 為 2。
我應該使用什麼 PUID 與 PGID?
請使用擁有媒體目錄之主機帳號的數值 ID,您可以透過 id -u 與 id -g 取得。在全新的 Ubuntu VPS 上,這兩個值通常皆為 1000。堆疊中的每個容器都必須使用相同的數值對,否則會導致某個應用程式寫入的檔案,另一個應用程式無法修改。變更數值後,請使用 docker compose up -d --force-recreate 重建容器,並使用 chown -R 修復現有檔案的權限。
我需要將這些網頁介面暴露到網際網路嗎?
不需要,且強烈建議不要這樣做。請在 Compose 檔案中將每個發布的連接埠綁定至 127.0.0.1,並透過 SSH tunnel、VPN 或負責 TLS termination(傳輸層安全性)並加入額外驗證機制的反向代理來存取這些介面。直接發布連接埠的風險比預期更高,因為 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 寫入相同的目錄。請給予媒體伺服器相同的 PUID 與 PGID,確保它能讀取由 arr 堆疊所寫入的檔案。