Arr stack в одному Docker Compose файлі
Запустіть Prowlarr, Sonarr, Radarr і qBittorrent на VPS з єдиним Docker Compose файлом, спільними PUID і PGID та структурою томів для hardlinks.
Що ви створюєте
Стек Docker Compose для автоматизації медіатеки складається з чотирьох контейнерів: Prowlarr для налаштування індексаторів, Sonarr для серіалів, Radarr для фільмів і qBittorrent як клієнта завантаження. Вони взаємодіють через мережу Compose за іменами служб і використовують спільне дерево каталогів на хості. Встановлення займає небагато часу. Те, що визначає, чи працюватиме стек роками або щотижня створюватиме проблеми, — це структура томів. Тому більшість цього посібника присвячено саме їй.
Стек не знаходить контент самостійно. Prowlarr зберігає індексатори, які ви до нього додаєте. Вибір індексаторів є вашим рішенням і вашою юридичною відповідальністю. Цей посібник охоплює інфраструктурну частину: користувачів, шляхи, дозволи, мережеву взаємодію контейнерів і перевірки, які підтверджують працездатність системи.
Якщо ви ще ніколи не створювали файл Compose, спочатку прочитайте основи Docker Compose для VPS. У цьому дописі передбачається, що docker compose version уже виводить результат на вашому сервері.
Чому жорсткі посилання не працюють і чому це визначає все
Коли Sonarr завершує завантаження, він імпортує файл до медіатеки. Якщо папка завантажень і папка медіатеки розташовані в одній файловій системі, імпорт виконується як жорстке посилання: друге ім’я вказує на ті самі дані на диску. Додатковий простір і час не потрібні. Торент продовжує роздавати дані через старе ім’я, а медіасервер читає їх через нове.
Якщо дві папки розташовані в різних файлових системах, ядро не може створити таке посилання. 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. Використовуйте власний обліковий запис, щоб читати й редагувати ці файли через 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Обидва рядки мають показувати один і той самий пристрій-джерело. Якщо пристрої різні, жорсткі посилання не працюватимуть незалежно від параметрів у конфігурації контейнера.
Каталоги бібліотек навмисно названо 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 передали б цей трафік безпосередньо повз правило deny у ufw. Така поведінка постійно дивує користувачів. Її пояснено в матеріалі чому Docker публікує порти безпосередньо повз ufw.
Порт 6881 навмисно опубліковано на всіх інтерфейсах. Це порт прослуховування torrent, який має бути доступним для вхідних з’єднань від peer-вузлів. Дозвольте його за допомогою 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. Станом на July 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 для кількох застосунків або підключайтеся до сервера через власноруч розгорнуту VPN WireGuard. Жоден із цих застосунків не має бути доступним із публічного інтернету, якщо перед ним встановлено лише власну сторінку входу.
qBittorrent під час першого запуску генерує випадковий пароль адміністратора та записує його в журнал контейнера. Прочитайте його, а потім змініть у вебінтерфейсі:
docker compose logs qbittorrent | grep -i passwordЯкщо пропустити зміну, під час кожного перезапуску генеруватиметься новий випадковий пароль, і вам щоразу доведеться знову шукати його в журналах.
Налаштуйте шляхи в кожній програмі
У qBittorrent відкрийте Options, потім Downloads і встановіть шлях для збереження за замовчуванням: /data/torrents. Папку для незавершених завантажень залиште в тому самому дереві, наприклад /data/torrents/incomplete. Завантаження, яке завершується за межами /data, не можна зв’язати жорстким посиланням із бібліотекою.
У 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 (системи доменних імен). Не використовуйте тут 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. Зупиніть стек, chown каталог і запустіть стек знову.
Якщо після видалення торента з qBittorrent файл у бібліотеці зник, це означає, що під час імпорту було створено копію, яку згодом видалили, або ви видалили дані, а не запис торента. За наявності справжнього жорсткого посилання видалення одного імені не впливає на інше. Дані звільняються лише тоді, коли кількість посилань дорівнює нулю.
Якщо диск заповнюється швидше, ніж обсяг доданих медіафайлів, це найдорожчий прояв проблеми з копіюванням. Перш ніж купувати додаткове сховище, виконайте наведену вище перевірку stat.
Що цьому стеку потрібно від VPS
Три програми arr не потребують багато ресурсів. Вони опитують індексатори, записують дані в невелику базу даних SQLite та перейменовують файли. Сервер із 2 GB RAM без проблем запускає всі чотири контейнери. Навантаження виникає з інших джерел. Клієнт завантаження створює високе навантаження на введення та виведення диска під час роботи з великими торрентами, а медіасервер, який транскодує відео на тому самому сервері, використовує ресурси CPU. Зберігайте медіафайли на томі з високою фактичною пропускною здатністю та встановіть обмеження пропускної здатності для клієнта завантаження, якщо сервер виконує інші важливі завдання.
FAQ
Чому Sonarr копіює файли замість створення жорстких посилань?
Тому що з погляду контейнера джерело та призначення розташовані в різних файлових системах. Два окремі bind-монтування, як-от /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 (безпеку транспортного рівня) і додає власну автентифікацію. Безпосередня публікація портів небезпечніша, ніж здається, оскільки 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.