Docker Composeでarrスタックを1ファイルに構築
Prowlarr、Sonarr、Radarr、qBittorrentをVPSで1つのDocker Composeファイルから運用します。共通のPUID、PGIDとボリューム構成で、ハードリンクを機能させる方法を解説します。
構築するもの
Docker Compose の arr スタックは、メディアライブラリを管理する4つのコンテナで構成されます。Prowlarr はインデクサー設定、Sonarr はシリーズ、Radarr は映画、qBittorrent はダウンロードクライアントを担当します。これらは Compose ネットワーク上でサービス名を使って通信し、ホスト上の1つのフォルダーツリーを共有します。インストール自体は短時間で完了します。スタックが何年も安定して動作するか、毎週のように問題が発生するかを左右するのはボリュームのレイアウトです。そのため、このガイドでは主にレイアウトについて説明します。
このスタックがコンテンツを自動的に探してくれるわけではありません。Prowlarr には追加したインデクサーが登録されます。どのインデクサーを使用するかは、利用者自身の判断と法的責任によります。このガイドでは、ユーザー、パス、権限、コンテナネットワーク、および動作を確認するためのチェックについて説明します。
Compose ファイルを作成したことがない場合は、先にVPS 向けの Docker Compose の基礎を読んでください。この投稿では、サーバー上ですでに docker compose version を実行すると何らかの出力が表示されることを前提とします。
ハードリンクが機能しない理由と、それがすべての要点である理由
Sonarrがダウンロードを完了すると、ファイルをライブラリにインポートします。ダウンロードフォルダーとライブラリフォルダーが同じファイルシステム上にある場合、インポートにはハードリンクが使われます。これは、ディスク上の同じデータを指す2つ目の名前です。追加の容量も処理時間も必要ありません。torrentは元の名前からシードを続け、メディアサーバーは新しい名前から読み取ります。
2つのフォルダーが異なるファイルシステム上にある場合、kernelはそのリンクを作成できません。Sonarrはコピーに切り替えます。40 GBのシーズンでは、ディスク使用量が80 GBになり、入出力に数分かかります。また、インポートログにはハードリンクに失敗し、代わりにファイルをコピーしたことが記録されます。ディスク容量が固定されたVPSでは、これが1週間で容量不足になる原因です。
ここに落とし穴があります。コンテナ内では、bind mountがファイルシステムの境界になります。/mnt/data/torrentsを/downloadsとして、/mnt/data/mediaを/tvとしてマウントすると、どちらも1台のホストディスク上にある場合でも、Sonarrには2つの別々のマウントとして認識されます。そのため、両者をまたぐリンクの作成を拒否します。公式のLinuxServer.ioイメージのドキュメントにも、これは明記されています。/downloadsと/tvを別々のパスとして使用すると、ハードリンク機能が失われます。
解決策は、マウントを1つにすることです。メディアを扱うすべてのコンテナに同じ単一ボリューム/mnt/data:/dataを割り当て、各コンテナが使用するパスをその中のフォルダーにします。マウントポイントが1つで、ファイルシステムも1つなら、ハードリンクが機能します。
ユーザー、グループ、フォルダーを作成する
コンテナは、PUID と PGID で設定された数値のユーザーIDとしてファイルを書き込みます。自分のアカウントを使用すると、sudo なしでSSH経由でファイルを読み取り、編集できます。
id -u
id -g新しいUbuntu VPSでは、通常どちらも 1000 を出力します。次に、ディレクトリツリーを作成します。メディアを保存するディスク上に配置し、ツリー全体をその1台のディスクに保持してください。
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先に進む前に、本当に1つのファイルシステム上にあることを確認します。
df --output=source,target /mnt/data/torrents /mnt/data/media両方の行に同じソースデバイスが表示される必要があります。異なるデバイスが表示される場合、コンテナ設定をどのように変更してもハードリンクは機能しません。
ライブラリフォルダーの名前が Movies と Shows なのは意図的です。すでに メディアサーバーとしてJellyfinを実行している場合は、/mnt/data/media をJellyfinに /media としてマウントしてください。ライブラリは、該当ガイドの配置どおり、/media/Movies と /media/Shows に配置されます。
環境ファイル
サーバーごとに変わる値は、Compose ファイルと同じ場所にある .env に保存します。
mkdir -p ~/arr && cd ~/arr~/arr/.env を記述します。
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataTZ を、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このファイルでは、4つの設定が重要な役割を果たします。
メディアを扱う3つのコンテナでは、${DATA_ROOT}:/data が同じです。Prowlarrはメディアファイルを開かないため、これを設定しません。
すべてのWebポートは 127.0.0.1 にバインドされるため、Dockerはループバックアドレスでのみ公開します。単純な 8989:8989 では、すべてのインターフェースで公開されます。その場合、Docker独自のファイアウォールルールにより、トラフィックがufwの deny ルールをそのまま通過します。この動作は非常に分かりにくく、Dockerがufwを通過してポートを直接公開する理由で説明しています。
ポート6881は意図的にすべてのインターフェースで公開します。これはtorrentのリスニングポートであり、着信するpeer接続から到達できる必要があります。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 ps4つのサービスすべてで running を読み込む必要があります。2026年7月現在、これらのイメージは lscr.io で公開されており、latest タグは現在のstableリリースを追跡します。アップグレードを予期せぬ変更ではなく、判断に基づくものにしたい場合は、代わりにバージョンタグを固定してください。
Webインターフェースに安全にアクセスする
ポートは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経由でサーバーにアクセスする方法を使用します。これらのアプリケーションを、ログインページだけを設けた状態でパブリックインターネットに公開しないでください。
qBittorrentは初回起動時にランダムな管理者パスワードを生成し、そのパスワードをコンテナログに出力します。パスワードを確認してから、Webインターフェースで変更します。
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が4つすべてのコンテナーを、内部DNS(domain name system)サービスを備えた1つのネットワークに接続するため、サービス名をホスト名として使用できます。ここでlocalhostは使用しないでください。Sonarrコンテナー内では、localhostがSonarrです。
Remote Path Mappingsは空のままにします。この機能は、ダウンロードクライアントが報告するパスを、arrアプリケーションから参照できるパスに変換するためのものです。共有/dataマウントを1つ使用すれば、両方のコンテナーがすでにすべてのパスを共有できます。これが、この構成に手間をかける2つ目の理由です。
ProwlarrをSonarrとRadarrに接続する
Prowlarrはインデクサーの定義を他のアプリケーションに反映するため、インデクサーを2回設定する必要がありません。各アプリケーションのAPI(アプリケーションプログラミングインターフェイス)キーが必要です。
SonarrでSettingsを開き、Generalに移動してAPIキーをコピーします。ProwlarrでSettingsを開き、Appsに移動してSonarrアプリケーションを追加し、3つのフィールドに入力します。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ステータスコードが返れば、ネットワーク経路に問題はありません。名前解決エラーが返る場合は、サービス名が間違っています。
hardlink が実際に作成されていることを確認する
link count を確認するまで、設定が正しいとは判断しないでください。1つの項目をimportしたら、downloadしたfileとlibraryのfileを比較します。
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkv最初の数値はinode、2番目の数値はlink countです。hardlinkされたfileでは、両方の場所で同じinodeが表示され、link countは2になります。異なるinodeが表示され、それぞれのlink countが1の場合、Sonarrはfileをcopyしています。import logにはhardlinkが失敗したと記録されます。
diskの使用量も確認してください。import時にdf -h /mnt/dataはほとんど変化しないはずです。hardlinkは名前を追加するだけで、データは追加しないためです。
実際に壊れるもの
インポート時の権限エラーは、コンテナのユーザーIDにライブラリフォルダーへの書き込み権限がないことを示します。メッセージは Access to the path ... is denied です。ls -ln /mnt/data/media で所有者IDが PUID と一致することを確認してください。また、コンテナがディレクトリに入るには、ディレクトリに実行ビットが必要です。
root が所有者として表示されるファイルは、ホスト側のディレクトリが存在する前にコンテナが起動したことを示します。そのため、Dockerがディレクトリをrootとして作成しました。スタックを停止し、ディレクトリを chown してから、再度起動してください。
qBittorrentからtorrentを削除した後にライブラリファイルがなくなっている場合、インポートがコピーで、後から削除された可能性があります。または、torrentエントリではなくデータを削除した可能性があります。実際のハードリンクでは、一方の名前を削除しても、もう一方はそのまま残ります。データが解放されるのは、リンク数が0になった場合だけです。
追加したメディアよりも速くディスク容量が埋まる場合、これはコピーによる問題が最も大きく現れた状態です。ストレージを追加購入する前に、上記の stat チェックを実行してください。
このスタックでVPSに必要な条件
3つのarrアプリケーションは軽量です。インデクサーをポーリングし、小規模なSQLiteデータベースに書き込み、ファイル名を変更します。RAMが2 GBのサーバーなら、4つすべてのコンテナを無理なく実行できます。負荷は別の処理から発生します。ダウンロードクライアントは、大容量のtorrentでディスクの読み書きを飽和させます。同じサーバー上でメディアサーバーが動画をトランスコードすると、CPUを消費します。メディアは実効スループットの高いボリュームに保存してください。サーバーで他に重要な処理を実行する場合は、ダウンロードクライアントに帯域幅制限を設定してください。
FAQ
Sonarrがファイルをハードリンクせず、コピーするのはなぜですか?
コンテナから見ると、ソースと宛先が異なるファイルシステム上にあるためです。/downloads と /tv のような2つの個別の bind mount は、同じホストディスクに由来する場合でも、異なるファイルシステムとして扱われます。すべてのコンテナで単一の親ディレクトリを /data としてマウントし、その中にダウンロード先とライブラリを配置してください。これでリンクを作成できます。両方のファイルで stat -c '%i %h %n' を実行し、同じ inode とリンク数 2 になっていることを確認してください。
使用する PUID と PGID は何ですか?
メディアツリーを所有するホストアカウントの数値 ID を使用してください。これは id -u と id -g で確認できます。新規の Ubuntu VPS では、通常は両方とも 1000 です。スタック内のすべてのコンテナで同じ組み合わせを使用する必要があります。値が異なると、あるアプリケーションが別のアプリケーションで変更できないファイルを書き込みます。値を変更したら、docker compose up -d --force-recreate でコンテナを再作成し、chown -R で既存のファイルを修正してください。
これらの Web インターフェースをインターネットに公開する必要がありますか?
いいえ。公開すべきではありません。Compose ファイルで、公開する各ポートを 127.0.0.1 にバインドしてください。その後、SSH トンネル、VPN、または TLS(transport layer security)を終端し、独自の認証を追加するリバースプロキシ経由でインターフェースにアクセスします。直接公開すると、見た目以上に危険です。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 スタックが書き込んだファイルを読み取れるようにしてください。