<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Nezzontli - Homelab</title>
    <subtitle>Opinionated, modern, pretty, and clean Zola theme.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://nezzontli.xyz/tags/homelab/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://nezzontli.xyz"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-16T00:00:00+00:00</updated>
    <id>https://nezzontli.xyz/tags/homelab/atom.xml</id>
    <entry xml:lang="en">
        <title>Servidor personal: Debian + Docker, no FreeBSD</title>
        <published>2026-07-16T00:00:00+00:00</published>
        <updated>2026-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            B.E. Alejandro
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://nezzontli.xyz/blog/sevidor/"/>
        <id>https://nezzontli.xyz/blog/sevidor/</id>
        
        <content type="html" xml:base="https://nezzontli.xyz/blog/sevidor/">&lt;h2 id=&quot;contexto&quot;&gt;Contexto&lt;&#x2F;h2&gt;
&lt;p&gt;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 &lt;code&gt;pcale&lt;&#x2F;code&gt;) es &lt;strong&gt;Debian 13 (Trixie)&lt;&#x2F;strong&gt; con todo containerizado en &lt;strong&gt;Docker&lt;&#x2F;strong&gt;, un stack por servicio en &lt;code&gt;docker-compose.yml&lt;&#x2F;code&gt; separados, sin hypervisor ni jails.&lt;&#x2F;p&gt;
&lt;p&gt;La razón del cambio fue práctica: Docker Compose me da aislamiento por servicio sin la curva de aprendizaje adicional de jails&#x2F;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.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;infraestructura-base&quot;&gt;Infraestructura base&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SO:&lt;&#x2F;strong&gt; Debian 13 (Trixie), kernel 6.12.x&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Actualizaciones:&lt;&#x2F;strong&gt; &lt;code&gt;unattended-upgrades&lt;&#x2F;code&gt; para parches de seguridad automáticos&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Todo lo demás vive en contenedores&lt;&#x2F;strong&gt; — cada servicio tiene su propio &lt;code&gt;docker-compose.yml&lt;&#x2F;code&gt; bajo &lt;code&gt;&#x2F;home&#x2F;ale&#x2F;&amp;lt;servicio&amp;gt;&#x2F;&lt;&#x2F;code&gt;, así config, volúmenes y upgrades quedan aislados por app&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;acceso-y-red&quot;&gt;Acceso y red&lt;&#x2F;h2&gt;
&lt;p&gt;No hay puertos abiertos al público en el router. El acceso normal es vía &lt;strong&gt;Tailscale&lt;&#x2F;strong&gt;: cada servicio interno se publica con un hostname propio y HTTPS real usando Tailscale Services:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;tailscale serve --service=svc:&amp;lt;nombre&amp;gt; --bg --https=443 http:&amp;#x2F;&amp;#x2F;localhost:&amp;lt;puerto&amp;gt;
tailscale serve --service=svc:&amp;lt;nombre&amp;gt; --bg --http=80 http:&amp;#x2F;&amp;#x2F;localhost:&amp;lt;puerto&amp;gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Esto da URLs como &lt;code&gt;https:&#x2F;&#x2F;nextcloud.tail32b955.ts.net&lt;&#x2F;code&gt;, con certificado válido, sin recordar puertos. Un par de servicios sí salen a internet público, pero vía &lt;strong&gt;Cloudflare Tunnel&lt;&#x2F;strong&gt; (&lt;code&gt;cloudflared&lt;&#x2F;code&gt;, conexión saliente únicamente, nada expuesto en el router) — el caso principal es PeerTube en &lt;code&gt;tube.richard69.lat&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Perímetro:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;SSH solo con clave, llave de seguridad física (YubiKey), &lt;code&gt;X11Forwarding&lt;&#x2F;code&gt; desactivado&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ufw&lt;&#x2F;code&gt; en default-deny, la mayoría de servicios restringidos a LAN (&lt;code&gt;192.168.68.0&#x2F;24&lt;&#x2F;code&gt;) + Tailscale (&lt;code&gt;100.0.0.0&#x2F;8&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;fail2ban&lt;&#x2F;code&gt; en el jail de SSH (maxretry 3, ban 24h)&lt;&#x2F;li&gt;
&lt;li&gt;Vaultwarden llegó a tener un hostname público en el tunnel; se sacó por completo y ahora es Tailscale-only&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;servicios-corriendo&quot;&gt;Servicios corriendo&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Nube y datos personales&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nextcloud&lt;&#x2F;strong&gt; — almacenamiento personal, también sirve como fuente de la biblioteca de Calibre-web (apunta directo a &lt;code&gt;Documents&#x2F;Libros&lt;&#x2F;code&gt; sin duplicar archivos)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Immich&lt;&#x2F;strong&gt; — fotos&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Vaultwarden&lt;&#x2F;strong&gt; — gestor de contraseñas (servidor compatible con Bitwarden), solo por Tailscale&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Calibre-web&lt;&#x2F;strong&gt; — lector&#x2F;biblioteca de ebooks&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Media&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Jellyfin&lt;&#x2F;strong&gt; — servidor de medios (películas, series, música)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;qBittorrent + Radarr + Sonarr + Prowlarr&lt;&#x2F;strong&gt; — el stack *arr completo: indexers → búsqueda → descarga → importación, aterrizando directo en la biblioteca de Jellyfin, que se refresca sola al importar&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Navidrome&lt;&#x2F;strong&gt; — streaming de música&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;spotiflac&lt;&#x2F;strong&gt;, &lt;strong&gt;AudioMuse AI&lt;&#x2F;strong&gt; — utilidades de música&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PeerTube&lt;&#x2F;strong&gt; — instancia propia y federada de video (&lt;code&gt;tube.richard69.lat&lt;&#x2F;code&gt;), con una imagen parcheada a mano (ver abajo)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Todo el lado de descargas (qBittorrent, Radarr, Sonarr, Prowlarr, spotiflac) corre con &lt;code&gt;network_mode: container:mullvad-exit-vpn&lt;&#x2F;code&gt;, compartiendo el namespace de red de un único contenedor &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;qdm12&#x2F;gluetun&quot;&gt;gluetun&lt;&#x2F;a&gt;, así ese tráfico —y solo ese— sale por un exit de Mullvad vía WireGuard.&lt;&#x2F;p&gt;
&lt;p&gt;Un extra que armé sobre esto: un script que convierte un canal de PeerTube seguido en una biblioteca de Jellyfin de archivos &lt;code&gt;.strm&lt;&#x2F;code&gt; (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.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Utilidades&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Glance&lt;&#x2F;strong&gt; — dashboard único: bookmarks, RSS, clima, chequeos de salud de cada servicio, buscador&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;4get&lt;&#x2F;strong&gt; — metabuscador privado, también el buscador embebido en Glance&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ntfy&lt;&#x2F;strong&gt; — notificaciones push self-hosted (alertas del servidor, watchdog de la VPN, etc.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Uptime Kuma &#x2F; Beszel&lt;&#x2F;strong&gt; — monitoreo, con umbrales de alerta mandando a ntfy&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Prosody&lt;&#x2F;strong&gt; — mensajería XMPP&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Forgejo&lt;&#x2F;strong&gt; — Git self-hosted (donde vive este mismo blog)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;problemas-encontrados-y-soluciones&quot;&gt;Problemas encontrados (y soluciones)&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;network_mode: container:X&lt;&#x2F;code&gt; no se re-resuelve solo.&lt;&#x2F;strong&gt; Docker fija esa referencia a un &lt;em&gt;container ID&lt;&#x2F;em&gt; en el momento de crear el contenedor. Si gluetun se recrea (&lt;code&gt;docker compose up --force-recreate&lt;&#x2F;code&gt;, 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 &lt;code&gt;Up (healthy)&lt;&#x2F;code&gt; y su proceso interno escucha bien desde adentro, pero nada llega desde afuera. Fix: force-recreate de cada contenedor afectado, no alcanza con &lt;code&gt;docker restart&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;*&lt;em&gt;La ruta de descarga de un &lt;em&gt;arr no puede ser su propia root folder.&lt;&#x2F;em&gt;&lt;&#x2F;em&gt; Si la categoría de qBittorrent apunta al mismo path que el root folder de Radarr&#x2F;Sonarr, la app no puede distinguir “todavía bajando” de “ya importado” y tira &lt;code&gt;DownloadClientRootFolderCheck&lt;&#x2F;code&gt;. Solución: carpetas de staging separadas (&lt;code&gt;&#x2F;media&#x2F;downloads&#x2F;movies&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;media&#x2F;downloads&#x2F;shows&lt;&#x2F;code&gt;), Radarr&#x2F;Sonarr importan por hardlink desde ahí al root folder real.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;ntfy en iOS necesita un &lt;code&gt;base-url&lt;&#x2F;code&gt; alcanzable.&lt;&#x2F;strong&gt; iOS no puede recibir contenido completo en el push directamente (limitación de APNs) — el teléfono hace polling al &lt;code&gt;base-url&lt;&#x2F;code&gt; del propio servidor después de recibir un push genérico de “new message”. Si ese &lt;code&gt;base-url&lt;&#x2F;code&gt; apunta a un dominio muerto, la notificación queda vacía sin ningún error visible en ningún lado.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;PeerTube solo acepta thumbnails remotos en JPEG.&lt;&#x2F;strong&gt; 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 &lt;code&gt;docker compose pull&lt;&#x2F;code&gt;) en cada actualización de PeerTube o el parche desaparece sin avisar.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;que-falta-por-hacer&quot;&gt;Qué falta por hacer&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Access delante de Uptime Kuma si algún día se tunelea al público&lt;&#x2F;li&gt;
&lt;li&gt;Reglas de &lt;code&gt;ufw&lt;&#x2F;code&gt; para un par de servicios más nuevos (4get, AudioMuse) que hoy solo responden desde localhost&lt;&#x2F;li&gt;
&lt;li&gt;Un manual real de recuperación ante desastres — por ahora ese conocimiento vive en mis notas, no en un documento&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;idea-central&quot;&gt;Idea central&lt;&#x2F;h2&gt;
&lt;p&gt;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).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;referencias&quot;&gt;Referencias&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;tailscale.com&#x2F;kb&#x2F;&quot;&gt;Documentación de Tailscale&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;qdm12&#x2F;gluetun&quot;&gt;gluetun&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;glanceapp&#x2F;glance&quot;&gt;Glance&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;wiki.servarr.com&#x2F;&quot;&gt;Servarr Wiki&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;docs.joinpeertube.org&#x2F;&quot;&gt;Documentación de PeerTube&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
</feed>
