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 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 elementos desse arquivo 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, portanto 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 frequentemente e é explicado em por que razão o Docker publica portas diretamente através do ufw.
A porta 6881 é publicada em todas as interfaces de propósito. É a porta de escuta do torrent e tem de estar acessível para ligações de entrada de pares. O mesmo comportamento do Docker aplica-se aqui, por isso não precisa de uma regra do ufw, e adicionar uma não altera nada. A documentação do Docker, verificada em outubro de 2026, afirma que o tráfego destinado a uma porta publicada é encaminhado antes de chegar às cadeias usadas pelo ufw, portanto sudo ufw allow 6881 não tem efeito sobre esta porta. A porta fica acessível a partir da Internet assim que o contentor arranca, e um deny do ufw também não a fecharia. Se precisar de limitar quem se pode ligar a ela, a documentação do Docker indica regras na cadeia DOCKER-USER, em vez do ufw. Mantenha o ufw para os serviços que não são executados no Docker, como o SSH, e consulte os princípios básicos da firewall ufw para um VPS se esta ferramenta for nova 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 outubro de 2026, estas imagens são publicadas em lscr.io e a etiqueta latest acompanha a versão estável atual. Por isso, fixe uma etiqueta de versão se quiser que as atualizações sejam uma decisão, e não uma surpresa.
Aceder às interfaces web com segurança
Como as portas estão associadas ao loopback, nada está exposto. 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. No entanto, uma VPN não permite aceder a estas portas por si só. A documentação do Docker, consultada em outubro de 2026, indica que, quando uma porta é publicada num endereço localhost, apenas o host Docker lhe pode aceder. Para usar a VPN, mantenha o túnel SSH ativo através dela ou altere as associações no ficheiro Compose de 127.0.0.1 para o endereço do próprio servidor na interface VPN. Nenhuma destas aplicações deve ficar na Internet pública com apenas a sua própria página de início de sessão à frente. Se escolher a opção do reverse proxy e preferir uma única conta para as quatro interfaces, em vez de controlar quatro inícios de sessão de aplicações separados, o Authentik fornece single sign-on self-hosted que o Traefik pode aplicar a todos os pedidos com forward auth.
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 não a alterar, será gerada uma nova palavra-passe aleatória em cada reinício, e terá de voltar aos logs sempre que isso acontecer.
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. Dois bind mounts separados, como /downloads e /tv, são sistemas de ficheiros diferentes, mesmo quando ambos 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 igual a 2.
Que PUID e PGID devo usar?
Use o ID numérico da conta do host que é proprietária da árvore de multimédia. Pode obtê-lo com id -u e id -g. Numa VPS Ubuntu nova, normalmente é 1000 para ambos. Todos os contentores da stack devem usar o mesmo par. Caso contrário, uma aplicação pode criar 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 ou de um reverse proxy no servidor, que termina o TLS (transport layer security) e adiciona a sua própria autenticação. Uma VPN, por si só, não consegue aceder a uma porta associada a 127.0.0.1, porque o Docker só a torna acessível a partir do próprio host. Se quiser acesso direto através de uma VPN, publique as portas no endereço VPN do servidor. Publicá-las diretamente na interface pública é pior do que parece, porque o Docker insere as suas próprias regras de firewall e uma regra deny do ufw não bloqueará esse tráfego.
Onde encontro a palavra-passe do qBittorrent?
A imagem LinuxServer.io escreve 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 multimédia 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 multimédia o mesmo PUID e PGID para que consiga ler o conteúdo escrito pela stack arr.