/ Caso · Infraestructura self-hosted

hacheserver

Un mini PC de segunda mano en mi salón con ocho servicios en Docker, accesible desde internet por un túnel saliente de Cloudflare y desde mis dispositivos por Tailscale, sin un solo puerto abierto en el router. Aloja devilfruittcg.com. Y es donde aprendí esta capa, que básicamente consistió en romperla y apuntar por qué.

Máquina
HP EliteDesk 800 G4 Mini, de segunda mano
CPU y RAM
Intel i5-8500T, 6 núcleos, 16 GB
Sistema
Ubuntu Server 26.04 LTS
Consumo
65 W
Acceso público
Túnel de Cloudflare
Acceso privado
Tailscale

Lo que no está en esta página: direcciones, identificadores de túnel y de dispositivos, subdominios de paneles privados y cualquier cosa con forma de token. Lo interesante es la arquitectura y las decisiones; las coordenadas no, y publicarlas sería justo lo contrario de lo que defiende el montaje.

Cómo está montado

8servicios en marcha
0puertos abiertos en el router
2vías de acceso separadas
65 Wde consumo total
9incidencias documentadas

Nada de abrir puertos

El demonio del túnel mantiene conexiones salientes hacia Cloudflare, así que el tráfico entra sin que nada escuche en el router y la IP de casa no se publica nunca.

Dos puertas, funciones distintas

El túnel es para las visitas y solo lleva las webs públicas. Tailscale es para mí y lleva SSH y los paneles. Ninguna puede hacer el trabajo de la otra por accidente.

Nombres de contenedor, no IPs

Cada ruta del proxy y del túnel apunta a un nombre de contenedor. Las IPs cambian en cuanto recreas algo; los nombres no. Dos de las nueve incidencias fueron exactamente esto.

Montado por UUID

El disco de datos se monta por UUID del sistema de archivos y no por ruta de dispositivo, porque añadir un segundo disco puede reordenar qué significa /dev/sda.

Reconstruible desde ficheros

Cada servicio es un Docker Compose, y el razonamiento vive en un runbook en la propia máquina. Rehacerlo es un checkout y un compose up, no una excavación arqueológica.

Nueve incidencias escritas

Síntoma, causa, solución, lección. Escritas en el momento y no reconstruidas después, porque la lección es la parte que sobrevive.

Dos puertas en vez de un agujero

Lo obvio para llegar a un servidor de casa desde fuera es abrir puertos en el router. No lo hice, por tres razones: la línea puede estar detrás de CGNAT, con lo cual quizá ni se puede; un puerto abierto es una invitación permanente; y separar el tráfico público de mi propio acceso hace que un error en un lado no pueda exponer el otro.

Túnel de CloudflareTailscale
Para quiénCualquiera que visite las webs públicasSolo mis dispositivos
Qué llevaLa web de WordPress y un rank trackerSSH y todos los paneles de administración
Cómo conectaConexiones salientes del servidor hacia CloudflareUna malla privada entre dispositivos autorizados
Qué añadeCDN, WAF y protección DDoS por delanteQue no se publica absolutamente nada
Config del routerNingunaNinguna

La regla que lo hizo funcionar: el contenedor del túnel tiene que estar en la red Docker de cada servicio que expone. Si no, el servicio responde perfectamente dentro de la máquina mientras la URL pública devuelve 502, que es como empezaron dos de las nueve incidencias de abajo.

Ocho servicios, un mini PC

Todo en Docker, detrás de un proxy inverso que se encarga del enrutado y de los certificados. Público y privado están separados a nivel de red, no confiando en que nadie adivine una URL.

ServicioQué haceAccesible desde
WordPressSirve devilfruittcg.com, la web de nicho del caso de SEO.Público
MySQLSu base de datos. Nunca expuesta, accesible solo dentro de la red Docker.Solo interno
Proxy inversoEnrutado y certificados TLS de todo lo que hay detrás.Panel privado
Rank trackerSeguimiento de keywords self-hosted para la web de nicho, en su propio subdominio.Público
Servidor de fotosFototeca personal en el disco de datos de 1 TB.Privado
DashboardUna página con todos los servicios y su estado.Privado
Filtro DNSDNS y bloqueo de publicidad para toda la casa.Privado
Monitor de uptimeComprueba cada servicio y avisa cuando uno deja de responder.Privado

Nueve cosas que se rompieron

Esta es la parte que yo querría leer de otra persona. Seguir un tutorial hasta que funciona lo hace cualquiera. Lo que merece la pena enseñar es qué pasó cuando no funcionó, y si la causa se entendió de verdad o solo se esquivó.

SíntomaCausaLección
El servidor se apagaba al arrancarUn conector SATA mal asentado después de montar el disco de datos de 1 TB.Apagado instantáneo a los segundos de encender es conector o cortocircuito. Deja la carcasa abierta y mira los primeros segundos.
502 en la web públicaWordPress y el proxy inverso estaban en redes Docker distintas y no se veían.Los contenedores de composes distintos son invisibles entre sí. Hay que declarar la red del otro como externa y unirse a ella.
502 en el rank trackerEl demonio del túnel no estaba en la red Docker de ese servicio y no podía resolver el nombre del contenedor.El contenedor del túnel tiene que estar en la red de cada servicio que expone.
Demasiados redireccionamientos en SafariCloudflare en modo Flexible mientras el proxy forzaba HTTPS, que entra en bucle.Flexible más SSL forzado es un bucle. Full más SSL forzado es lo correcto.
Fallaba la petición de certificadoEl proxy de Cloudflare bloquea la verificación HTTP de Let's Encrypt.Con la nube naranja activa, siempre DNS challenge, con un token de API acotado.
Error de conexión a la base de datosQuedó un valor de relleno como contraseña en el compose, y MySQL ya había guardado la primera con la que arrancó.Cambiar la contraseña en el compose no la cambia en el volumen. Hay que recrear el volumen, y solo cuando es seguro hacerlo.
Desapareció el panel de administraciónEl filtro de DNS cambia su puerto interno en cuanto termina su asistente inicial.Un panel que desaparece justo al acabar la configuración normalmente ha cambiado de puerto. Mira los logs antes de recrear nada.
Host validation failedEl dashboard exige ahora declarar explícitamente los hosts permitidos.Un error de host u origen en el primer acceso es una comprobación de seguridad, no un fallo. Declara las direcciones que usas de verdad.
No se podía abrir el fichero de base de datosUna carpeta montada la creó root, así que el contenedor no podía escribir en ella.Un contenedor que no puede escribir en su volumen casi siempre es un problema de permisos en la carpeta del host.

Por qué un servidor de casa pinta algo en un portfolio

AfirmaciónEvidencia
La capa de debajo del CMSSistema operativo, discos y fstab, red de Docker, proxy inverso, TLS, DNS y acceso remoto. No un panel gestionado haciendo de infraestructura.
Seguridad por arquitecturaSin puertos abiertos, tráfico público y privado por vías distintas, paneles nunca publicados y base de datos nunca expuesta.
Diagnóstico, no parchesNueve incidencias con la causa identificada en cada una. Los 502 eran red de Docker, el bucle de redirecciones era un modo de SSL, el fallo de escritura eran permisos del host.
Está documentadoUn runbook en la máquina y un vault de notas enlazadas, que es la razón de que esta página se haya podido escribir desde registros y no desde la memoria.
Es realdevilfruittcg.com se sirve desde ahí, en público, y tiene su propio caso.

Sigue abierto: confirmar si la línea está detrás de CGNAT, sacar una copia antigua de fotos del SSD del sistema y terminar la conexión con Search Console del rank tracker. Un homelab sin tareas pendientes es un homelab que no usa nadie.

/ Siguiente

Todo conecta.

Esta es la capa que fui a buscar cuando el diseño dejó de bastarme, y en la que quiero seguir. Si necesitas a alguien que se haga cargo de la máquina además de la web, escríbeme.