Arr stack у Docker Compose: Prowlarr, Sonarr, Radarr
Запустіть Prowlarr, Sonarr, Radarr і qBittorrent одним Docker Compose на VPS: спільні PUID, PGID і томи збережуть hardlink під час імпорту.
Що ви налаштовуєте
Docker Compose arr stack складається з чотирьох контейнерів, які керують медіатекою: Prowlarr відповідає за налаштування indexer, Sonarr — за серіали, Radarr — за фільми, а qBittorrent — за клієнт завантаження. Вони взаємодіють через мережу Compose за іменами сервісів і спільно використовують одне дерево каталогів на хості. Встановлення займає небагато часу. Від структури томів залежить, чи працюватиме stack роками, чи щотижня створюватиме проблеми, тому більша частина цього посібника присвячена саме їй.
Stack не шукає контент самостійно. Prowlarr використовує indexer, які ви до нього додали, а вибір indexer є вашим рішенням і вашою юридичною відповідальністю. У цьому посібнику розглянуто інфраструктурну частину: користувачів, шляхи, права доступу, мережу контейнерів і перевірки, які підтверджують, що все працює.
Якщо ви ніколи не створювали файл Compose, спочатку прочитайте основи Docker Compose для VPS. У цьому матеріалі передбачається, що docker compose version уже виводить результат на вашому сервері.
Чому hardlink не працює і чому це визначає всю схему
Коли Sonarr завершує завантаження, він імпортує файл до медіатеки. Якщо каталог завантажень і каталог медіатеки розташовані в тій самій файловій системі, імпорт виконується через hardlink: це друге ім’я, що вказує на ті самі дані на диску. Додатковий простір і час не потрібні. Торент продовжує роздавати файл за старим ім’ям, а медіасервер читає його за новим.
Якщо два каталоги розташовані в різних файлових системах, ядро не може створити таке посилання. Sonarr переходить до копіювання. Сезон розміром 40 GB тепер займає 80 GB на диску й потребує кількох хвилин операцій введення-виведення, а журнал імпорту фіксує, що створення hardlink не вдалося і файл було скопійовано. На VPS із фіксованим обсягом диска саме так можна залишитися без вільного місця за тиждень.
Ось де виникає проблема. Усередині контейнера bind mount є межею файлової системи. Якщо змонтувати /mnt/data/torrents як /downloads, а /mnt/data/media як /tv, Sonarr бачитиме два окремі монтування, навіть якщо обидва розташовані на одному диску хоста, і відмовиться створювати посилання між ними. В офіційній документації образу LinuxServer.io це зазначено безпосередньо: використання окремих шляхів /downloads і /tv позбавляє можливості створювати hardlink.
Потрібно використовувати одне монтування. Кожен контейнер, який працює з медіафайлами, отримує той самий єдиний том, /mnt/data:/data, а всі шляхи в контейнері мають бути каталогами всередині нього. Одна точка монтування, одна файлова система — hardlink працює.
Створіть користувача, групу та каталоги
Контейнери записують файли від імені користувача з числовим ідентифікатором, заданим параметрами PUID і PGID. Використовуйте власний обліковий запис, щоб читати й редагувати ці файли через 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В обох рядках має бути вказано той самий вихідний пристрій. Якщо пристрої різні, hard link ніколи не працюватиме, незалежно від налаштувань контейнера.
Каталоги бібліотек навмисно названо 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 опублікував би порт на всіх інтерфейсах, а власні правила firewall Docker передали б цей трафік безпосередньо повз правило ufw deny. Така поведінка постійно дивує користувачів. Її пояснено в матеріалі чому Docker публікує порти безпосередньо через ufw.
Порт 6881 навмисно публікується на всіх інтерфейсах. Це порт для прослуховування torrent-трафіку, і він має бути доступним для вхідних з’єднань від пірів. Дозвольте його за допомогою sudo ufw allow 6881. Якщо ця команда для вас нова, прочитайте основи firewall ufw для VPS.
Каталоги конфігурації є окремими для кожного застосунку, а спільним є лише том із медіафайлами. Створіть їх до першого запуску, щоб вони належали вашому користувачеві, а не root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psУсі чотири сервіси мають читати running. Станом на липень 2026 року ці образи публікуються на 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 на сервері. Для постійного доступу розмістіть стек за Traefik із TLS-сертифікатами для кількох застосунків або підключайтеся до сервера через WireGuard VPN, розгорнутий власноруч. Жоден із цих застосунків не повинен бути доступним із публічного інтернету, якщо перед ним є лише власна сторінка входу. Якщо ви оберете reverse proxy і хочете використовувати один обліковий запис для всіх чотирьох інтерфейсів замість чотирьох окремих облікових записів застосунків, Authentik надає self-hosted single sign-on, який Traefik може застосовувати до кожного запиту через forward auth.
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 розміщує всі чотири контейнери в одній мережі з внутрішньою службою DNS (domain name system). Не використовуйте тут localhost: усередині контейнера Sonarr localhost — це Sonarr.
Залиште Remote Path Mappings порожнім. Ця функція призначена для перетворення шляху, який повідомляє клієнт завантажень, у шлях, доступний застосунку arr. За наявності спільного монтування /data обидва контейнери вже використовують однакові шляхи. Це друга причина, чому така структура варта докладених зусиль.
Підключіть Prowlarr до Sonarr і Radarr
Prowlarr передає визначення індексаторів до інших застосунків, тому індексатор потрібно налаштувати лише один раз, а не двічі. Для цього потрібен ключ API (інтерфейсу програмування застосунків) від кожного з них.
У Sonarr відкрийте Settings, потім General і скопіюйте ключ API. У Prowlarr відкрийте Settings, потім Apps, додайте застосунок Sonarr і заповніть три поля. У полі Prowlarr Server вкажіть http://prowlarr:9696. У полі Sonarr Server вкажіть http://sonarr:8989. У полі API Key вкажіть скопійоване значення. Натисніть Test. Зелений результат означає, що Prowlarr підключився до Sonarr через мережу Compose. Повторіть ці дії для Radarr, використавши http://radarr:7878.
Червоний результат із повідомленням про відмову в підключенні майже завжди означає неправильне ім’я сервісу або відсутній префікс http://. Переконайтеся, що ім’я дозволяється з контейнера:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Код стану HTTP підтверджує, що мережевий маршрут працює. Помилка дозволення імені означає, що ім’я сервісу неправильне.
Перевірте, що жорсткі посилання справді створюються
Не вважайте налаштування правильним, доки не перевірите кількість посилань. Після імпорту одного елемента порівняйте завантажений файл із файлом у бібліотеці:
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 під час імпорту має майже не змінюватися, оскільки жорстке посилання додає ім’я, але не дані.
Що саме ламається
Помилки доступу під час імпорту означають, що ідентифікатор користувача контейнера не може записувати дані до папки бібліотеки. Повідомлення має вигляд Access to the path ... is denied. Перевірте за допомогою ls -ln /mnt/data/media, чи відповідає ідентифікатор власника вашому PUID. Пам’ятайте, що каталог має мати біт виконання, перш ніж контейнер зможе до нього увійти.
Файли, власником яких відображається root, означають, що контейнер запустився до створення каталогу на хості, тому Docker створив його від імені root. Зупиніть stack, chown каталог і запустіть stack знову.
Якщо після видалення торента з qBittorrent файл у бібліотеці також зник, це означає, що під час імпорту було створено копію, яку згодом видалили, або ви видалили дані, а не запис торента. У разі справжнього hardlink видалення одного імені не видаляє інше, оскільки дані звільняються лише після того, як лічильник посилань досягне нуля.
Якщо диск заповнюється швидше, ніж додаються медіафайли, це найдорожчий прояв проблеми з копіюванням. Виконайте перевірку stat вище, перш ніж купувати додаткове сховище.
Що цьому стеку потрібно від VPS
Три застосунки arr невибагливі до ресурсів. Вони опитують indexer-и, записують дані в невелику базу SQLite та перейменовують файли. Сервер із 2 GB RAM без проблем запускає всі чотири контейнери. Навантаження надходить з інших компонентів. Download client активно використовує диски під час завантаження великих torrent-ів, а media server, який транскодує відео на тому самому сервері, навантажує CPU. Зберігайте медіа на volume із реальними показниками пропускної здатності та встановіть обмеження bandwidth для download client, якщо сервер виконує інші важливі для вас завдання. Плануйте ресурси для цих компонентів окремо, а не припускайте, що запасу достатньо: self-hosted робочий простір AFFiNE — це ще чотири контейнери з базою даних, і на сервері з 2 GB цей стек може використати більшу частину пам’яті. Не кожна додаткова служба потребує стільки ресурсів: однопрофільний сервіс на кшталт self-hosted трекера тренувань openGym без проблем працює на тому самому сервері, якщо налаштувати для нього власний TLS і заздалегідь визначити розташування файлу бази даних, перш ніж доручати йому зберігання історії тренувань за рік. Будь-який стек із web application, базою даних Postgres і чергою фонових worker-ів потребує ресурсів, ближчих до верхньої межі, як AFFiNE. Тому вирішіть, чи має self-hosted service desk Chatwoot працювати на цьому сервері, чи на окремому, перш ніж обмеження проявиться посеред імпорту. Для bursty workloads потрібна ще більша обережність, оскільки саме пікове, а не середнє навантаження може конфліктувати з імпортом: якщо ви розглядаєте self-hosted OneCLI, який надає кожному користувачу власного ізольованого агента, порівняйте опубліковані вимоги до ресурсів із фактично доступними ресурсами під час роботи qBittorrent на максимальній швидкості, а не зі значенням, яке free -h показує на неактивному сервері.
FAQ
Чому Sonarr копіює файли замість створення hard link?
Тому що з погляду контейнера джерело й призначення розташовані в різних файлових системах. Два окремі bind mount, наприклад /downloads і /tv, є різними файловими системами, навіть якщо обидва походять з одного диска хоста. Монтуйте один батьківський каталог як /data у кожному контейнері та розмістіть у ньому завантаження й медіатеку. Після цього створення посилання стане можливим. Перевірте результат за допомогою stat -c '%i %h %n' для обох файлів: вони мають мати однаковий inode і кількість посилань 2.
Які PUID і PGID потрібно використовувати?
Використовуйте числовий ідентифікатор облікового запису хоста, якому належить дерево медіафайлів. Отримати його можна за допомогою id -u і id -g. На чистому Ubuntu VPS це зазвичай 1000 для обох значень. Усі контейнери стека повинні використовувати одну й ту саму пару. Інакше один застосунок створюватиме файли, які інший не зможе змінювати. Після зміни значень повторно створіть контейнери за допомогою docker compose up -d --force-recreate і виправте наявні файли за допомогою chown -R.
Чи потрібно відкривати ці вебінтерфейси для доступу з інтернету?
Ні, і цього не слід робити. Прив’яжіть кожен опублікований порт до 127.0.0.1 у файлі Compose. Після цього підключайтеся до інтерфейсів через SSH-тунель, VPN або reverse proxy, який завершує TLS (transport layer security) і додає власну автентифікацію. Безпосередня публікація небезпечніша, ніж здається, оскільки Docker додає власні правила firewall, а правило 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-стеком.