SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-28

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 уже виводить результат на вашому сервері.

Коли 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

Тому що з погляду контейнера джерело й призначення розташовані в різних файлових системах. Два окремі 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-стеком.

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