<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://ricardllop.com/es/blog</id>
    <title>RicardLlop Blog</title>
    <updated>2026-02-25T21:12:08.428Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://ricardllop.com/es/blog"/>
    <subtitle>RicardLlop Blog</subtitle>
    <icon>https://ricardllop.com/es/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Homelab con Docker Compose]]></title>
        <id>https://ricardllop.com/es/blog/homelab</id>
        <link href="https://ricardllop.com/es/blog/homelab"/>
        <updated>2026-02-25T21:12:08.428Z</updated>
        <summary type="html"><![CDATA[Este artículo trata sobre un homelab autoalojado que corre en un mini PC dedicado en una red doméstica, gestionado íntegramente con Docker Compose. La configuración proporciona gestión de fotos, streaming de medios, bloqueo de anuncios a nivel de red, proxy inverso con TLS automático y DNS dinámico — todo accesible desde cualquier lugar mediante un dominio público, o localmente mediante hostnames .local.]]></summary>
        <content type="html"><![CDATA[<p>Este artículo trata sobre un homelab autoalojado que corre en un mini PC dedicado en una red doméstica, gestionado íntegramente con Docker Compose. La configuración proporciona gestión de fotos, streaming de medios, bloqueo de anuncios a nivel de red, proxy inverso con TLS automático y DNS dinámico — todo accesible desde cualquier lugar mediante un dominio público, o localmente mediante hostnames <code>.local</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="arquitectura-de-red">Arquitectura de red<a href="https://ricardllop.com/es/blog/homelab#arquitectura-de-red" class="hash-link" aria-label="Enlace directo al Arquitectura de red" title="Enlace directo al Arquitectura de red" translate="no">​</a></h2>
<p>El router solo redirige los puertos <strong>80</strong> y <strong>443</strong> hacia el mini PC. Todo el tráfico público y local entra a través de <strong>Caddy</strong>, que termina TLS y enruta las peticiones al servicio correspondiente. Los servicios internos se comunican a través de una red bridge Docker compartida (<code>homelab</code>) y nunca son directamente accesibles desde el exterior.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Internet</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │  :80 / :443</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Router (reenvío de puertos)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Mini PC</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── Caddy (proxy inverso, TLS)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │     ├── immich.homelab.example.com  ──► Immich</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │     ├── immich.local  (solo LAN)   ──► Immich</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │     ├── jellyfin.local  (solo LAN) ──► Jellyfin</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │     └── adguard.local  (solo LAN)  ──► AdGuard Home</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── Immich        (biblioteca de fotos)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── Jellyfin      (servidor multimedia)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── AdGuard Home  (DNS + bloqueo de anuncios)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └── DDNS Updater  (mantiene el registro DNS apuntando a la IP del hogar)</span><br></span></code></pre></div></div>
<p>Los servicios de solo uso local (cualquier hostname bajo <code>.local</code>) rechazan conexiones fuera de <code>192.168.0.0/24</code> a nivel de Caddy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="estructura-del-proyecto">Estructura del proyecto<a href="https://ricardllop.com/es/blog/homelab#estructura-del-proyecto" class="hash-link" aria-label="Enlace directo al Estructura del proyecto" title="Enlace directo al Estructura del proyecto" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── 00_homelab-network.sh           # Crea la red Docker compartida (ejecutar una vez)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── 01_docker-compose-up-all.sh     # Inicia todos los servicios</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── caddy-reverse-proxy/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── Caddyfile                   # Definiciones de rutas y reglas de acceso local</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── docker-compose.yml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── immich/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── docker-compose.yml          # Immich server, ML, Redis, PostgreSQL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── example.env                 # Plantilla — copiar a .env y rellenar secretos</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── backup-borg-setup.sh        # Inicialización única del repositorio Borg</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── backup-borg-script-v2.sh   # Backup incremental + purga (ejecutar con cron)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── jellifyn/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── docker-compose.yml          # Jellyfin con passthrough de GPU AMD</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── adguardhome/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── docker-compose.yml          # AdGuard Home en red host</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└── namecheap-ddns-updater/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── Dockerfile                  # Imagen mínima Alpine + bash + curl</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── entrypoint.sh               # Bucle de polling que llama a la API DDNS de Namecheap</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── docker-compose.yml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └── example.env                 # Plantilla — copiar a .env y rellenar secretos</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="servicios">Servicios<a href="https://ricardllop.com/es/blog/homelab#servicios" class="hash-link" aria-label="Enlace directo al Servicios" title="Enlace directo al Servicios" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="caddy--proxy-inverso-y-tls">Caddy — proxy inverso y TLS<a href="https://ricardllop.com/es/blog/homelab#caddy--proxy-inverso-y-tls" class="hash-link" aria-label="Enlace directo al Caddy — proxy inverso y TLS" title="Enlace directo al Caddy — proxy inverso y TLS" translate="no">​</a></h3>
<p><a href="https://caddyserver.com/" target="_blank" rel="noopener noreferrer" class="">Caddy</a> es el único punto de entrada para todo el tráfico. Obtiene y renueva automáticamente certificados TLS mediante Let's Encrypt para dominios públicos, y emite su propio certificado de CA local para hostnames <code>.local</code>. El <code>Caddyfile</code> define un snippet reutilizable <code>local_only</code> que cancela cualquier conexión que no provenga de la subred doméstica, aplicado a cada bloque <code>.local</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="immich--biblioteca-de-fotos">Immich — biblioteca de fotos<a href="https://ricardllop.com/es/blog/homelab#immich--biblioteca-de-fotos" class="hash-link" aria-label="Enlace directo al Immich — biblioteca de fotos" title="Enlace directo al Immich — biblioteca de fotos" translate="no">​</a></h3>
<p><a href="https://immich.app/" target="_blank" rel="noopener noreferrer" class="">Immich</a> es una alternativa autoalojada a Google Fotos con backup automático desde el móvil, reconocimiento facial, detección de objetos y una interfaz de álbumes/línea de tiempo. La stack consta de cuatro contenedores:</p>
<table><thead><tr><th>Contenedor</th><th>Función</th></tr></thead><tbody><tr><td><code>immich-server</code></td><td>API principal e interfaz web</td></tr><tr><td><code>immich-machine-learning</code></td><td>Inferencia de reconocimiento facial y de objetos</td></tr><tr><td><code>redis</code> (Valkey)</td><td>Cola de trabajos y caché</td></tr><tr><td><code>database</code> (PostgreSQL + pgvecto-rs)</td><td>Almacenamiento persistente con búsqueda vectorial para álbumes inteligentes</td></tr></tbody></table>
<p>La configuración se realiza mediante un archivo <code>.env</code> (ver <code>example.env</code>). Las variables más importantes son <code>UPLOAD_LOCATION</code> (donde se almacenan las fotos en disco) y <code>DB_DATA_LOCATION</code> (directorio de datos de PostgreSQL).</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="backup">Backup<a href="https://ricardllop.com/es/blog/homelab#backup" class="hash-link" aria-label="Enlace directo al Backup" title="Enlace directo al Backup" translate="no">​</a></h4>
<p>Las fotos y la base de datos de Immich se respaldan con <a href="https://borgbackup.readthedocs.io/" target="_blank" rel="noopener noreferrer" class="">Borg</a>:</p>
<ol>
<li class=""><strong>Configuración inicial</strong> — inicializar el repositorio Borg:<!-- -->
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">./immich/backup-borg-setup.sh</span><br></span></code></pre></div></div>
</li>
<li class=""><strong>Backup incremental</strong> — volcar la base de datos y archivar la biblioteca:<!-- -->
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">./immich/backup-borg-script-v2.sh</span><br></span></code></pre></div></div>
<!-- -->Programar con cron. Las miniaturas y el vídeo transcodificado están excluidos (se pueden regenerar). Los archivos se purgan para conservar las últimas 4 semanas y 3 instantáneas mensuales.</li>
</ol>
<p>Para instrucciones de restauración, ver la <a href="https://immich.app/docs/guides/template-backup-script/#restoring" target="_blank" rel="noopener noreferrer" class="">guía de backup de Immich</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jellyfin--servidor-multimedia">Jellyfin — servidor multimedia<a href="https://ricardllop.com/es/blog/homelab#jellyfin--servidor-multimedia" class="hash-link" aria-label="Enlace directo al Jellyfin — servidor multimedia" title="Enlace directo al Jellyfin — servidor multimedia" translate="no">​</a></h3>
<p><a href="https://jellyfin.org/" target="_blank" rel="noopener noreferrer" class="">Jellyfin</a> es un sistema multimedia autoalojado para películas, series y música. El contenedor está configurado con passthrough de GPU AMD (<code>/dev/dri</code>) y el driver <code>radeonsi</code> VAAPI para transcodificación de vídeo acelerada por hardware. Los archivos de medios se leen desde <code>/mnt/disk1/r-gmk/jellyfinmedia</code> del host.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="adguard-home--dns-y-bloqueo-de-anuncios">AdGuard Home — DNS y bloqueo de anuncios<a href="https://ricardllop.com/es/blog/homelab#adguard-home--dns-y-bloqueo-de-anuncios" class="hash-link" aria-label="Enlace directo al AdGuard Home — DNS y bloqueo de anuncios" title="Enlace directo al AdGuard Home — DNS y bloqueo de anuncios" translate="no">​</a></h3>
<p><a href="https://github.com/AdguardTeam/AdGuardHome" target="_blank" rel="noopener noreferrer" class="">AdGuard Home</a> actúa como resolvedor DNS de la red. Todos los dispositivos de la LAN apuntan al mini PC como servidor DNS. AdGuard Home bloquea dominios de anuncios y rastreadores antes de que lleguen al navegador, y también resuelve los hostnames <code>.local</code> a la IP LAN del mini PC para que las rutas locales de Caddy funcionen en cualquier dispositivo sin entradas manuales en <code>/etc/hosts</code>.</p>
<p>Se ejecuta con <code>network_mode: host</code> para poder escuchar en el puerto 53 de la interfaz LAN. La interfaz web está en el puerto 81, accesible mediante <code>adguard.local</code> a través de Caddy.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="actualizador-ddns-de-namecheap">Actualizador DDNS de Namecheap<a href="https://ricardllop.com/es/blog/homelab#actualizador-ddns-de-namecheap" class="hash-link" aria-label="Enlace directo al Actualizador DDNS de Namecheap" title="Enlace directo al Actualizador DDNS de Namecheap" translate="no">​</a></h3>
<p>Un servicio personalizado ligero (Alpine + bash + curl) que mantiene un registro DNS de Namecheap actualizado cuando cambia la IP del hogar. Cada 10 minutos obtiene la IP pública actual desde <code>checkip.amazonaws.com</code> y llama a la API DDNS de Namecheap para cada hostname configurado.</p>
<p>Configurar mediante <code>.env</code> (ver <code>example.env</code>):</p>
<table><thead><tr><th>Variable</th><th>Descripción</th></tr></thead><tbody><tr><td><code>DDNS_PASSWORD</code></td><td>Contraseña DDNS de Namecheap (encontrada en la página de gestión DNS del dominio)</td></tr><tr><td><code>DOMAIN_NAME</code></td><td>Dominio raíz, p. ej. <code>example.com</code></td></tr><tr><td><code>HOSTNAME_ENTRIES</code></td><td>Subdominios separados por comas a actualizar, p. ej. <code>@,immich,vpn</code></td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="primeros-pasos">Primeros pasos<a href="https://ricardllop.com/es/blog/homelab#primeros-pasos" class="hash-link" aria-label="Enlace directo al Primeros pasos" title="Enlace directo al Primeros pasos" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="requisitos-previos">Requisitos previos<a href="https://ricardllop.com/es/blog/homelab#requisitos-previos" class="hash-link" aria-label="Enlace directo al Requisitos previos" title="Enlace directo al Requisitos previos" translate="no">​</a></h3>
<ul>
<li class="">Docker y Docker Compose instalados en el host</li>
<li class="">Borg instalado en el host (para los backups de Immich)</li>
<li class="">Un dominio de Namecheap con DDNS habilitado</li>
<li class="">Reenvío de puertos del router: <strong>80</strong> y <strong>443</strong> → mini PC</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuración-inicial">Configuración inicial<a href="https://ricardllop.com/es/blog/homelab#configuraci%C3%B3n-inicial" class="hash-link" aria-label="Enlace directo al Configuración inicial" title="Enlace directo al Configuración inicial" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># 1. Crear la red Docker compartida</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">./00_homelab-network.sh</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 2. Configurar Immich</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cp immich/example.env immich/.env</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Editar immich/.env: establecer DB_PASSWORD, UPLOAD_LOCATION, DB_DATA_LOCATION</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 3. Configurar el actualizador DDNS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cp namecheap-ddns-updater/example.env namecheap-ddns-updater/.env</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Editar namecheap-ddns-updater/.env: establecer DDNS_PASSWORD, DOMAIN_NAME, HOSTNAME_ENTRIES</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 4. Inicializar el repositorio de backup Borg</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">./immich/backup-borg-setup.sh</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 5. Iniciar todos los servicios</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">./01_docker-compose-up-all.sh</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="iniciar--detener-servicios-individuales">Iniciar / detener servicios individuales<a href="https://ricardllop.com/es/blog/homelab#iniciar--detener-servicios-individuales" class="hash-link" aria-label="Enlace directo al Iniciar / detener servicios individuales" title="Enlace directo al Iniciar / detener servicios individuales" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cd &lt;service-dir&gt;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker compose up -d       # iniciar</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker compose down        # detener</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker compose logs -f     # seguir logs</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>Ricard Llop</name>
            <uri>https://github.com/ricardllop</uri>
        </author>
        <category label="DevOps" term="DevOps"/>
        <category label="Homelab" term="Homelab"/>
        <category label="Docker" term="Docker"/>
        <category label="Caddy" term="Caddy"/>
        <category label="Immich" term="Immich"/>
        <category label="Adguard" term="Adguard"/>
        <category label="Jellyfin" term="Jellyfin"/>
    </entry>
</feed>