Servidor personal: Debian + Docker, no FreeBSD

By B.E. Alejandro


Contexto

Este post reemplaza una entrada anterior que describía el plan original: FreeBSD, jails, bastilleBSD. Ese plan no se ejecutó así. Lo que terminó corriendo en la máquina reusada (hostname pcale) es Debian 13 (Trixie) con todo containerizado en Docker, un stack por servicio en docker-compose.yml separados, sin hypervisor ni jails.

La razón del cambio fue práctica: Docker Compose me da aislamiento por servicio sin la curva de aprendizaje adicional de jails/bhyve, y el ecosistema de imágenes ya resueltas (linuxserver, imágenes oficiales) redujo tiempo de instalación a casi cero comparado con compilar todo desde ports.

Infraestructura base

Acceso y red

No hay puertos abiertos al público en el router. El acceso normal es vía Tailscale: cada servicio interno se publica con un hostname propio y HTTPS real usando Tailscale Services:

tailscale serve --service=svc:<nombre> --bg --https=443 http://localhost:<puerto>
tailscale serve --service=svc:<nombre> --bg --http=80 http://localhost:<puerto>

Esto da URLs como https://nextcloud.tail32b955.ts.net, con certificado válido, sin recordar puertos. Un par de servicios sí salen a internet público, pero vía Cloudflare Tunnel (cloudflared, conexión saliente únicamente, nada expuesto en el router) — el caso principal es PeerTube en tube.richard69.lat.

Perímetro:

Servicios corriendo

Nube y datos personales

Media

Todo el lado de descargas (qBittorrent, Radarr, Sonarr, Prowlarr, spotiflac) corre con network_mode: container:mullvad-exit-vpn, compartiendo el namespace de red de un único contenedor gluetun, así ese tráfico —y solo ese— sale por un exit de Mullvad vía WireGuard.

Un extra que armé sobre esto: un script que convierte un canal de PeerTube seguido en una biblioteca de Jellyfin de archivos .strm (punteros a la URL del HLS del video original). Jellyfin los reproduce en directo desde la instancia origen sin que el archivo toque el disco del servidor.

Utilidades

Problemas encontrados (y soluciones)

network_mode: container:X no se re-resuelve solo. Docker fija esa referencia a un container ID en el momento de crear el contenedor. Si gluetun se recrea (docker compose up --force-recreate, o cualquier reinicio del stack), todo lo que comparte su netns (qBittorrent, Radarr, Sonarr, Prowlarr, spotiflac) queda apuntando a un namespace huérfano. El síntoma es engañoso: el contenedor se ve Up (healthy) y su proceso interno escucha bien desde adentro, pero nada llega desde afuera. Fix: force-recreate de cada contenedor afectado, no alcanza con docker restart.

*La ruta de descarga de un arr no puede ser su propia root folder. Si la categoría de qBittorrent apunta al mismo path que el root folder de Radarr/Sonarr, la app no puede distinguir “todavía bajando” de “ya importado” y tira DownloadClientRootFolderCheck. Solución: carpetas de staging separadas (/media/downloads/movies, /media/downloads/shows), Radarr/Sonarr importan por hardlink desde ahí al root folder real.

ntfy en iOS necesita un base-url alcanzable. iOS no puede recibir contenido completo en el push directamente (limitación de APNs) — el teléfono hace polling al base-url del propio servidor después de recibir un push genérico de “new message”. Si ese base-url apunta a un dominio muerto, la notificación queda vacía sin ningún error visible en ningún lado.

PeerTube solo acepta thumbnails remotos en JPEG. El validador de videos federados de PeerTube rechaza en silencio cualquier video cuyo thumbnail remoto sea PNG o WebP. Lo resolví con un parche chico horneado en una imagen Docker custom — hay que reconstruirla (no solo docker compose pull) en cada actualización de PeerTube o el parche desaparece sin avisar.

Qué falta por hacer

Idea central

El objetivo cambió de “aprender FreeBSD a fondo” a “tener servicios propios corriendo de forma confiable, con el mínimo de superficie expuesta al público”. Docker Compose y Tailscale resultaron el camino más corto ahí. FreeBSD y jails siguen siendo un proyecto que me interesa, pero para otro momento — ver el post de OpenBSD para la versión de “control total” aplicada a algo más chico (el hospedaje de este mismo sitio).

Referencias