Stack arr em um único arquivo Docker Compose
Execute Prowlarr, Sonarr, Radarr e qBittorrent em uma VPS com PUID, PGID e volumes compartilhados para preservar hardlinks nas importações.
O que você vai criar
Uma stack arr do Docker Compose é formada por quatro containers que gerenciam uma biblioteca de mídia: Prowlarr gerencia as configurações dos indexadores, Sonarr gerencia séries, Radarr gerencia filmes e qBittorrent é o cliente de download. Eles se comunicam pela rede do Compose usando o nome do serviço e compartilham uma árvore de diretórios no host. A instalação é curta. O que determina se a stack funcionará por anos ou causará problemas toda semana é o layout dos volumes, por isso a maior parte deste guia trata desse assunto.
A stack não encontra conteúdo por você. O Prowlarr mantém os indexadores adicionados a ele, e a escolha dos indexadores é sua, assim como a responsabilidade legal pelo uso deles. Este guia trata da infraestrutura: usuários, caminhos, permissões, rede entre containers e verificações que comprovam que tudo está funcionando.
Se você nunca escreveu um arquivo Compose, leia primeiro o básico do Docker Compose para uma VPS. Este artigo pressupõe que docker compose version já exibe alguma saída no seu servidor.
Por que os hardlinks falham e por que esse é o ponto central
Quando o Sonarr termina um download, ele importa o arquivo para a biblioteca. Se a pasta de downloads e a pasta da biblioteca estiverem no mesmo sistema de arquivos, a importação será feita com um hardlink: um segundo nome apontando para os mesmos dados no disco. Isso não usa espaço adicional nem leva tempo. O torrent continua fazendo seed pelo nome antigo, enquanto o servidor de mídia lê o novo.
Se as duas pastas estiverem em sistemas de arquivos diferentes, o kernel não poderá criar esse link. O Sonarr usará uma cópia como alternativa. Uma temporada de 40 GB agora ocupará 80 GB de disco e consumirá vários minutos de entrada e saída, e o log de importação registrará que o hardlink falhou e que o arquivo foi copiado. Em um VPS com uma cota fixa de disco, é assim que as pessoas ficam sem espaço em uma semana.
Esta é a armadilha. Dentro de um contêiner, uma montagem bind é um limite de sistema de arquivos. Monte /mnt/data/torrents como /downloads e /mnt/data/media como /tv; embora ambos estejam no mesmo disco do host, o Sonarr verá duas montagens separadas e se recusará a criar links entre elas. A documentação oficial da imagem do LinuxServer.io afirma isso diretamente: usar os caminhos separados /downloads e /tv elimina a possibilidade de criar hardlinks.
A correção é usar uma montagem. Todo contêiner que acessa mídia recebe o mesmo volume único, /mnt/data:/data, e cada caminho usado por eles é uma pasta dentro dele. Um ponto de montagem, um sistema de arquivos, hardlinks funcionando.
Crie o usuário, o grupo e as pastas
Os contêineres gravam arquivos usando um ID numérico de usuário, definido por PUID e PGID. Use sua própria conta para poder ler e editar esses arquivos por SSH sem sudo.
id -u
id -gAmbos geralmente exibem 1000 em uma VPS Ubuntu recém-criada. Agora crie a árvore. Coloque-a no disco que contém sua mídia e mantenha toda a árvore nesse mesmo disco.
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/dataVerifique se ela realmente está em um único sistema de arquivos antes de continuar:
df --output=source,target /mnt/data/torrents /mnt/data/mediaAs duas linhas devem exibir o mesmo dispositivo de origem. Dispositivos diferentes significam que hardlinks nunca funcionarão, independentemente do que você definir na configuração do contêiner.
As pastas da biblioteca são nomeadas Movies e Shows de propósito. Se você já executa o Jellyfin como servidor de mídia, monte /mnt/data/media no Jellyfin como /media, e as bibliotecas serão criadas em /media/Movies e /media/Shows, exatamente onde aquele guia as coloca.
O arquivo de ambiente
Mantenha em .env, ao lado do arquivo Compose, os valores que variam de um servidor para outro.
mkdir -p ~/arr && cd ~/arrEscreva ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataDefina TZ como seu próprio fuso horário, por exemplo, Europe/Berlin. Os aplicativos arr agendam tarefas e registram as linhas de log nesse fuso, portanto um valor incorreto deixará todos os logs confusos posteriormente.
O arquivo Compose
Escreva ~/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-stoppedQuatro itens nesse arquivo fazem o trabalho principal.
${DATA_ROOT}:/data é idêntico nos três contêineres que acessam arquivos de mídia. O Prowlarr não recebe esse mapeamento, porque nunca abre um arquivo de mídia.
Todas as portas web são vinculadas a 127.0.0.1, portanto o Docker as publica somente no endereço de loopback. Um 8989:8989 simples publicaria a porta em todas as interfaces, e as próprias regras de firewall do Docker encaminhariam esse tráfego diretamente, ignorando uma regra deny do ufw. Esse comportamento surpreende muitas pessoas e é explicado em por que o Docker publica portas diretamente através do ufw.
A porta 6881 é publicada em todas as interfaces de propósito. Essa é a porta de escuta do torrent e ela precisa estar acessível para conexões de entrada de peers. Permita-a com sudo ufw allow 6881 e consulte os fundamentos do firewall ufw para uma VPS se esse comando for novo para você.
Os diretórios de configuração são separados por aplicação, e somente o volume de mídia é compartilhado. Crie-os antes da primeira inicialização para que pertençam ao seu usuário, e não ao root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psOs quatro serviços devem ler running. Em julho de 2026, essas imagens são publicadas em lscr.io, e a tag latest acompanha a versão estável atual. Portanto, use uma tag de versão específica se quiser que as atualizações sejam uma decisão, e não uma surpresa.
Acesse as interfaces web com segurança
Como as portas estão vinculadas ao loopback, nada está exposto ainda. Encaminhe-as por SSH a partir da sua própria máquina:
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-serverAgora http://127.0.0.1:8989 no seu navegador acessa o Sonarr no servidor. Para acesso permanente, coloque a stack atrás do Traefik com certificados TLS para vários aplicativos ou acesse o servidor por meio de uma VPN WireGuard hospedada por você. Nenhum desses aplicativos deve ficar na internet pública protegido apenas pela própria página de login.
O qBittorrent gera uma senha de administrador aleatória na primeira inicialização e a exibe no log do contêiner. Leia a senha e altere-a na interface web:
docker compose logs qbittorrent | grep -i passwordSe você não fizer essa alteração, uma nova senha aleatória será gerada a cada reinicialização, e você precisará consultar os logs novamente.
Defina os caminhos dentro de cada aplicação
No qBittorrent, abra Options, depois Downloads, e defina o caminho padrão de salvamento como /data/torrents. Mantenha a pasta de downloads incompletos dentro da mesma árvore, como /data/torrents/incomplete. Um download concluído fora de /data não pode receber um hardlink para a biblioteca.
No Sonarr, abra Settings, depois Media Management, e adicione a pasta raiz /data/media/Shows. No Radarr, a pasta raiz é /data/media/Movies. Esses são caminhos dentro do container. O caminho do host /mnt/data/media/Shows é rejeitado porque esse diretório não existe do ponto de vista do container.
No Sonarr e no Radarr, abra Settings, depois Download Clients, e adicione o qBittorrent. O host é qbittorrent e a porta é 8080. O nome do serviço funciona como hostname porque o Compose coloca os quatro containers na mesma rede, com um serviço interno de DNS (domain name system). Não use localhost aqui: dentro do container do Sonarr, localhost é o Sonarr.
Deixe Remote Path Mappings vazio. Esse recurso traduz um caminho informado pelo cliente de download para um caminho que a aplicação arr pode acessar. Com um único mount compartilhado /data, os dois containers já usam os mesmos caminhos. Esse é o segundo motivo pelo qual vale a pena usar este layout.
Conectar o Prowlarr ao Sonarr e ao Radarr
O Prowlarr envia as definições dos indexadores para os outros aplicativos. Assim, você configura um indexador uma vez, em vez de duas. Ele precisa de uma chave de API (interface de programação de aplicativos) de cada um.
No Sonarr, abra Settings, depois General, e copie a chave de API. No Prowlarr, abra Settings, depois Apps, adicione um aplicativo Sonarr e preencha três campos. Prowlarr Server é http://prowlarr:9696. Sonarr Server é http://sonarr:8989. API Key é o valor copiado. Pressione Test. Um resultado verde significa que o Prowlarr alcançou o Sonarr pela rede do Compose. Repita o procedimento com o Radarr em http://radarr:7878.
Um resultado vermelho informando que a conexão foi recusada quase sempre significa que o nome do serviço está incorreto ou que falta o prefixo http://. Confirme se o nome é resolvido de dentro do contêiner:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Um código de status HTTP comprova que o caminho de rede está funcionando. Um erro de resolução de nome comprova que o nome do serviço está incorreto.
Confirme que os hardlinks estão sendo criados
Não confie na configuração até verificar a contagem de links. Depois que um item for importado, compare o arquivo baixado com o arquivo da biblioteca:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvO primeiro número é o inode, e o segundo é a contagem de links. Um arquivo criado com hardlink mostra o mesmo inode nos dois locais e uma contagem de links de 2. Dois inodes diferentes, cada um com uma contagem de links de 1, significam que o Sonarr copiou o arquivo. O log de importação informará que o hardlink falhou.
Monitore o disco também. df -h /mnt/data deve quase não mudar durante uma importação, porque um hardlink adiciona um nome, mas não adiciona dados.
O que realmente falha
Erros de permissão durante a importação significam que o ID de usuário do contêiner não pode gravar na pasta da biblioteca. A mensagem é Access to the path ... is denied. Use ls -ln /mnt/data/media para verificar se o ID do proprietário corresponde ao seu PUID e lembre-se de que os diretórios precisam do bit de execução antes que o contêiner possa acessá-los.
Arquivos que parecem pertencer a root significam que o contêiner foi iniciado antes de o diretório do host existir, então o Docker o criou como root. Pare a stack, chown o diretório e inicie a stack novamente.
Excluir um torrent do qBittorrent e descobrir que o arquivo da biblioteca desapareceu significa que a importação foi uma cópia que depois foi removida, ou que você excluiu os dados em vez da entrada do torrent. Com um hardlink real, remover um nome mantém o outro intacto, porque os dados só são liberados quando a contagem de links chega a zero.
Um disco que fica cheio mais rapidamente do que a mídia adicionada é o problema de cópia em sua forma mais dispendiosa. Execute a verificação stat acima antes de comprar mais armazenamento.
O que esta stack precisa de um VPS
Os três aplicativos arr são leves. Eles consultam indexadores, gravam dados em um banco de dados SQLite pequeno e renomeiam arquivos. Um servidor com 2 GB de RAM executa os quatro contêineres com folga. A carga vem de outras tarefas. Um cliente de download satura a entrada e a saída do disco ao lidar com torrents grandes, e um servidor de mídia que transcodifica vídeo na mesma máquina consumirá a CPU. Mantenha a mídia em um volume com taxa de transferência real e defina um limite de largura de banda no cliente de download se o servidor estiver executando outra tarefa importante.
FAQ
Por que o Sonarr copia os arquivos em vez de criar hard links?
Isso ocorre porque a origem e o destino estão em sistemas de arquivos diferentes do ponto de vista do container. Dois bind mounts separados, como /downloads e /tv, são dois sistemas de arquivos, mesmo quando ambos vêm de um único disco do host. Monte um único diretório pai como /data em todos os containers e coloque os downloads e a biblioteca dentro dele. Assim, o link será possível. Confirme o resultado com stat -c '%i %h %n' nos dois arquivos: o mesmo inode e uma contagem de links igual a 2.
Quais PUID e PGID devo usar?
Use o id numérico da conta do host que é proprietária da árvore de mídia. Obtenha esses valores com id -u e id -g. Em uma VPS Ubuntu nova, normalmente ambos são 1000. Todos os containers da stack devem usar o mesmo par. Caso contrário, uma aplicação poderá criar arquivos que outra não consegue modificar. Depois de alterar os valores, recrie os containers com docker compose up -d --force-recreate e corrija os arquivos existentes com chown -R.
Preciso expor essas interfaces web à internet?
Não, e você não deve fazer isso. Vincule cada porta publicada a 127.0.0.1 no arquivo Compose. Depois, acesse as interfaces por meio de um túnel SSH, de uma VPN ou de um proxy reverso que encerre o TLS (segurança da camada de transporte) e adicione sua própria autenticação. Publicá-las diretamente é mais perigoso do que parece, porque o Docker insere suas próprias regras de firewall. Uma regra deny do ufw não interromperá esse tráfego.
Onde encontro a senha do qBittorrent?
A imagem do LinuxServer.io exibe uma senha temporária para o usuário admin no log de inicialização. Execute docker compose logs qbittorrent | grep -i password para lê-la. Depois, defina uma senha permanente em Options e Web UI. Uma nova senha temporária é gerada a cada reinicialização até que você defina uma senha própria.
O Jellyfin pode usar as mesmas pastas?
Sim. Esse é o objetivo desse layout. Monte /mnt/data/media no servidor de mídia como /media. As bibliotecas ficarão em /media/Movies e /media/Shows, enquanto Sonarr e Radarr gravarão nesses mesmos diretórios por meio de /data/media. Dê ao servidor de mídia o mesmo PUID e PGID para que ele possa ler o que a stack do arr grava.