Por qué consolidar cinco servicios en un solo VPS
El enfoque «una app, un VPS» tiene su lógica: aislamiento total, despliegue independiente, sin riesgo de contención. También tiene un coste real — cinco servidores, cinco direcciones IP, cinco renovaciones, cinco configuraciones de nginx, cinco certificados TLS que vigilar. Para un stack personal o un equipo pequeño, ese coste solo se justifica si las aplicaciones tienen picos de carga muy distintos o requisitos de seguridad incompatibles.
Nextcloud, Vaultwarden, Jellyfin, Immich y Paperless-ngx comparten la característica de ser aplicaciones de tráfico moderado, utilizadas principalmente por uno o pocos usuarios. Vaultwarden consume menos de 50 MB de RAM en modo de espera. Paperless-ngx ronda los 200 MB. Immich, el más exigente en reposo fuera del transcodificado, se mantiene por debajo de 400 MB en inactividad. Estas mediciones están documentadas en los proyectos GitHub respectivos y en los comentarios de la comunidad self-hosting.
La consolidación en un VPS no elimina los riesgos — los concentra. La contrapartida es un único punto de entrada que endurecer, un único certificado que gestionar y un manifiesto Docker Compose versionado que constituye por sí solo la documentación completa de la infraestructura.
Lo que este stack aporta en la práctica
- Soberanía sobre los datos — archivos, contraseñas, fotos y documentos permanecen en tu servidor, bajo tu clave de cifrado, sin dependencia de ningún proveedor cloud de terceros.
- Un único certificado TLS wildcard — Caddy solicita y renueva automáticamente
*.votre-domaine.commediante el reto DNS-01, cubriendo todos los subdominios del stack con una sola configuración. - Red Docker interna aislada — ninguna aplicación expone un puerto público directamente; todo el tráfico entrante pasa por Caddy en los puertos 80 y 443, y los servicios se comunican a través de una red bridge privada.
- Volúmenes nombrados y restauración predecible — cada servicio declara sus datos en un volumen Docker nombrado (
nextcloud_data,vaultwarden_data…), lo que hace que las copias de seguridad y las migraciones sean reproducibles con un solo comandorsync. - Actualizaciones independientes — descargar una nueva imagen de Immich no reinicia ni Jellyfin ni Paperless-ngx;
docker compose up -d --no-deps immichsolo toca el servicio en cuestión. - Coste fijo y predecible — un VPS de recursos fijos elimina las sorpresas de facturación que generan los servicios en la nube cuando el transcodificado de Jellyfin se dispara o las tareas OCR de Paperless-ngx se acumulan.
- Posible interconexión de servicios — Nextcloud puede usar Redis y MariaDB ya presentes en el Compose; Immich comparte la misma red que el reverse proxy sin configuración adicional.
Requisitos previos: dimensionamiento y puertos
El suelo realista para este stack en uso habitual es 4 vCPU / 8 GB de RAM / 100 GB de almacenamiento SSD. Este dimensionamiento cubre las huellas en reposo de cada servicio y deja margen para el transcodificado bajo demanda de Jellyfin y las tareas OCR de Paperless-ngx, que son los dos picos de carga más relevantes del stack.
Huellas medidas en modo de espera (sin sesión activa, sin tarea en segundo plano):
- Nextcloud (PHP-FPM + cron): ~300 MB según la carga de sincronización.
- Vaultwarden: < 50 MB, imagen Rust muy compacta.
- Jellyfin: ~250 MB sin transcodificado activo. En transcodificado software (x264, 1080p): entre 1 y 2 vCPU en punta.
- Immich (server + microservices): ~350-400 MB en reposo. Las tareas de aprendizaje automático (detección de caras, clasificación) pueden consumir hasta 2 GB de RAM según el volumen.
- Paperless-ngx (web + worker): ~200 MB. El OCR de Tesseract sobre un PDF de 50 páginas puede saturar temporalmente un vCPU.
- Caddy: < 30 MB.
- Bases de datos (MariaDB para Nextcloud + Paperless, Redis): ~200 MB combinados.
Total estimado en reposo: ~1,6 GB de los 8 GB disponibles. El margen absorbe los picos y permite añadir otro servicio sin redimensionar.
Puertos que abrir en el cortafuegos: 80/tcp y 443/tcp únicamente. El resto permanece cerrado — los servicios internos no se exponen directamente.
Despliegue paso a paso
Preparar el VPS
Conéctate por SSH a tu VPS y actualiza el sistema:
apt update && apt upgrade -y. Instala Docker y el plugin Compose:curl -fsSL https://get.docker.com | sh. Verifica la instalación:docker compose version. Crea un usuario no root dedicado y agrégalo al grupodocker:adduser deploy && usermod -aG docker deploy. Activa UFW con las reglas mínimas:ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable.Configurar el DNS
En tu zona DNS, crea un registro A para el dominio raíz apuntando a la IP de tu VPS, luego subdominios CNAME o A para cada servicio:
nextcloud.votre-domaine.com,vault.votre-domaine.com,jellyfin.votre-domaine.com,photos.votre-domaine.comydocs.votre-domaine.com. Si usas el reto DNS-01 de Caddy para el certificado wildcard, asegúrate de que tu proveedor DNS tiene una API compatible con el módulocaddy-dnscorrespondiente. Espera la propagación del DNS (entre minutos y horas según el TTL configurado).Crear la estructura de directorios
Crea el árbol del proyecto en el VPS:
mkdir -p /opt/homelab/{caddy,nextcloud,vaultwarden,jellyfin,immich,paperless}. Este directorio contendrá el archivodocker-compose.yml, elCaddyfiley los archivos de entorno. Los datos persistentes se almacenarán en volúmenes Docker nombrados, no en bind-mounts, para simplificar las copias de seguridad y evitar problemas de permisos.Escribir el Caddyfile
En
/opt/homelab/caddy/Caddyfile, declara un bloque por subdominio. Ejemplo para Nextcloud:nextcloud.votre-domaine.com { reverse_proxy nextcloud:80 }. Repite el patrón para cada servicio apuntando al nombre del servicio Docker (vaultwarden,jellyfin,immich-server,paperless-webserver). Caddy obtiene y renueva los certificados Let's Encrypt automáticamente en el primer acceso. Para un certificado wildcard, sustituye los bloques individuales por*.votre-domaine.comcon el módulo DNS de tu proveedor, configurado mediante las variables de entorno de la imagen Caddy personalizada.Escribir el docker-compose.yml
Crea
/opt/homelab/docker-compose.ymlcon una redproxycompartida y una redinternalaislada. Declara el servicio Caddy conports: ["80:80", "443:443"]yvolumes: ["./caddy/Caddyfile:/etc/caddy/Caddyfile", "caddy_data:/data"]. Para cada aplicación, declaranetworks: [proxy, internal]y no expongas ningúnports:— solo Caddy expone puertos públicos. Usadepends_onconcondition: service_healthypara que Nextcloud no intente conectarse a MariaDB antes de que esté lista. Las variables sensibles (contraseñas de bases de datos, claves secretas) van en un archivo.envreferenciado porenv_file: .env.Configurar las variables de entorno
Crea
/opt/homelab/.envcon las variables requeridas por cada servicio:MYSQL_ROOT_PASSWORD,MYSQL_DATABASE,MYSQL_USER,MYSQL_PASSWORDpara MariaDB;NEXTCLOUD_ADMIN_USER,NEXTCLOUD_ADMIN_PASSWORD,NEXTCLOUD_TRUSTED_DOMAINSpara Nextcloud;VAULTWARDEN_ADMIN_TOKENpara Vaultwarden. Genera los secretos conopenssl rand -hex 32. Para Immich, copia el archivo.envde ejemplo del repositorio oficial. Nunca hagas commit de este archivo en un repositorio público; añade.enva tu.gitignore.Lanzar el stack
Desde
/opt/homelab, ejecutadocker compose pullpara descargar todas las imágenes y luegodocker compose up -dpara arrancar el conjunto. Sigue los logs de arranque condocker compose logs -fpara asegurarte de que cada servicio inicia sin errores. La primera inicialización de Nextcloud y Paperless-ngx puede tardar unos minutos (migraciones de base de datos, generación de claves). Caddy obtiene los certificados TLS en el primer acceso a cada subdominio — verifica que los puertos 80 y 443 son accesibles desde el exterior antes de probar.Finalizar la configuración de cada servicio
Accede a cada interfaz web para completar la configuración inicial: Nextcloud (
nextcloud.votre-domaine.com) para activar las aplicaciones recomendadas (Contacts, Calendar, Talk); Immich (photos.votre-domaine.com) para configurar las bibliotecas y activar las tareas de aprendizaje automático en segundo plano; Paperless-ngx (docs.votre-domaine.com) para configurar el consumidor de documentos y el OCR; Jellyfin (jellyfin.votre-domaine.com) para apuntar a las carpetas de medios montadas como volúmenes. Vaultwarden (vault.votre-domaine.com) solo requiere crear una cuenta desde el cliente Bitwarden — no se necesita configuración inicial del servidor.
Caddy, Nginx Proxy Manager o Traefik: qué reverse proxy para este stack
Desplace la tabla
| Criterio | Caddy | Nginx Proxy Manager | Traefik |
|---|---|---|---|
| Configuración | Caddyfile declarativo, recarga sin downtime | Interfaz web, sin archivos que editar | Labels Docker, recarga automática |
| SSL automático | Integrado, DNS-01 y HTTP-01 nativos | Let's Encrypt vía interfaz, DNS-01 posible | Let's Encrypt vía ACME resolver, DNS-01 vía providers |
| Wildcard | Nativo vía módulo caddy-dns | Posible pero requiere configuración manual | Nativo vía certificateResolvers |
| Curva de aprendizaje | Baja — Caddyfile legible en 10 minutos | Muy baja — todo se hace con clics | Media — documentación extensa |
| Adecuado para este stack | Sí — configuración versionada, recarga en caliente | Sí para empezar, menos ideal para versionar | Sí para stacks más complejos, sobrecarga de config aquí |
Hardening mínimo antes de la exposición pública
Cuatro medidas a aplicar antes de hacer el stack accesible desde el exterior:
1. Desactiva la página de registro abierto de Vaultwarden poniendo SIGNUPS_ALLOWED=false en el .env una vez creada tu cuenta.
2. Añade una cabecera X-Robots-Tag: noindex en el Caddyfile para Vaultwarden y la interfaz de administración de Paperless-ngx — estas páginas no deben indexarse.
3. Activa la rotación de logs de Docker (log-opts en /etc/docker/daemon.json) para evitar que los logs de Jellyfin o Paperless-ngx llenen el disco.
4. Programa un docker compose pull && docker compose up -d semanal mediante cron para mantener las imágenes actualizadas — las aplicaciones self-hosted publican periódicamente parches de seguridad. Revisa los changelogs antes de actualizar Immich, que puede introducir migraciones de base de datos no reversibles.
Resolución de problemas: errores comunes en el arranque
Error response from daemon: network proxy declared as external, but could not be found — Este mensaje aparece cuando la red Docker externa declarada en docker-compose.yml no existe todavía. Créala manualmente antes del primer docker compose up: docker network create proxy. O convierte la red en interna en el Compose (elimina external: true) para que Compose la cree él mismo.
nextcloud.votre-domaine.com redirected you too many times — Nextcloud detecta la petición HTTPS como HTTP porque Caddy la reenvía en HTTP sobre la red interna. Añade NEXTCLOUD_TRUSTED_PROXIES con la subred Docker (por ejemplo 172.16.0.0/12) y OVERWRITEPROTOCOL=https en el .env. Sin estas variables, Nextcloud no confía en las cabeceras X-Forwarded-Proto transmitidas por Caddy e intenta redirigir a HTTPS indefinidamente.
Immich cannot connect to database: connection refused — Immich arranca antes de que PostgreSQL esté listo. Añade depends_on con condition: service_healthy en el servicio immich-server y verifica que el servicio database declara un healthcheck válido (por ejemplo pg_isready -U immich). Sin healthcheck, Docker Compose considera el servicio «arrancado» en cuanto se lanza el contenedor, no cuando acepta conexiones.
Paperless-ngx worker exited with error: celery worker unhealthy — La base de datos Redis no es accesible. Comprueba que el servicio Redis está en la misma red que Paperless y que la variable PAPERLESS_REDIS apunta al nombre del servicio Compose (redis://redis:6379), no a localhost — en Docker Compose, localhost dentro de un contenedor apunta al propio contenedor, no a otro servicio.
Caddy no renueva el certificado wildcard — Si usas el reto DNS-01, verifica que las variables de entorno de la API DNS (token, zone ID) se transmiten correctamente al servicio Caddy en el Compose. Un token caducado o un permiso DNS insuficiente hace que la renovación falle silenciosamente 30 días antes de la expiración — Caddy registra el error pero no bloquea el tráfico hasta la expiración real.
Para ir más lejos
Este stack cubre los cinco servicios más demandados en las configuraciones homelab de 2026. Cada uno tiene una guía dedicada en este blog si quieres profundizar en un aspecto concreto: backup de Nextcloud, gestión de álbumes en Immich, transcodificado hardware en Jellyfin, reglas de retención en Paperless-ngx o sincronización multidispositivo en Vaultwarden.
Para ir más allá de este stack, las próximas piezas habitualmente añadidas son Uptime Kuma (monitorización de servicios internos) y Ntfy o Apprise (notificaciones push). Estos servicios son suficientemente ligeros para añadirse al mismo Compose sin afectar al dimensionamiento.
Si la gestión manual de actualizaciones y copias de seguridad se convierte en una carga, Coolify y Dokploy ofrecen interfaces que automatizan estas tareas conservando la arquitectura Docker Compose subyacente.