Docker Compose para Prowlarr, Sonarr, Radarr y qBittorrent
Ejecuta Prowlarr, Sonarr, Radarr y qBittorrent en un VPS con un solo Compose, PUID, PGID y rutas compartidas para conservar los hardlinks.
Qué va a crear
Una pila arr de Docker Compose consta de cuatro contenedores que gestionan una biblioteca multimedia: Prowlarr para la configuración de los indexadores, Sonarr para las series, Radarr para las películas y qBittorrent como cliente de descarga. Se comunican entre sí a través de la red de Compose mediante el nombre del servicio y comparten un único árbol de directorios en el host. La instalación es breve. El diseño de los volúmenes determina si la pila funcionará durante años o si le dará problemas cada semana, por lo que la mayor parte de esta guía trata sobre ese aspecto.
La pila no busca contenido por usted. Prowlarr contiene los indexadores que añada, y usted decide qué indexadores usar y asume la responsabilidad legal correspondiente. Esta guía cubre la infraestructura: usuarios, rutas, permisos, redes de contenedores y comprobaciones que demuestran que funciona.
Si nunca ha escrito un archivo de Compose, lea los conceptos básicos de Docker Compose para un VPS primero. Esta publicación presupone que docker compose version ya muestra alguna salida en su servidor.
Por qué fallan los hardlinks y por qué todo depende de esto
Cuando Sonarr termina una descarga, importa el archivo en la biblioteca. Si la carpeta de descargas y la carpeta de la biblioteca están en el mismo sistema de archivos, la importación usa un hardlink: un segundo nombre que apunta a los mismos datos del disco. No ocupa espacio adicional ni requiere tiempo apreciable. El torrent sigue compartiendo desde el nombre anterior mientras el servidor multimedia lee el nuevo.
Si las dos carpetas están en sistemas de archivos diferentes, el kernel no puede crear ese enlace. Sonarr recurre a una copia. Una temporada de 40 GB pasa a ocupar 80 GB de disco y requiere varios minutos de entrada y salida, y el registro de importación indica que el hardlink falló y que el archivo se copió. En un VPS con una cuota de disco fija, así es como se agota el espacio en una semana.
Aquí está el problema. Dentro de un contenedor, un bind mount es un límite entre sistemas de archivos. Monte /mnt/data/torrents como /downloads y /mnt/data/media como /tv; aunque ambos estén en un solo disco del host, Sonarr ve dos montajes independientes y se niega a crear enlaces entre ellos. La documentación oficial de la imagen de LinuxServer.io lo indica directamente: usar las rutas independientes /downloads y /tv impide crear hardlinks.
La solución es usar un solo montaje. Cada contenedor que acceda a los archivos multimedia recibe el mismo volumen único, /mnt/data:/data, y todas las rutas que utilice son carpetas dentro de ese volumen. Un punto de montaje, un sistema de archivos y hardlinks funcionales.
Cree el usuario, el grupo y las carpetas
Los contenedores escriben archivos con un ID de usuario numérico, definido por PUID y PGID. Use su propia cuenta para poder leer y editar esos archivos mediante SSH sin sudo.
id -u
id -gAmbos suelen mostrar 1000 en una VPS de Ubuntu recién creada. Ahora cree el árbol de directorios. Colóquelo en el disco que contiene los archivos multimedia y mantenga todo el árbol en ese mismo 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/dataCompruebe que realmente sea un solo sistema de archivos antes de continuar:
df --output=source,target /mnt/data/torrents /mnt/data/mediaAmbas líneas deben mostrar el mismo dispositivo de origen. Si muestran dispositivos diferentes, los hard links nunca funcionarán, independientemente de lo que configure en el contenedor.
Las carpetas de la biblioteca se llaman Movies y Shows deliberadamente. Si ya ejecuta Jellyfin como servidor multimedia, monte /mnt/data/media en Jellyfin como /media. Sus bibliotecas quedarán en /media/Movies y /media/Shows, exactamente donde las coloca esa guía.
El archivo de entorno
Mantenga en .env, junto al archivo de Compose, los valores que cambian en cada servidor.
mkdir -p ~/arr && cd ~/arrEscriba ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataEstablezca TZ en su propia zona, como Europe/Berlin. Las aplicaciones de arr programan tareas y registran las líneas de log en esa zona, por lo que un valor incorrecto hará que todos los logs resulten confusos más adelante.
El archivo de Compose
Escriba ~/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-stoppedCuatro elementos de ese archivo realizan el trabajo principal.
${DATA_ROOT}:/data es idéntico en los tres contenedores que acceden a archivos multimedia. Prowlarr no lo necesita porque nunca abre un archivo multimedia.
Todos los puertos web están vinculados a 127.0.0.1, de modo que Docker los publica sólo en la dirección de loopback. Un 8989:8989 simple los publicaría en todas las interfaces, y las reglas de firewall propias de Docker transportarían ese tráfico directamente, sin pasar por una regla deny de ufw. Este comportamiento sorprende constantemente a los usuarios y se explica en por qué Docker publica los puertos directamente a través de ufw.
El puerto 6881 se publica en todas las interfaces de forma intencionada. Es el puerto de escucha de torrent y debe ser accesible para las conexiones entrantes de los pares. Permítalo con sudo ufw allow 6881 y consulte los fundamentos del firewall ufw para un VPS si ese comando es nuevo para usted.
Los directorios de configuración son independientes para cada aplicación y sólo se comparte el volumen multimedia. Créelos antes del primer arranque para que pertenezcan a su usuario y no a root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psLos cuatro servicios deben usar running. En julio de 2026, estas imágenes se publican en lscr.io y la etiqueta latest sigue la versión estable actual. Fije una etiqueta de versión si quiere que las actualizaciones sean una decisión y no una sorpresa.
Acceda a las interfaces web de forma segura
Como los puertos están enlazados a loopback, todavía no hay nada expuesto. Reenvíelos mediante SSH desde su propio equipo:
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-serverAhora http://127.0.0.1:8989 en el navegador permite acceder a Sonarr en el servidor. Para obtener acceso permanente, coloque la pila detrás de Traefik con certificados TLS para varias aplicaciones o acceda al servidor mediante una VPN WireGuard autohospedada. Ninguna de estas aplicaciones debe quedar en Internet pública con sólo su propia página de inicio de sesión delante. Si opta por el proxy inverso y prefiere tener una sola cuenta para las cuatro interfaces en lugar de mantener cuatro inicios de sesión de aplicaciones independientes, Authentik proporciona inicio de sesión único autohospedado que Traefik puede aplicar a cada petición mediante autenticación delegada.
qBittorrent genera una contraseña de administrador aleatoria durante el primer arranque y la muestra en el registro del contenedor. Léala y cámbiela en la interfaz web:
docker compose logs qbittorrent | grep -i passwordSi omite el cambio, se genera una contraseña aleatoria nueva en cada reinicio y tendrá que volver a consultar los registros cada vez.
Configurar las rutas dentro de cada aplicación
En qBittorrent, abra Options, después Downloads, y establezca /data/torrents como ruta de guardado predeterminada. Mantenga la carpeta de descargas incompletas dentro del mismo árbol, por ejemplo, /data/torrents/incomplete. Una descarga que termine en cualquier ubicación fuera de /data no se puede enlazar mediante hard links a la biblioteca.
En Sonarr, abra Settings, después Media Management, y añada la carpeta raíz /data/media/Shows. En Radarr, la carpeta raíz es /data/media/Movies. Estas son rutas dentro del contenedor. La ruta del host /mnt/data/media/Shows se rechaza porque ese directorio no existe desde el punto de vista del contenedor.
En Sonarr y Radarr, abra Settings, después Download Clients, y añada qBittorrent. El host es qbittorrent y el puerto es 8080. El nombre del servicio funciona como nombre de host porque Compose coloca los cuatro contenedores en una red con un servicio DNS (domain name system) interno. No use localhost aquí: dentro del contenedor de Sonarr, localhost es Sonarr.
Deje Remote Path Mappings vacío. Esta función traduce una ruta que comunica el cliente de descargas a una ruta que la aplicación arr puede ver. Con un único montaje compartido de /data, ambos contenedores ya coinciden en todas las rutas. Esta es la segunda razón por la que este diseño merece el esfuerzo.
Conecte Prowlarr con Sonarr y Radarr
Prowlarr envía las definiciones de los indexadores a las demás aplicaciones. Así, configura cada indexador una sola vez en lugar de dos. Necesita una clave de API (interfaz de programación de aplicaciones) de cada una.
En Sonarr, abra Settings y después General. Copie la clave de API. En Prowlarr, abra Settings y después Apps. Añada una aplicación Sonarr y complete los tres campos. Prowlarr Server es http://prowlarr:9696. Sonarr Server es http://sonarr:8989. API Key es el valor que copió. Pulse Test. Un resultado en verde significa que Prowlarr llegó a Sonarr a través de la red de Compose. Repita el proceso con Radarr en http://radarr:7878.
Un resultado en rojo que indique que se rechazó la conexión casi siempre significa que el nombre del servicio es incorrecto o que falta el prefijo http://. Confirme que el nombre se resuelve desde dentro del contenedor:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Un código de estado HTTP demuestra que la ruta de red funciona. Un error de resolución de nombres demuestra que el nombre del servicio es incorrecto.
Compruebe que los enlaces duros se están creando realmente
No dé por válida la configuración hasta comprobar el número de enlaces. Después de importar un elemento, compare el archivo descargado con el archivo de la biblioteca:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvEl primer número es el inode y el segundo es el número de enlaces. Un archivo enlazado mediante un enlace duro muestra el mismo inode en ambas ubicaciones y un número de enlaces de 2. Dos inodes diferentes, cada uno con un número de enlaces de 1, indican que Sonarr copió el archivo. El registro de importación indicará que el enlace duro falló.
Compruebe también el uso del disco. df -h /mnt/data apenas debería cambiar durante una importación, porque un enlace duro añade un nombre, pero no datos.
Qué se rompe realmente
Los errores de permisos durante la importación indican que el id de usuario del contenedor no puede escribir en la carpeta de la biblioteca. El mensaje es Access to the path ... is denied. Compruébelo con ls -ln /mnt/data/media para confirmar que el id del propietario coincide con su PUID, y recuerde que los directorios necesitan el bit de ejecución para que el contenedor pueda acceder a ellos.
Los archivos que aparecen como propiedad de root indican que el contenedor se inició antes de que existiera el directorio del host, por lo que Docker lo creó como root. Detenga el stack, chown el directorio y vuelva a iniciarlo.
Si elimina un torrent de qBittorrent y descubre que también ha desaparecido el archivo de la biblioteca, la importación era una copia que se eliminó después, o eliminó los datos en lugar de la entrada del torrent. Con un hardlink real, eliminar un nombre no afecta al otro, porque los datos sólo se liberan cuando el contador de enlaces llega a cero.
Que el disco se llene más rápido que el volumen de contenido multimedia añadido indica el problema de las copias en su forma más costosa. Ejecute la comprobación stat anterior antes de comprar más almacenamiento.
Lo que esta pila necesita de un VPS
Las tres aplicaciones arr son ligeras. Consultan los indexadores, escriben en una base de datos SQLite pequeña y cambian el nombre de los archivos. Un servidor con 2 GB de RAM ejecuta los cuatro contenedores sin problemas. La carga procede de otros servicios. Un cliente de descargas satura la entrada y salida del disco con torrents grandes, y un servidor multimedia que transcodifica vídeo en el mismo equipo consumirá la CPU. Mantenga los archivos multimedia en un volumen con un rendimiento real y establezca un límite de ancho de banda en el cliente de descargas si el servidor realiza alguna otra tarea importante. Asigne recursos a esas otras tareas por separado en lugar de suponer que queda margen: un espacio de trabajo AFFiNE autohospedado son otros cuatro contenedores con una base de datos detrás y, en un equipo con 2 GB, quiere reservar para sí la mayor parte de esa memoria. No todos los servicios adicionales consumen tanto: algo de propósito único, como un registro de entrenamientos openGym autohospedado, comparte el equipo sin problemas, siempre que le proporcione su propio TLS y sepa dónde está el archivo de la base de datos antes de confiarle un año de historial de entrenamiento. Cualquier servicio que incluya una aplicación web, una base de datos PostgreSQL y una cola de trabajos en segundo plano se sitúa más cerca del extremo de AFFiNE de ese rango. Por tanto, decida si un sistema de soporte Chatwoot autohospedado debe ejecutarse en este servidor o en uno propio antes de descubrir el límite a mitad de una importación. Las cargas de trabajo con picos requieren aún más precaución, porque lo que entra en conflicto con una importación es su pico, no su promedio: si está considerando un OneCLI autohospedado que proporciona a cada persona un agente en su propio entorno aislado, compare sus cifras de dimensionamiento publicadas con la memoria realmente libre mientras qBittorrent se ejecuta al máximo, no con lo que free -h muestra en un equipo inactivo.
FAQ
¿Por qué Sonarr copia los archivos en lugar de crear enlaces físicos?
Porque, desde el punto de vista del contenedor, el origen y el destino están en sistemas de archivos diferentes. Dos montajes bind independientes, como /downloads y /tv, son dos sistemas de archivos aunque ambos procedan de un solo disco del host. Monte un único directorio principal como /data en todos los contenedores y coloque dentro las descargas y la biblioteca. Así será posible crear el enlace. Confirme el resultado con stat -c '%i %h %n' en ambos archivos: deben tener el mismo inode y un recuento de enlaces de 2.
¿Qué PUID y PGID debo usar?
Use el id numérico de la cuenta del host propietaria del árbol multimedia. Puede obtenerlo con id -u y id -g. En un Ubuntu VPS recién instalado, normalmente es 1000 para ambos. Todos los contenedores de la pila deben usar el mismo par. De lo contrario, una aplicación puede crear archivos que otra no pueda modificar. Después de cambiar los valores, vuelva a crear los contenedores con docker compose up -d --force-recreate y corrija los archivos existentes con chown -R.
¿Debo exponer estas interfaces web a Internet?
No. No debe hacerlo. Enlace cada puerto publicado a 127.0.0.1 en el archivo Compose. Después, acceda a las interfaces mediante un túnel SSH, una VPN o un reverse proxy que termine TLS (seguridad de la capa de transporte) y añada su propia autenticación. Publicarlas directamente es más peligroso de lo que parece, porque Docker inserta sus propias reglas de firewall y una regla deny de ufw no detendrá ese tráfico.
¿Dónde encuentro la contraseña de qBittorrent?
La imagen de LinuxServer.io muestra una contraseña temporal para el usuario admin en su registro de inicio. Ejecute docker compose logs qbittorrent | grep -i password para leerla. Después, establezca una contraseña permanente en Options y Web UI. Se genera una nueva contraseña temporal en cada reinicio hasta que establezca una propia.
¿Puede Jellyfin usar las mismas carpetas?
Sí. Ese es el objetivo de esta disposición. Monte /mnt/data/media en el servidor multimedia como /media. Sus bibliotecas estarán en /media/Movies y /media/Shows, mientras Sonarr y Radarr escribirán en esos mismos directorios mediante /data/media. Asigne al servidor multimedia el mismo PUID y PGID para que pueda leer lo que escriba la pila de aplicaciones arr.