Stack arr em um único Docker Compose
Execute Prowlarr, Sonarr, Radarr e qBittorrent em uma VPS com um Compose, PUID, PGID e volumes compartilhados para manter hardlinks funcionando.
O que você vai criar
Uma stack arr em Docker Compose é composta por quatro contentores que gerem uma biblioteca multimédia: Prowlarr para as definições dos indexadores, Sonarr para séries, Radarr para filmes e qBittorrent como cliente de transferência. Estes serviços comunicam entre si através da rede do Compose, usando o nome do serviço, e partilham uma árvore de diretórios no host. A instalação é curta. O que determina se a stack funciona durante anos ou causa problemas todas as semanas é a organização dos volumes, por isso a maior parte deste guia é dedicada a esse tema.
A stack não encontra conteúdo por si só. O Prowlarr gere os indexadores que adicionar, e a escolha dos indexadores é da sua responsabilidade e está sujeita às obrigações legais aplicáveis. Este guia aborda a infraestrutura: utilizadores, caminhos, permissões, rede dos contentores e verificações que confirmam o funcionamento.
Se nunca escreveu um ficheiro Compose, leia primeiro os conceitos básicos do Docker Compose para uma VPS. Este artigo pressupõe que docker compose version já apresenta algum resultado no seu servidor.
Por que as hardlinks falham e por que isso é essencial
Quando o Sonarr termina um download, importa o ficheiro para a sua biblioteca. Se a pasta de downloads e a pasta da biblioteca estiverem no mesmo sistema de ficheiros, a importação usa uma hardlink: um segundo nome que aponta para os mesmos dados no disco. Não ocupa espaço adicional nem demora tempo significativo. O torrent continua a fazer seeding através do nome antigo, enquanto o servidor de media lê o novo.
Se as duas pastas estiverem em sistemas de ficheiros diferentes, o kernel não consegue criar essa ligação. O Sonarr faz fallback para uma cópia. Uma temporada de 40 GB passa a ocupar 80 GB de disco e demora vários minutos de entrada e saída, e o log de importação regista que a hardlink falhou e que o ficheiro foi copiado. Num VPS com um limite fixo de disco, é assim que se esgota o espaço numa semana.
Aqui está o problema. Dentro de um contentor, um bind mount é uma fronteira entre sistemas de ficheiros. Monte /mnt/data/torrents como /downloads e /mnt/data/media como /tv e, embora ambos estejam no mesmo disco do host, o Sonarr vê dois mounts separados e recusa criar uma hardlink entre eles. A documentação oficial da imagem LinuxServer.io diz isto diretamente: usar os caminhos separados /downloads e /tv elimina a possibilidade de criar hardlinks.
A correção é usar um único mount. Cada contentor que acede a media recebe o mesmo volume único, /mnt/data:/data, e todos os caminhos utilizados são pastas dentro desse volume. Um ponto de montagem, um sistema de ficheiros e hardlinks funcionais.
Crie o utilizador, o grupo e as pastas
Os contentores escrevem ficheiros com um ID de utilizador numérico, definido por PUID e PGID. Use a sua própria conta para poder ler e editar esses ficheiros por SSH sem sudo.
id -u
id -gAmbos normalmente apresentam 1000 numa VPS Ubuntu nova. Agora crie a árvore. Coloque-a no disco onde estão os seus conteúdos multimé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/dataConfirme que é realmente um único sistema de ficheiros antes de continuar:
df --output=source,target /mnt/data/torrents /mnt/data/mediaAs duas linhas têm de apresentar o mesmo dispositivo de origem. Dois dispositivos diferentes significam que as hardlinks nunca funcionarão, independentemente do que definir na configuração do contentor.
As pastas da biblioteca chamam-se Movies e Shows de propósito. Se já executa Jellyfin como servidor multimédia, monte /mnt/data/media no Jellyfin como /media, e as bibliotecas ficarão em /media/Movies e /media/Shows, exatamente onde esse guia as coloca.
O ficheiro de ambiente
Mantenha em .env os valores que variam por servidor, junto do ficheiro Compose.
mkdir -p ~/arr && cd ~/arrEscreva ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataDefina TZ com a sua própria zona, como Europe/Berlin. As aplicações arr agendam tarefas e registam linhas de log nessa zona, pelo que um valor incorreto torna todos os logs confusos mais tarde.
O ficheiro 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-stoppedHá quatro elementos nesse ficheiro que fazem o trabalho efetivo.
${DATA_ROOT}:/data é idêntico nos três contentores que acedem a ficheiros multimédia. O Prowlarr não o recebe porque nunca abre um ficheiro multimédia.
Todas as portas web estão associadas a 127.0.0.1, por isso o Docker publica-as apenas 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, ultrapassando uma regra deny do ufw. Este comportamento surpreende constantemente os utilizadores e é explicado em por que motivo 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 tem de estar acessível para ligações de entrada de pares. Permita-a com sudo ufw allow 6881 e consulte os fundamentos da firewall ufw para um VPS se esse comando for novo para si.
Os diretórios de configuração são separados por aplicação, e apenas o volume de multimédia é partilhado. Crie-os antes do primeiro arranque para que pertençam ao seu utilizador, 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, estas imagens são publicadas em lscr.io e a tag latest acompanha a versão estável atual. Por isso, use uma tag de versão específica se quiser que as atualizações dependam de uma decisão, e não de uma surpresa.
Aceder com segurança às interfaces web
Como as portas estão em 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 acede ao Sonarr no servidor. Para acesso permanente, coloque a stack atrás do Traefik com certificados TLS para várias aplicações ou aceda ao servidor através de uma VPN WireGuard alojada por si. Nenhuma destas aplicações deve ficar na Internet pública protegida apenas pela sua própria página de login. Se optar pelo reverse proxy e preferir ter uma conta para as quatro interfaces, em vez de controlar quatro logins de aplicações separados, o Authentik fornece single sign-on autoalojado que o Traefik pode impor em todos os pedidos com autenticação forward.
O qBittorrent gera uma palavra-passe de administrador aleatória no primeiro arranque e apresenta-a no log do contentor. Leia-a e altere-a na interface web:
docker compose logs qbittorrent | grep -i passwordSe ignorar a alteração, será gerada uma nova palavra-passe aleatória a cada reinício e terá de voltar aos logs sempre.
Defina os caminhos dentro de cada aplicação
No qBittorrent, abra Options, depois Downloads, e defina o caminho predefinido para guardar ficheiros como /data/torrents. Mantenha a pasta de downloads incompletos dentro da mesma árvore, por exemplo, /data/torrents/incomplete. Um download que termine fora de /data não pode ser associado à biblioteca através de hardlink.
No Sonarr, abra Settings, depois Media Management, e adicione a pasta raiz /data/media/Shows. No Radarr, a pasta raiz é /data/media/Movies. Estes são caminhos dentro do contentor. O caminho do host /mnt/data/media/Shows é rejeitado porque esse diretório não existe do ponto de vista do contentor.
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 nome de host porque o Compose coloca os quatro contentores na mesma rede, com um serviço DNS (domain name system) interno. Não use localhost aqui: dentro do contentor do Sonarr, localhost é o Sonarr.
Deixe Remote Path Mappings vazio. Esta funcionalidade traduz um caminho comunicado pelo cliente de downloads para um caminho que a aplicação arr consiga ver. Com um único mount partilhado em /data, os dois contentores já usam os mesmos caminhos. Essa é a segunda razão pela qual esta disposição compensa o esforço.
Conecte o Prowlarr ao Sonarr e ao Radarr
O Prowlarr envia as definições dos indexadores para as outras aplicações. Assim, configura um indexador uma vez, em vez de duas. Precisa de uma chave de API (interface de programação de aplicações) de cada aplicação.
No Sonarr, abra Settings, depois General, e copie a chave de API. No Prowlarr, abra Settings, depois Apps, adicione uma aplicação Sonarr e preencha três campos. Prowlarr Server é http://prowlarr:9696. Sonarr Server é http://sonarr:8989. API Key é o valor que copiou. Prima Test. Um resultado verde significa que o Prowlarr conseguiu contactar o Sonarr através da rede Compose. Repita o processo com o Radarr em http://radarr:7878.
Um resultado vermelho com a mensagem de que a ligaçã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 a partir do interior do contentor:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Um código de estado HTTP confirma que o caminho de rede está funcional. Um erro de resolução de nome confirma que o nome do serviço está incorreto.
Comprove que os hardlinks estão a ser usados
Não confie na configuração até confirmar a contagem de links. Depois de um item ser importado, compare o ficheiro transferido com o ficheiro 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 ficheiro ao qual foi aplicado um hardlink apresenta o mesmo inode nas duas localizações 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 ficheiro; o log de importação indicará que o hardlink falhou.
Verifique também o disco. df -h /mnt/data quase não deve variar durante uma importação, porque um hardlink adiciona um nome, mas não dados.
O que realmente falha
Erros de permissão durante a importação significam que o ID de utilizador do contentor não pode escrever na pasta da biblioteca. A mensagem é Access to the path ... is denied. Use ls -ln /mnt/data/media para confirmar que o ID do proprietário corresponde ao seu PUID e lembre-se de que as pastas precisam do bit de execução para que o contentor possa entrar nelas.
Ficheiros que aparecem como pertencentes a root indicam que o contentor arrancou antes de a pasta do anfitrião existir, pelo que o Docker a criou como root. Pare a stack, chown a pasta e inicie-a novamente.
Eliminar um torrent do qBittorrent e descobrir que o ficheiro da biblioteca desapareceu significa que a importação foi uma cópia que foi removida mais tarde ou que eliminou os dados em vez da entrada do torrent. Com uma hardlink real, remover um nome mantém o outro intacto, porque os dados só são libertados quando a contagem de links chega a zero.
Um disco que fica cheio mais depressa do que o volume de multimédia adicionado é o problema das cópias na 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 numa pequena base de dados SQLite e mudam o nome dos ficheiros. Um servidor com 2 GB de RAM executa os quatro contentores sem problemas. A carga vem de outros componentes. Um cliente de downloads satura a entrada e saída do disco com torrents grandes, e um servidor multimédia que transcodifica vídeo no mesmo equipamento consumirá o CPU. Mantenha os ficheiros multimédia num volume com débito real e defina um limite de largura de banda no cliente de downloads se o servidor estiver a executar outras tarefas importantes. Reserve recursos para essas tarefas separadamente, em vez de presumir que existe margem suficiente: um espaço de trabalho AFFiNE autoalojado corresponde a mais quatro contentores com uma base de dados por trás e, num equipamento com 2 GB, pretende usar a maior parte dessa memória. Nem todos os serviços adicionais custam tanto: algo de finalidade única, como um monitor de treinos openGym autoalojado, partilha o equipamento sem problemas, desde que lhe atribua o seu próprio TLS e saiba onde está o ficheiro da base de dados antes de lhe confiar um ano de histórico de treinos. Qualquer serviço que inclua uma aplicação Web, uma base de dados PostgreSQL e uma fila de tarefas em segundo plano aproxima-se mais do extremo ocupado pelo AFFiNE. Por isso, decida se um serviço de suporte Chatwoot autoalojado pertence a este servidor ou deve ter o seu próprio servidor antes de descobrir o limite a meio de uma importação. As cargas de trabalho com picos exigem ainda mais cuidado, porque é o pico, e não a média, que entra em conflito com uma importação: se estiver a considerar um OneCLI autoalojado que fornece a cada pessoa um agente num sandbox próprio, compare os requisitos publicados com os recursos realmente livres enquanto o qBittorrent está a funcionar no limite, e não com o que free -h mostra num equipamento inativo.
FAQ
Por que o Sonarr copia os ficheiros em vez de criar hard links?
Porque, do ponto de vista do contentor, a origem e o destino estão em sistemas de ficheiros diferentes. Duas montagens bind separadas, como /downloads e /tv, são dois sistemas de ficheiros, mesmo quando ambas vêm do mesmo disco do host. Monte um único diretório-pai como /data em todos os contentores e coloque os downloads e a biblioteca dentro dele. Assim, a criação do link torna-se possível. Confirme o resultado com stat -c '%i %h %n' nos dois ficheiros: o mesmo inode e uma contagem de links de 2.
Que PUID e PGID devo usar?
Use os IDs numéricos da conta do host que é proprietária da árvore de media. Pode obtê-los com id -u e id -g. Numa VPS Ubuntu nova, normalmente são 1000 para ambos. Todos os contentores da stack devem usar o mesmo par. Caso contrário, uma aplicação cria ficheiros que outra não consegue modificar. Depois de alterar os valores, recrie os contentores com docker compose up -d --force-recreate e corrija os ficheiros existentes com chown -R.
Preciso de expor estas interfaces web à Internet?
Não. Não deve fazê-lo. Associe cada porta publicada a 127.0.0.1 no ficheiro Compose. Depois, aceda às interfaces através de um túnel SSH, de uma VPN ou de um reverse proxy que termine o TLS (segurança da camada de transporte) e adicione a sua própria autenticação. Publicá-las diretamente é pior do que parece, porque o Docker insere as suas próprias regras de firewall. Uma regra deny do ufw não impedirá esse tráfego.
Onde encontro a palavra-passe do qBittorrent?
A imagem LinuxServer.io apresenta uma palavra-passe temporária para o utilizador admin no log de arranque. Execute docker compose logs qbittorrent | grep -i password para a consultar. Depois, defina uma palavra-passe permanente em Options e Web UI. É gerada uma nova palavra-passe temporária a cada reinício até definir uma palavra-passe própria.
O Jellyfin pode usar as mesmas pastas?
Sim. Esse é o objetivo desta estrutura. Monte /mnt/data/media no servidor de media como /media. As bibliotecas ficam em /media/Movies e /media/Shows, enquanto o Sonarr e o Radarr escrevem nesses mesmos diretórios através de /data/media. Atribua ao servidor de media o mesmo PUID e PGID para que possa ler o conteúdo criado pela stack arr.