วิธีตั้งค่า arr stack ด้วย Docker Compose บน VPS
รวม Prowlarr, Sonarr, Radarr และ qBittorrent ไว้ในไฟล์เดียวบน VPS พร้อมตั้งค่า PUID, PGID และ Volume ให้รองรับการทำ Hardlink อย่างถูกต้อง เพื่อลดปัญหาไฟล์ซ้ำซ้อนในระบบ
สิ่งที่คุณกำลังสร้าง
Docker Compose arr stack ประกอบด้วยคอนเทนเนอร์ 4 ตัวที่ทำหน้าที่จัดการคลังสื่อ: Prowlarr สำหรับตั้งค่า indexer, Sonarr สำหรับซีรีส์, Radarr สำหรับภาพยนตร์ และ qBittorrent ในฐานะไคลเอนต์ดาวน์โหลด บริการเหล่านี้สื่อสารกันผ่านเครือข่ายของ Compose โดยใช้ชื่อบริการ และใช้โครงสร้างโฟลเดอร์ร่วมกันบนโฮสต์ การติดตั้งนั้นทำได้รวดเร็ว ส่วนที่จะตัดสินว่า stack นี้จะทำงานได้ยาวนานหลายปีหรือสร้างปัญหาให้คุณทุกสัปดาห์คือการจัดวาง volume ดังนั้นเนื้อหาส่วนใหญ่ของคู่มือนี้จึงเน้นไปที่เรื่องดังกล่าว
stack นี้ไม่ได้ทำหน้าที่ค้นหาเนื้อหาให้คุณ Prowlarr จะเก็บ indexer ใดก็ตามที่คุณเพิ่มเข้าไป ซึ่งการเลือกใช้ indexer ใดนั้นเป็นการตัดสินใจและความรับผิดชอบทางกฎหมายของคุณเอง คู่มือนี้ครอบคลุมเรื่องระบบพื้นฐาน ได้แก่ ผู้ใช้, path, สิทธิ์การเข้าถึง, เครือข่ายของคอนเทนเนอร์ และการตรวจสอบเพื่อยืนยันว่าระบบทำงานได้จริง
หากคุณไม่เคยเขียนไฟล์ Compose มาก่อน โปรดอ่าน พื้นฐาน Docker Compose สำหรับ VPS ก่อน บทความนี้สมมติว่า docker compose version แสดงผลลัพธ์บนเซิร์ฟเวอร์ของคุณแล้ว โปรดใช้ Docker Engine 28.0.0 หรือใหม่กว่า เนื่องจากเวอร์ชันที่เก่ากว่าอาจอนุญาตให้โฮสต์ในเครือข่ายท้องถิ่นเดียวกันเข้าถึงพอร์ตที่เปิดไว้ที่ localhost ได้
เหตุผลที่ hardlink ใช้งานไม่ได้ และทำไมเรื่องนี้ถึงสำคัญที่สุด
เมื่อ Sonarr ดาวน์โหลดไฟล์เสร็จสิ้น ระบบจะนำไฟล์เข้าสู่คลังสื่อของคุณ หากโฟลเดอร์ดาวน์โหลดและโฟลเดอร์คลังสื่ออยู่บนระบบไฟล์ (filesystem) เดียวกัน และมีการเปิดใช้งาน hardlinking ไว้ Sonarr จะพยายามสร้าง hardlink ซึ่งเป็นการสร้างชื่ออ้างอิงที่สองที่ชี้ไปยังข้อมูลชุดเดียวกันบนดิสก์ วิธีนี้ไม่กินพื้นที่เพิ่มและไม่เสียเวลา ไฟล์ torrent จะยังคง seeding ต่อไปได้จากชื่อเดิม ในขณะที่ media server ของคุณอ่านข้อมูลจากชื่อใหม่
หากโฟลเดอร์ทั้งสองอยู่คนละระบบไฟล์กัน เคอร์เนลจะไม่สามารถสร้างลิงก์ดังกล่าวได้ Sonarr จึงต้องเปลี่ยนไปใช้วิธีคัดลอกไฟล์แทน ส่งผลให้ซีรีส์ขนาด 40 GB กลายเป็น 80 GB บนดิสก์ และต้องใช้เวลาในการอ่านเขียนข้อมูลนานหลายนาที Sonarr จะบันทึกความล้มเหลวในการทำ hardlink ข้ามระบบไฟล์ไว้ในระดับ Trace และบันทึกข้อยกเว้นอื่น ๆ ไว้ในระดับ Debug ดังนั้น log ปกติจึงไม่การันตีว่าจะแสดงข้อความแจ้งเตือนความล้มเหลวเสมอไป บน VPS ที่มีพื้นที่ดิสก์จำกัด นี่คือสาเหตุที่ทำให้พื้นที่เต็มภายในเวลาเพียงหนึ่งสัปดาห์
นี่คือกับดักที่สำคัญ ภายในคอนเทนเนอร์ การทำ bind mount ถือเป็นขอบเขตของระบบไฟล์ หากคุณ mount /mnt/data/torrents เป็น /downloads และ /mnt/data/media เป็น /tv แม้ว่าทั้งสองจะอยู่บนดิสก์ของโฮสต์เดียวกัน แต่ Sonarr จะมองเห็นเป็น mount ที่แยกจากกันและไม่สามารถทำ hardlink ข้ามกันได้ เอกสารประกอบของอิมเมจอย่างเป็นทางการจาก LinuxServer.io ได้ระบุเรื่องนี้ไว้อย่างชัดเจนว่า การใช้พาธ /downloads และ /tv แยกกัน จะทำให้สูญเสียความสามารถในการทำ hardlink
วิธีแก้ไขคือการใช้ mount เพียงจุดเดียว คอนเทนเนอร์ทุกตัวที่เข้าถึงสื่อจะต้องใช้ volume เดียวกันคือ /mnt/data:/data และทุกพาธที่ใช้งานจะต้องเป็นโฟลเดอร์ที่อยู่ภายในนั้น เมื่อมีจุด mount เดียวและระบบไฟล์เดียว hardlink จึงจะทำงานได้อย่างถูกต้อง
สร้างผู้ใช้ กลุ่ม และโฟลเดอร์
คอนเทนเนอร์จะเขียนไฟล์โดยใช้ numeric user id ซึ่งกำหนดโดย PUID และ PGID ให้ใช้บัญชีผู้ใช้ของคุณเองเพื่อให้คุณสามารถอ่านและแก้ไขไฟล์เหล่านั้นผ่าน SSH ได้โดยไม่ต้องใช้ sudo
id -u
id -gโดยปกติแล้วทั้งสองค่าจะแสดงเป็น 1000 บน Ubuntu VPS ที่ติดตั้งใหม่ จากนั้นให้สร้างโครงสร้างไดเรกทอรี โดยวางไว้บนดิสก์ลูกเดียวกับที่เก็บสื่อของคุณ และเก็บโครงสร้างทั้งหมดไว้บนดิสก์ลูกนั้น
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 ในฐานะเซิร์ฟเวอร์สื่อ อยู่แล้ว ให้ mount /mnt/data/media เข้าไปใน Jellyfin ในฐานะ /media ซึ่งคลังสื่อจะไปอยู่ที่ /media/Movies และ /media/Shows ตามที่คู่มือนั้นระบุไว้ทุกประการ
ไฟล์ environment
เก็บค่าที่เปลี่ยนแปลงตามแต่ละเซิร์ฟเวอร์ไว้ใน .env ซึ่งอยู่ถัดจากไฟล์ Compose
mkdir -p ~/arr && cd ~/arrเขียน ~/arr/.env ดังนี้:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataตั้งค่า TZ ให้เป็นโซนเวลาของคุณ เช่น Europe/Berlin แอปพลิเคชันตระกูล arr จะกำหนดเวลาทำงานและประทับเวลาใน log ตามโซนเวลานี้ ดังนั้นหากตั้งค่าผิดพลาดจะทำให้การตรวจสอบ log ในภายหลังเกิดความสับสนได้
ไฟล์ 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 address เท่านั้น การใช้ 8989:8989 เปล่าๆ จะเป็นการเผยแพร่บนทุกอินเทอร์เฟซ และกฎไฟร์วอลล์ของ Docker เองจะนำทราฟฟิกนั้นผ่านกฎ ufw deny ไปโดยตรง พฤติกรรมนี้สร้างความประหลาดใจให้ผู้ใช้อยู่เสมอ และมีการอธิบายไว้ใน เหตุผลที่ Docker เผยแพร่พอร์ตโดยข้าม ufw
พอร์ต 6881 ถูกเผยแพร่บนทุกอินเทอร์เฟซโดยเจตนา นี่คือพอร์ตสำหรับฟังการเชื่อมต่อ torrent และจำเป็นต้องเข้าถึงได้สำหรับการเชื่อมต่อจาก peer ขาเข้า พฤติกรรมของ Docker แบบเดียวกันนี้มีผลที่นี่ ดังนั้นคุณจึงไม่จำเป็นต้องมีกฎ ufw สำหรับพอร์ตนี้ และการเพิ่มกฎเข้าไปก็ไม่ได้เปลี่ยนแปลงอะไร เอกสารของ Docker ซึ่งตรวจสอบเมื่อวันที่ 8 พฤศจิกายน 2026 ระบุว่าทราฟฟิกที่ไปยังพอร์ตที่เผยแพร่จะถูกกำหนดเส้นทางก่อนที่จะถึง chain ที่ ufw ใช้ ดังนั้น sudo ufw allow 6881 จึงไม่มีผลกับพอร์ตนี้ Docker จะเผยแพร่พอร์ตบนทุกอินเทอร์เฟซของโฮสต์ทันทีที่คอนเทนเนอร์เริ่มทำงาน และ ufw deny ก็ไม่สามารถปิดพอร์ตนี้ได้เช่นกัน หากคุณต้องการจำกัดผู้ที่สามารถเชื่อมต่อได้ เอกสารของ Docker ชี้ให้ไปที่กฎใน chain DOCKER-USER ของ Docker เองเมื่อใช้ iptables backend เป็นค่าเริ่มต้น ส่วน nftables backend จะต้องใช้กฎในตาราง nftables แยกต่างหาก ให้ใช้ ufw สำหรับบริการที่ไม่ได้รันใน Docker เช่น SSH และอ่าน พื้นฐานไฟร์วอลล์ ufw สำหรับ VPS หากคุณยังไม่คุ้นเคยกับเครื่องมือนี้
ไดเรกทอรีการตั้งค่าจะแยกกันตามแอปพลิเคชัน และมีเพียง volume ของสื่อบันทึกข้อมูลเท่านั้นที่ใช้ร่วมกัน ให้สร้างไดเรกทอรีเหล่านี้ก่อนเริ่มทำงานครั้งแรกเพื่อให้เป็นกรรมสิทธิ์ของผู้ใช้ของคุณแทนที่จะเป็น root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psบริการทั้งสี่ควรใช้ running ณ วันที่ 8 พฤศจิกายน 2026 อิมเมจเหล่านี้ถูกเผยแพร่บน lscr.io และแท็ก latest จะติดตามเวอร์ชันเสถียรปัจจุบัน ดังนั้นให้ระบุเวอร์ชันแบบเจาะจง (pin) หากคุณต้องการตัดสินใจเรื่องการอัปเกรดด้วยตนเองแทนที่จะให้เกิดการเปลี่ยนแปลงโดยไม่คาดคิด
เข้าถึงเว็บอินเทอร์เฟซอย่างปลอดภัย
เว็บอินเทอร์เฟซถูกผูกไว้กับ loopback ให้ส่งต่อ (forward) ผ่าน 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 บนเซิร์ฟเวอร์ได้ หากต้องการการเข้าถึงแบบถาวร ให้วาง stack ไว้หลัง Traefik พร้อมใบรับรอง TLS สำหรับหลายแอป หรือเข้าถึงเซิร์ฟเวอร์ผ่าน VPN แบบ WireGuard ที่คุณโฮสต์เอง อย่างไรก็ตาม VPN ไม่สามารถเข้าถึงพอร์ตเหล่านี้ได้โดยตรง เอกสารของ Docker ซึ่งตรวจสอบเมื่อวันที่ 8 พฤศจิกายน 2026 ระบุว่าด้วย Docker Engine 28.0.0 หรือใหม่กว่าและการตั้งค่า bridge เริ่มต้น เฉพาะ Docker host เท่านั้นที่สามารถเข้าถึงพอร์ตที่เผยแพร่บนที่อยู่ localhost ได้ หากต้องการใช้ VPN ให้คงการรัน SSH tunnel ผ่าน VPN ไว้ หรือเปลี่ยนการผูก (binding) ในไฟล์ Compose จาก 127.0.0.1 เป็นที่อยู่ของเซิร์ฟเวอร์บนอินเทอร์เฟซ VPN เอง แอปพลิเคชันเหล่านี้ไม่ควรอยู่บนอินเทอร์เน็ตสาธารณะโดยมีเพียงหน้าล็อกอินของตัวเองป้องกันไว้เท่านั้น หากคุณเลือกใช้ reverse proxy และต้องการบัญชีเดียวสำหรับทั้งสี่อินเทอร์เฟซแทนที่จะต้องจำล็อกอินแยกกันสี่บัญชี Authentik ช่วยให้คุณทำ single sign-on แบบ self-hosted ได้ ซึ่ง Traefik สามารถบังคับใช้กับทุกคำขอผ่าน forward auth ได้ Sonarr และ Radarr มีข้อควรระวังสองประการเมื่ออยู่หลัง proxy ดังนั้นก่อนที่คุณจะกำหนดเส้นทางผ่าน Traefik โปรดอ่าน วิธีการรัน Sonarr และ Radarr หลัง reverse proxy เพื่อดูการตั้งค่า Allowed Hosts ที่จะช่วยป้องกันข้อผิดพลาด Invalid Hostname และเหตุผลที่ /api ต้องข้ามการล็อกอิน
qBittorrent จะสร้างรหัสผ่านผู้ดูแลระบบแบบสุ่มในการเริ่มทำงานครั้งแรกและแสดงไว้ใน log ของคอนเทนเนอร์ ให้อ่านรหัสผ่านนั้นแล้วเปลี่ยนในเว็บอินเทอร์เฟซ:
docker compose logs qbittorrent | grep -i passwordหากคุณข้ามการเปลี่ยนรหัสผ่าน ระบบจะสร้างรหัสผ่านสุ่มใหม่ทุกครั้งที่รีสตาร์ท และคุณจะต้องกลับไปดูที่ log ทุกครั้ง
กำหนดพาธภายในแต่ละแอปพลิเคชัน
ใน qBittorrent ให้เปิด Options แล้วไปที่ Downloads จากนั้นกำหนดค่า default save path เป็น /data/torrents ให้เก็บโฟลเดอร์ incomplete-downloads ไว้ในโครงสร้างเดียวกัน เช่น /data/torrents/incomplete การดาวน์โหลดที่เสร็จสิ้นในตำแหน่งอื่นนอกเหนือจาก /data จะไม่สามารถทำ hardlink เข้าสู่ไลบรารีด้วยไฟล์ Compose นี้ได้
ใน Sonarr ให้เปิด Settings แล้วไปที่ Media Management จากนั้นเพิ่ม root folder เป็น /data/media/Shows สำหรับ Radarr ให้กำหนด root folder เป็น /data/media/Movies พาธเหล่านี้คือพาธภายในคอนเทนเนอร์ ส่วนพาธบนโฮสต์อย่าง /mnt/data/media/Shows จะถูกปฏิเสธ เนื่องจากไดเรกทอรีดังกล่าวไม่มีอยู่จริงในมุมมองของคอนเทนเนอร์
ทั้งใน Sonarr และ Radarr ให้เปิด Settings แล้วไปที่ Download Clients จากนั้นเพิ่ม qBittorrent โดยกำหนด host เป็น qbittorrent และ port เป็น 8080 ชื่อบริการสามารถใช้เป็น hostname ได้เนื่องจาก Compose นำคอนเทนเนอร์ทั้ง 4 ตัวมาไว้บนเครือข่ายเดียวกันโดยมีบริการ DNS (domain name system) ภายในรองรับ ห้ามใช้ localhost ในส่วนนี้ เพราะภายในคอนเทนเนอร์ Sonarr นั้น localhost หมายถึงตัว Sonarr เอง
ปล่อยให้ช่อง Remote Path Mappings ว่างไว้ ฟีเจอร์นี้มีไว้เพื่อแปลงพาธที่ไคลเอนต์ดาวน์โหลดรายงานให้เป็นพาธที่แอปพลิเคชันตระกูล arr มองเห็นได้ แต่ด้วยการ mount โฟลเดอร์ /data ร่วมกัน ทั้งสองคอนเทนเนอร์จึงใช้พาธเดียวกันโดยสมบูรณ์ ซึ่งเป็นเหตุผลประการที่สองที่ทำให้การจัดโครงสร้างแบบนี้คุ้มค่าที่จะทำ
การเชื่อมต่อ Prowlarr เข้ากับ Sonarr และ Radarr
Prowlarr จะส่งข้อมูลการตั้งค่า indexer ไปยังแอปพลิเคชันอื่น ทำให้คุณกำหนดค่า indexer เพียงครั้งเดียวแทนที่จะต้องทำซ้ำสองรอบ โดยกระบวนการนี้จำเป็นต้องใช้ API (application programming interface) key จากแต่ละแอปพลิเคชัน
ใน Sonarr ให้เปิดไปที่ Settings จากนั้นเลือก General แล้วคัดลอก API key ออกมา ใน Prowlarr ให้เปิดไปที่ Settings แล้วเลือก Apps จากนั้นเพิ่มแอปพลิเคชัน Sonarr และกรอกข้อมูลในสามช่องที่กำหนด โดย Prowlarr Server คือ http://prowlarr:9696 ส่วน Sonarr Server คือ http://sonarr:8989 และ API Key คือค่าที่คุณคัดลอกมา จากนั้นกด Test หากผลลัพธ์แสดงเป็นสีเขียว หมายความว่า Prowlarr สามารถเชื่อมต่อกับ Sonarr ผ่านเครือข่าย Compose ได้สำเร็จ ให้ทำซ้ำขั้นตอนเดิมกับ Radarr ที่ http://radarr:7878
หากผลลัพธ์แสดงเป็นสีแดงพร้อมข้อความว่าการเชื่อมต่อถูกปฏิเสธ (connection refused) หมายความว่าการเชื่อมต่อไม่สามารถเข้าถึงบริการที่กำลังรอรับคำขอ (listening service) ณ ที่อยู่และพอร์ตที่กำหนดไว้ ให้ตรวจสอบว่าชื่อโฮสต์สามารถแปลงเป็น IP ได้จากภายในคอนเทนเนอร์หรือไม่:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989รหัสสถานะ HTTP ที่ได้รับจะเป็นตัวยืนยันว่าเส้นทางเครือข่ายใช้งานได้ปกติ หากเกิดข้อผิดพลาดในการแปลงชื่อ (name resolution error) หมายความว่าชื่อบริการไม่สามารถระบุตำแหน่งได้ ให้ตรวจสอบการสะกดชื่อและตรวจสอบว่าคอนเทนเนอร์ทั้งสองตัวทำงานอยู่บนเครือข่าย Compose เดียวกันหรือไม่
พิสูจน์ว่ามีการทำ hardlink เกิดขึ้นจริง
อย่าเพิ่งเชื่อมั่นในการตั้งค่าจนกว่าคุณจะได้เห็นจำนวน link count หลังจากนำเข้าไฟล์รายการแรกแล้ว ให้เปรียบเทียบไฟล์ที่ดาวน์โหลดมากับไฟล์ในคลังข้อมูล:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvตัวเลขแรกคือ inode และตัวเลขที่สองคือ link count บนระบบไฟล์เดียวกัน ไฟล์สองไฟล์ที่เป็น hardlink ต่อกันจะแสดง inode เดียวกันและมีค่า link count อย่างน้อย 2 หากมีการสร้าง hardlink เพิ่มเติม ค่านี้ก็จะเพิ่มขึ้นตามไปด้วย หาก inode ต่างกันแสดงว่าไฟล์เหล่านั้นไม่ใช่ hardlink ต่อกัน แต่ก็ไม่ได้เป็นเครื่องยืนยันว่าความพยายามในการทำ hardlink ล้มเหลว นอกจากนี้ Sonarr จะทำการคัดลอกไฟล์โดยไม่พยายามทำ hardlink หากการตั้งค่า hardlink ถูกปิดใช้งานไว้ ความล้มเหลวในการทำ hardlink ข้ามระบบไฟล์จะถูกบันทึกไว้ในระดับ Trace และข้อยกเว้นอื่นๆ เกี่ยวกับ hardlink จะอยู่ในระดับ Debug ดังนั้น log การนำเข้าทั่วไปจึงไม่สามารถรับประกันได้ว่าจะระบุสาเหตุของปัญหาได้เสมอไป
ให้สังเกตการใช้งานดิสก์ด้วยเช่นกัน ค่า df -h /mnt/data ควรจะแทบไม่เปลี่ยนแปลงเมื่อมีการนำเข้าไฟล์ เพราะการทำ hardlink เป็นเพียงการเพิ่มชื่อไฟล์โดยไม่มีการคัดลอกข้อมูลจริง
สิ่งที่ทำให้เกิดข้อผิดพลาด
ข้อผิดพลาดด้านสิทธิ์ในการนำเข้าข้อมูล (import) หมายความว่า user id ของคอนเทนเนอร์ไม่สามารถเขียนข้อมูลลงในโฟลเดอร์ library ได้ ข้อความแจ้งเตือนคือ Access to the path ... is denied ให้ตรวจสอบด้วย ls -ln /mnt/data/media ว่า owner id ตรงกับ PUID ของคุณหรือไม่ และโปรดจำไว้ว่าไดเรกทอรีจำเป็นต้องมี execute bit ก่อนที่คอนเทนเนอร์จะเข้าถึงได้
ไดเรกทอรีที่ถูกครอบครองโดย root อาจเกิดขึ้นจากการเริ่มคอนเทนเนอร์ก่อนที่ไดเรกทอรีบนโฮสต์จะถูกสร้างขึ้น เนื่องจาก Docker จะสร้างไดเรกทอรีสำหรับ bind-mount ขึ้นมาใหม่หากยังไม่มีอยู่ ให้หยุด stack, chown ไดเรกทอรีนั้น แล้วเริ่มการทำงานใหม่อีกครั้ง
การลบ torrent ออกจาก qBittorrent แล้วพบว่าไฟล์ใน library หายไปด้วยนั้น จำเป็นต้องตรวจสอบว่ามีการลบ path ใดไปบ้าง การลบไฟล์ที่ดาวน์โหลดเพียงอย่างเดียวจะไม่ลบสำเนาใน library หรือ hardlink ที่แยกต่างหากออกไป ในกรณีที่เป็น hardlink จริง การลบชื่อหนึ่งออกจะยังคงรักษาอีกชื่อหนึ่งไว้ได้ เนื่องจากข้อมูลจะถูกลบออกก็ต่อเมื่อจำนวน link ลดลงจนเหลือศูนย์เท่านั้น
หากพื้นที่ดิสก์เต็มเร็วกว่าสื่อที่คุณเพิ่มเข้าไป นั่นคือปัญหาการคัดลอกไฟล์ในรูปแบบที่สิ้นเปลืองที่สุด ให้ตรวจสอบด้วย stat ตามที่ระบุไว้ข้างต้นก่อนที่คุณจะตัดสินใจซื้อพื้นที่จัดเก็บข้อมูลเพิ่ม
สิ่งที่ stack นี้ต้องการจาก VPS
แอปพลิเคชันตระกูล arr ทั้งสามตัวใช้ทรัพยากรน้อย โดยทำหน้าที่ดึงข้อมูลจาก indexer, เขียนข้อมูลลงในฐานข้อมูล SQLite ขนาดเล็ก และเปลี่ยนชื่อไฟล์ ภาระงานส่วนใหญ่มาจากส่วนอื่น ตัว download client จะทำให้การอ่านและเขียนดิสก์ (I/O) เต็มขีดจำกัดเมื่อโหลด torrent ขนาดใหญ่ และ media server ที่ทำการแปลงรหัสวิดีโอบนเครื่องเดียวกันจะใช้ CPU สูง ควรเก็บไฟล์สื่อไว้บน volume ที่มีอัตราการรับส่งข้อมูลสูง และตั้งค่าจำกัดแบนด์วิดท์บน download client หากเซิร์ฟเวอร์ต้องทำงานอื่นที่คุณให้ความสำคัญ ควรจัดสรรงบประมาณสำหรับงานเหล่านั้นแยกต่างหาก แทนที่จะคาดหวังว่าจะมีทรัพยากรเหลือเฟือ ตัวอย่างเช่น การ self-host พื้นที่ทำงาน AFFiNE จะต้องใช้ container เพิ่มอีก 4 ตัวพร้อมฐานข้อมูลเบื้องหลัง โดย AFFiNE แนะนำ RAM 2 GB สำหรับการใช้งานพื้นฐาน และ 4 GB สำหรับเอกสารที่มีความยาวเกิน 10,000 คำ สำหรับบริการที่มีวัตถุประสงค์เฉพาะอย่าง การ self-host ระบบติดตามการออกกำลังกาย openGym ควรแยก TLS ของตัวเองและตรวจสอบให้แน่ใจว่าทราบตำแหน่งของโฟลเดอร์ข้อมูลก่อนที่จะฝากประวัติการฝึกซ้อมตลอดทั้งปีไว้กับมัน บริการใดก็ตามที่ประกอบด้วยเว็บแอปพลิเคชัน, ฐานข้อมูล Postgres และ background worker queue จะใช้ทรัพยากรใกล้เคียงกับ AFFiNE ดังนั้นควรตัดสินใจให้ดีว่า การ self-host ระบบสนับสนุนลูกค้า Chatwoot ควรอยู่บนเซิร์ฟเวอร์นี้หรือแยกเครื่องออกไป ก่อนที่คุณจะพบว่าทรัพยากรไม่เพียงพอในระหว่างการนำเข้าข้อมูล ภาระงานแบบกระชาก (bursty workloads) ต้องการความระมัดระวังเป็นพิเศษ เพราะจุดสูงสุดของการใช้งานจะส่งผลกระทบต่อการนำเข้าข้อมูลมากกว่าค่าเฉลี่ย หากคุณกำลังพิจารณา การ self-host OneCLI เพื่อให้แต่ละคนมี agent ใน sandbox ของตนเอง ให้ตรวจสอบตัวเลขขนาดทรัพยากรที่แนะนำเทียบกับทรัพยากรที่ว่างอยู่จริงในขณะที่ qBittorrent กำลังทำงานเต็มกำลัง ไม่ใช่เทียบกับสิ่งที่ free -h แสดงในขณะที่เครื่องไม่ได้ทำงานใดๆ
FAQ
ทำไม Sonarr ถึงคัดลอกไฟล์แทนที่จะทำ hardlink?
เพราะมีการปิดใช้งาน hardlink หรือการทำ hardlink ล้มเหลว ซึ่งรวมถึงกรณีที่ต้นทางและปลายทางอยู่บนระบบไฟล์ (filesystem) คนละตัวกันในมุมมองของคอนเทนเนอร์ การใช้ bind mount แยกกันสองจุด เช่น /downloads และ /tv จะถือเป็น mount point คนละจุดกันแม้ว่าทั้งคู่จะมาจากดิสก์โฮสต์ลูกเดียวกันก็ตาม และ hardlink ไม่สามารถข้าม mount point ได้ ให้ mount ไดเรกทอรีหลักเพียงจุดเดียวเป็น /data ในทุกคอนเทนเนอร์ แล้ววางโฟลเดอร์ดาวน์โหลดและคลังไฟล์ไว้ภายในนั้น การเชื่อมโยงก็จะทำได้ ตรวจสอบผลลัพธ์ด้วย stat -c '%i %h %n' บนไฟล์ทั้งสองไฟล์ หากอยู่บนระบบไฟล์เดียวกัน ทั้งคู่ต้องแสดง inode เลขเดียวกันและมีค่า link count อย่างน้อย 2
ควรใช้ PUID และ PGID ค่าใด?
ให้ใช้เลข ID ของบัญชีผู้ใช้บนโฮสต์ที่เป็นเจ้าของโครงสร้างไฟล์สื่อ ซึ่งคุณสามารถดูได้จาก id -u และ id -g บน Ubuntu VPS ที่ติดตั้งใหม่ ค่านี้มักจะเป็น 1000 สำหรับทั้งสองค่า คอนเทนเนอร์ทุกตัวใน stack ต้องใช้คู่ ID เดียวกัน มิฉะนั้นแอปพลิเคชันหนึ่งจะเขียนไฟล์ที่อีกแอปพลิเคชันไม่สามารถแก้ไขได้ หลังจากเปลี่ยนค่าแล้ว ให้สร้างคอนเทนเนอร์ใหม่ด้วย docker compose up -d --force-recreate และแก้ไขไฟล์ที่มีอยู่เดิมด้วย chown -R
จำเป็นต้องเปิดเผยหน้าเว็บอินเทอร์เฟซเหล่านี้สู่อินเทอร์เน็ตหรือไม่?
ไม่จำเป็น และไม่ควรทำ ให้ bind พอร์ตที่เปิดใช้งานแต่ละพอร์ตเข้ากับ 127.0.0.1 ในไฟล์ Compose จากนั้นเข้าถึงอินเทอร์เฟซผ่าน SSH tunnel หรือผ่าน reverse proxy บนเซิร์ฟเวอร์ที่ทำหน้าที่จัดการ TLS (transport layer security) และเพิ่มการยืนยันตัวตนของคุณเอง สำหรับ Docker Engine 28.0.0 หรือใหม่กว่าและการตั้งค่า bridge เริ่มต้น VPN เพียงอย่างเดียวจะไม่สามารถเข้าถึงพอร์ตที่เปิดไว้บน 127.0.0.1 ได้ เนื่องจาก Docker ทำให้พอร์ตที่เปิดใช้งานนั้นเข้าถึงได้จากตัวโฮสต์เองเท่านั้น Docker เวอร์ชันเก่าจะยอมให้โฮสต์ที่อยู่ในวงเครือข่ายท้องถิ่นเดียวกันเข้าถึงพอร์ตที่เปิดไว้บน localhost ได้ หากคุณต้องการเข้าถึงโดยตรงผ่าน VPN ให้เปิดพอร์ตบน IP address ของ VPN ของเซิร์ฟเวอร์แทน การเปิดพอร์ตเหล่านี้บนอินเทอร์เฟซสาธารณะโดยตรงนั้นแย่กว่าที่เห็น เพราะ Docker จะแทรกกฎ firewall ของตัวเองเข้าไป และกฎ ufw deny จะไม่สามารถหยุดทราฟฟิกดังกล่าวได้
จะหาพาสเวิร์ดของ qBittorrent ได้ที่ไหน?
อิมเมจจาก LinuxServer.io จะพิมพ์พาสเวิร์ดชั่วคราวสำหรับผู้ใช้ admin ไว้ใน log ตอนเริ่มต้นระบบ ให้รัน docker compose logs qbittorrent | grep -i password เพื่ออ่านพาสเวิร์ดดังกล่าว จากนั้นตั้งค่าพาสเวิร์ดถาวรในเมนู Options และ Web UI ระบบจะสร้างพาสเวิร์ดชั่วคราวใหม่ทุกครั้งที่รีสตาร์ทจนกว่าคุณจะตั้งค่าพาสเวิร์ดของคุณเอง
Jellyfin สามารถใช้โฟลเดอร์เดียวกันได้หรือไม่?
ได้ และนั่นคือจุดประสงค์ของโครงสร้างนี้ ให้ mount /mnt/data/media เข้าไปยังเซิร์ฟเวอร์สื่อของคุณในฐานะ /media โดยคลังไฟล์จะอยู่ที่ /media/Movies และ /media/Shows ในขณะที่ Sonarr และ Radarr จะเขียนไฟล์ลงในไดเรกทอรีเดียวกันนั้นผ่าน /data/media ให้กำหนดค่า PUID และ PGID เดียวกันให้กับเซิร์ฟเวอร์สื่อ เพื่อให้สามารถอ่านสิ่งที่ stack ของ arr เขียนลงไปได้