Docker Compose: De 0 a Homelab Completo en 2025

Robot punk orquestando contenedores Docker Compose con docker-compose.yml servicios múltiples 2025

Introducción: Tu homelab completo en minutos

¿Cansado de instalar servicios uno por uno? Docker Compose convierte tu servidor en un homelab completamente funcional con un solo comando.

En esta guía encontrarás 12 stacks listos para copiar y pegar, desde media centers hasta monitorización, pasando por automatización y productividad. Cada stack incluye configuración completa, explicaciones y mejores prácticas.

💡 Lo que aprenderás:

  • 12 docker-compose.yml funcionales y listos para producción
  • Estructura profesional de directorios para homelab
  • Gestión de secretos, redes y volúmenes
  • Monitorización y logs centralizados
  • Troubleshooting de problemas comunes

¿Por qué Docker Compose para tu homelab?

Ventajas frente a contenedores individuales

Aspecto docker run (individual) docker-compose.yml
Complejidad Comandos largos y difíciles de recordar ✅ Todo en archivo YAML legible
Reproducibilidad Hay que documentar comandos manualmente ✅ Archivo = documentación viva
Servicios múltiples Gestionar cada contenedor por separado docker compose up -d para todo
Dependencias Orden manual de inicio depends_on automático
Redes Configurar manualmente con –network ✅ Red aislada automática por stack
Actualizaciones Stop, rm, pull, run (4 pasos) docker compose pull && up -d
Backup config Scripts personalizados ✅ Versionar .yml en git

Casos de uso ideales

  • Media Center completo: Plex/Jellyfin + Sonarr + Radarr + qBittorrent
  • Stack automatización: n8n + PostgreSQL + Redis
  • Monitorización: Prometheus + Grafana + Node Exporter
  • Productividad: Nextcloud + MariaDB + Redis + Collabora
  • Desarrollo: GitLab + PostgreSQL + Redis + Runner

Estructura base para homelab profesional

Antes de crear stacks, organiza tu servidor con esta estructura recomendada:

/home/usuario/docker/
├── stacks/
│   ├── media/              # Plex, Jellyfin, Arr stack
│   │   └── docker-compose.yml
│   ├── automation/         # n8n, Home Assistant
│   │   └── docker-compose.yml
│   ├── monitoring/         # Prometheus, Grafana
│   │   └── docker-compose.yml
│   ├── productivity/       # Nextcloud, Vaultwarden
│   │   └── docker-compose.yml
│   └── network/            # Pi-hole, Nginx Proxy Manager
│       └── docker-compose.yml
├── data/                   # Volúmenes persistentes
│   ├── plex/
│   ├── nextcloud/
│   ├── grafana/
│   └── ...
├── media/                  # Contenido multimedia
│   ├── peliculas/
│   ├── series/
│   └── musica/
└── backups/                # Backups automáticos
    └── ...

Comando para crear estructura completa:

mkdir -p ~/docker/{stacks/{media,automation,monitoring,productivity,network},data,media/{peliculas,series,musica},backups}

Stack 1: Media Center completo (Jellyfin + Arr)

El stack más popular para homelab: servidor multimedia con descarga y organización automática.

Servicios incluidos:

  • Jellyfin: Media server open source (alternativa a Plex)
  • Sonarr: Gestión automática de series
  • Radarr: Gestión automática de películas
  • qBittorrent: Cliente torrent con WebUI
  • Prowlarr: Indexer manager (un solo lugar para trackers)

Archivo: ~/docker/stacks/media/docker-compose.yml

version: '3.8'

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/jellyfin/config:/config
      - ~/docker/data/jellyfin/cache:/cache
      - ~/docker/media:/media
    ports:
      - "8096:8096"
    restart: unless-stopped
    networks:
      - media

  sonarr:
    image: linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/sonarr:/config
      - ~/docker/media/series:/tv
      - ~/docker/data/qbittorrent/downloads:/downloads
    ports:
      - "8989:8989"
    restart: unless-stopped
    networks:
      - media

  radarr:
    image: linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/radarr:/config
      - ~/docker/media/peliculas:/movies
      - ~/docker/data/qbittorrent/downloads:/downloads
    ports:
      - "7878:7878"
    restart: unless-stopped
    networks:
      - media

  prowlarr:
    image: linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/prowlarr:/config
    ports:
      - "9696:9696"
    restart: unless-stopped
    networks:
      - media

  qbittorrent:
    image: linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Madrid
      - WEBUI_PORT=8080
    volumes:
      - ~/docker/data/qbittorrent/config:/config
      - ~/docker/data/qbittorrent/downloads:/downloads
    ports:
      - "8080:8080"
      - "6881:6881"
      - "6881:6881/udp"
    restart: unless-stopped
    networks:
      - media

networks:
  media:
    driver: bridge

Desplegar el stack:

cd ~/docker/stacks/media
docker compose up -d

Acceso a servicios:

  • Jellyfin: http://IP:8096
  • Sonarr: http://IP:8989
  • Radarr: http://IP:7878
  • Prowlarr: http://IP:9696
  • qBittorrent: http://IP:8080 (user: admin, password en logs: docker logs qbittorrent)
⚡ Quick Start:

  1. Configurar Prowlarr → añadir indexers
  2. En Prowlarr → Apps → añadir Sonarr y Radarr (API keys)
  3. En Sonarr/Radarr → Download Clients → añadir qBittorrent
  4. Añadir contenido en Sonarr/Radarr → descarga automática
  5. Jellyfin detecta nuevos archivos automáticamente

Stack 2: n8n (Automatización avanzada)

Workflow automation potente con persistencia PostgreSQL y caché Redis. Alternativa self-hosted a Zapier/Make.

Archivo: ~/docker/stacks/automation/docker-compose.yml

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: n8n-postgres
    environment:
      POSTGRES_DB: n8n
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: n8n_secure_password_change_me
    volumes:
      - ~/docker/data/n8n/postgres:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - n8n
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: n8n-redis
    volumes:
      - ~/docker/data/n8n/redis:/data
    restart: unless-stopped
    networks:
      - n8n
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_secure_password_change_me
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - N8N_ENCRYPTION_KEY=change_this_to_random_32_chars
      - WEBHOOK_URL=https://n8n.tudominio.com/
      - GENERIC_TIMEZONE=Europe/Madrid
      - N8N_METRICS=true
      - N8N_LOG_LEVEL=info
    volumes:
      - ~/docker/data/n8n/data:/home/node/.n8n
    ports:
      - "5678:5678"
    restart: unless-stopped
    networks:
      - n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

networks:
  n8n:
    driver: bridge

Acceso: http://IP:5678

🔒 Seguridad importante:

  • Cambiar POSTGRES_PASSWORD por contraseña fuerte
  • Cambiar N8N_ENCRYPTION_KEY a string aleatorio de 32 caracteres
  • Si expones a internet, usar Cloudflare Tunnel o Nginx + autenticación

Generar encryption key segura:

openssl rand -base64 32

Stack 3: Nextcloud (Nube privada completa)

Tu propia nube con oficina online, calendario, contactos y sincronización de archivos.

Archivo: ~/docker/stacks/productivity/docker-compose.yml

version: '3.8'

services:
  mariadb:
    image: mariadb:11
    container_name: nextcloud-db
    command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword_change_me
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: nextcloud_password_change_me
    volumes:
      - ~/docker/data/nextcloud/mariadb:/var/lib/mysql
    restart: unless-stopped
    networks:
      - nextcloud

  redis:
    image: redis:7-alpine
    container_name: nextcloud-redis
    restart: unless-stopped
    networks:
      - nextcloud

  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    environment:
      - MYSQL_HOST=mariadb
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=nextcloud_password_change_me
      - REDIS_HOST=redis
      - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.tudominio.com IP_DE_TU_SERVIDOR
      - OVERWRITEPROTOCOL=https
      - OVERWRITEHOST=nextcloud.tudominio.com
    volumes:
      - ~/docker/data/nextcloud/html:/var/www/html
      - ~/docker/data/nextcloud/data:/var/www/html/data
    ports:
      - "8081:80"
    restart: unless-stopped
    networks:
      - nextcloud
    depends_on:
      - mariadb
      - redis

  # Opcional: Collabora Online (LibreOffice online)
  collabora:
    image: collabora/code:latest
    container_name: collabora
    environment:
      - domain=nextcloud\\.tudominio\\.com
      - username=admin
      - password=collabora_admin_password
      - extra_params=--o:ssl.enable=false --o:ssl.termination=true
    ports:
      - "9980:9980"
    restart: unless-stopped
    networks:
      - nextcloud
    cap_add:
      - MKNOD

networks:
  nextcloud:
    driver: bridge

Acceso: http://IP:8081

Primer inicio (crear usuario admin):

  1. Ir a http://IP:8081
  2. Crear usuario administrador
  3. Sistema detecta automáticamente MariaDB y Redis
  4. Instalar apps recomendadas: Calendar, Contacts, Tasks, Notes

Stack 4: Monitorización completa (Prometheus + Grafana)

Monitoriza tu homelab con métricas detalladas y dashboards profesionales.

Archivo: ~/docker/stacks/monitoring/docker-compose.yml

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
    volumes:
      - ~/docker/data/prometheus/config:/etc/prometheus
      - ~/docker/data/prometheus/data:/prometheus
    ports:
      - "9090:9090"
    restart: unless-stopped
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin_change_me
      - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource
    volumes:
      - ~/docker/data/grafana:/var/lib/grafana
    ports:
      - "3000:3000"
    restart: unless-stopped
    networks:
      - monitoring
    depends_on:
      - prometheus

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    command:
      - '--path.rootfs=/host'
    volumes:
      - '/:/host:ro,rslave'
    ports:
      - "9100:9100"
    restart: unless-stopped
    networks:
      - monitoring

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8082:8080"
    restart: unless-stopped
    networks:
      - monitoring
    privileged: true
    devices:
      - /dev/kmsg

networks:
  monitoring:
    driver: bridge

Archivo de configuración Prometheus:

Crear ~/docker/data/prometheus/config/prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

Acceso:

  • Prometheus: http://IP:9090
  • Grafana: http://IP:3000 (admin/admin_change_me)
  • Node Exporter: http://IP:9100
  • cAdvisor: http://IP:8082

Configurar Grafana (primer uso):

  1. Login en Grafana
  2. Configuration → Data Sources → Add Prometheus
  3. URL: http://prometheus:9090 → Save & Test
  4. Dashboards → Import → ID 1860 (Node Exporter Full) → Load
  5. Dashboards → Import → ID 193 (Docker containers) → Load

Stack 5: Vaultwarden (Gestor de contraseñas)

Implementación ligera de Bitwarden. Gestor de contraseñas self-hosted compatible con apps oficiales de Bitwarden.

Archivo: ~/docker/stacks/productivity/vaultwarden-compose.yml

version: '3.8'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    environment:
      - DOMAIN=https://vault.tudominio.com
      - SIGNUPS_ALLOWED=true  # Cambiar a false después de crear cuenta
      - ADMIN_TOKEN=admin_token_change_to_random_string
      - SMTP_HOST=smtp.gmail.com
      - SMTP_FROM=tuemail@gmail.com
      - SMTP_PORT=587
      - SMTP_SECURITY=starttls
      - SMTP_USERNAME=tuemail@gmail.com
      - SMTP_PASSWORD=tu_app_password
    volumes:
      - ~/docker/data/vaultwarden:/data
    ports:
      - "8083:80"
    restart: unless-stopped

networks:
  default:
    driver: bridge

Acceso: http://IP:8083

Panel admin: http://IP:8083/admin (usar ADMIN_TOKEN)

🔐 Seguridad CRÍTICA:

  • SIEMPRE exponer con HTTPS (Cloudflare Tunnel o reverse proxy con SSL)
  • Cambiar SIGNUPS_ALLOWED=false después de crear tu cuenta
  • Generar ADMIN_TOKEN fuerte: openssl rand -base64 48
  • Configurar SMTP para recuperación de contraseña
  • Hacer backups regulares de ~/docker/data/vaultwarden

Stack 6: Pi-hole + Unbound (DNS privado y bloqueador de ads)

Bloquea publicidad y trackers a nivel de red para todos tus dispositivos.

Archivo: ~/docker/stacks/network/docker-compose.yml

version: '3.8'

services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    environment:
      TZ: 'Europe/Madrid'
      WEBPASSWORD: 'admin_password_change_me'
      PIHOLE_DNS_: '127.0.0.1#5335'  # Usar Unbound
      DNSMASQ_LISTENING: 'all'
    volumes:
      - ~/docker/data/pihole/etc-pihole:/etc/pihole
      - ~/docker/data/pihole/etc-dnsmasq.d:/etc/dnsmasq.d
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8084:80/tcp"
    restart: unless-stopped
    networks:
      - dns
    depends_on:
      - unbound

  unbound:
    image: mvance/unbound:latest
    container_name: unbound
    volumes:
      - ~/docker/data/unbound:/opt/unbound/etc/unbound
    ports:
      - "5335:53/tcp"
      - "5335:53/udp"
    restart: unless-stopped
    networks:
      dns:
        ipv4_address: 172.20.0.2

networks:
  dns:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

Acceso: http://IP:8084/admin

Configuración post-instalación:

  1. Login con contraseña de WEBPASSWORD
  2. Settings → DNS → Upstream DNS servers → Verificar que apunta a Unbound (127.0.0.1#5335)
  3. Adlists → Añadir listas recomendadas (StevenBlack, OISD, etc.)
  4. Tools → Update Gravity
  5. En tu router: Cambiar DNS a IP del servidor Pi-hole

Stack 7: Home Assistant (Domótica completa)

Control total de tu smart home con integraciones ilimitadas.

Archivo: ~/docker/stacks/automation/homeassistant-compose.yml

version: '3.8'

services:
  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    container_name: homeassistant
    environment:
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/homeassistant/config:/config
      - /etc/localtime:/etc/localtime:ro
    network_mode: host
    restart: unless-stopped
    privileged: true

  # Opcional: MQTT Broker (para dispositivos Zigbee/WiFi)
  mosquitto:
    image: eclipse-mosquitto:latest
    container_name: mosquitto
    volumes:
      - ~/docker/data/mosquitto/config:/mosquitto/config
      - ~/docker/data/mosquitto/data:/mosquitto/data
      - ~/docker/data/mosquitto/log:/mosquitto/log
    ports:
      - "1883:1883"
      - "9001:9001"
    restart: unless-stopped

  # Opcional: Zigbee2MQTT (para dispositivos Zigbee)
  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:latest
    container_name: zigbee2mqtt
    environment:
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/zigbee2mqtt:/app/data
      - /run/udev:/run/udev:ro
    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0  # Adaptador Zigbee
    ports:
      - "8085:8080"
    restart: unless-stopped
    privileged: true

Acceso: http://IP:8123

Configuración mosquitto:

Crear ~/docker/data/mosquitto/config/mosquitto.conf:

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
listener 1883
allow_anonymous true
🔗 Integración con n8n:

Home Assistant tiene nodo oficial en n8n. Puedes crear automatizaciones que combinen:

  • Eventos de sensores → notificaciones Telegram
  • Webhooks externos → activar escenas en casa
  • Calendario → encender luces automáticamente

Stack 8: Portainer (Gestión visual de Docker)

Interfaz web para gestionar todos tus contenedores, stacks y volúmenes.

Archivo: ~/docker/stacks/management/docker-compose.yml

version: '3.8'

services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ~/docker/data/portainer:/data
    ports:
      - "9443:9443"
      - "9000:9000"
    restart: unless-stopped

Acceso: https://IP:9443

Funcionalidades destacadas:

  • Ver todos los contenedores, imágenes, volúmenes y redes
  • Iniciar/parar/reiniciar contenedores con un click
  • Ver logs en tiempo real
  • Acceder a terminal de contenedores
  • Gestionar stacks completos (equivalente a docker-compose)
  • Monitorización de recursos (CPU, RAM, red)
  • Actualizar imágenes con un click

Stack 9: Uptime Kuma (Monitorización de servicios)

Monitoriza disponibilidad de tus servicios y recibe alertas cuando caigan.

Archivo: ~/docker/stacks/monitoring/uptime-kuma-compose.yml

version: '3.8'

services:
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    volumes:
      - ~/docker/data/uptime-kuma:/app/data
    ports:
      - "3001:3001"
    restart: unless-stopped

Acceso: http://IP:3001

Tipos de monitorización:

  • HTTP(s): Verificar que web responde (status 200)
  • TCP Port: Verificar puerto abierto (SSH, bases de datos)
  • Ping: Verificar host alcanzable
  • DNS: Resolver nombres de dominio
  • Docker: Estado de contenedores específicos

Notificaciones soportadas: Telegram, Discord, Email, Slack, Webhook, Gotify, Pushover, Signal, y 90+ más.

Stack 10: Nginx Proxy Manager (Reverse Proxy visual)

Gestiona reverse proxies, SSL automático y acceso a servicios con dominios limpios.

Archivo: ~/docker/stacks/network/npm-compose.yml

version: '3.8'

services:
  nginx-proxy-manager:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    environment:
      DB_SQLITE_FILE: "/data/database.sqlite"
    volumes:
      - ~/docker/data/nginx-proxy-manager/data:/data
      - ~/docker/data/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
    ports:
      - "80:80"
      - "443:443"
      - "81:81"
    restart: unless-stopped

Acceso admin: http://IP:81

Credenciales por defecto:

  • Email: admin@example.com
  • Password: changeme
  • (Cambiar inmediatamente al primer login)

Ejemplo de configuración:

Para acceder a Jellyfin mediante https://jellyfin.tudominio.com:

  1. Hosts → Proxy Hosts → Add Proxy Host
  2. Domain Names: jellyfin.tudominio.com
  3. Scheme: http
  4. Forward Hostname/IP: jellyfin (nombre del contenedor)
  5. Forward Port: 8096
  6. SSL → Request SSL Certificate → Force SSL
  7. Save
💡 Requisitos para SSL automático:

  • Tener dominio propio
  • Apuntar DNS A record a tu IP pública
  • Puertos 80 y 443 abiertos en router (port forward)
  • NPM gestionará certificados Let’s Encrypt automáticamente

Stack 11: Audiobookshelf (Biblioteca de audiolibros y podcasts)

Gestiona y escucha tu colección de audiolibros y podcasts con apps móviles nativas.

Archivo: ~/docker/stacks/media/audiobookshelf-compose.yml

version: '3.8'

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Madrid
    volumes:
      - ~/docker/data/audiobookshelf/config:/config
      - ~/docker/data/audiobookshelf/metadata:/metadata
      - ~/docker/media/audiobooks:/audiobooks
      - ~/docker/media/podcasts:/podcasts
    ports:
      - "13378:80"
    restart: unless-stopped

Acceso: http://IP:13378

Características destacadas:

  • Reproductor web con velocidad variable
  • Apps nativas iOS/Android
  • Seguimiento automático de progreso
  • Metadata automática (covers, descripciones, autores)
  • Gestión de podcasts con suscripciones
  • Multi-usuario con progreso independiente

Stack 12: Stirling-PDF (Herramientas PDF todo en uno)

Suite completa de herramientas PDF self-hosted: merge, split, OCR, comprimir, convertir…

Archivo: ~/docker/stacks/productivity/stirling-pdf-compose.yml

version: '3.8'

services:
  stirling-pdf:
    image: frooodle/s-pdf:latest
    container_name: stirling-pdf
    environment:
      - DOCKER_ENABLE_SECURITY=false  # true para login obligatorio
      - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=true
      - LANGS=es_ES
    volumes:
      - ~/docker/data/stirling-pdf/trainingData:/usr/share/tesseract-ocr/5/tessdata
      - ~/docker/data/stirling-pdf/extraConfigs:/configs
      - ~/docker/data/stirling-pdf/logs:/logs
    ports:
      - "8086:8080"
    restart: unless-stopped

Acceso: http://IP:8086

Operaciones disponibles (40+):

Categoría Funciones
General Merge, Split, Rotate, Reorder, Extract pages
Conversión PDF ↔ Images, Word, Excel, PowerPoint, HTML
Seguridad Add/Remove password, Permisos, Firmas digitales
OCR Extraer texto de PDFs escaneados (30+ idiomas)
Compresión Reducir tamaño, Optimizar para web
Edición Add watermark, Header/Footer, Page numbers

Alternativa 100% local a: Adobe Acrobat, SmallPDF, iLovePDF, PDF24.

Gestión avanzada de stacks

Comandos esenciales

# Levantar stack
cd ~/docker/stacks/media
docker compose up -d

# Ver logs de stack completo
docker compose logs -f

# Ver logs de servicio específico
docker compose logs -f jellyfin

# Parar stack sin eliminar contenedores
docker compose stop

# Parar y eliminar contenedores (mantiene volúmenes)
docker compose down

# Eliminar todo incluyendo volúmenes (PELIGRO: pierdes datos)
docker compose down -v

# Actualizar imágenes y recrear contenedores
docker compose pull
docker compose up -d

# Ver estado de servicios
docker compose ps

# Reiniciar servicio específico
docker compose restart jellyfin

# Ejecutar comando en contenedor
docker compose exec jellyfin bash

Variables de entorno con archivos .env

Para no hardcodear contraseñas, usa archivo .env junto al docker-compose.yml:

Ejemplo: ~/docker/stacks/automation/.env

POSTGRES_PASSWORD=mi_password_super_segura
N8N_ENCRYPTION_KEY=gQKxvVHm8fJ2nL5pR9sT3wX7yZ1aC4eF
WEBHOOK_URL=https://n8n.midominio.com/

Uso en docker-compose.yml:

services:
  postgres:
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  n8n:
    environment:
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      WEBHOOK_URL: ${WEBHOOK_URL}
🔒 Seguridad con .env:

  • Añadir .env a .gitignore (nunca commitear secretos)
  • Crear .env.example con variables vacías como plantilla
  • Permisos restrictivos: chmod 600 .env

Gestión de redes entre stacks

Para que servicios de diferentes stacks se comuniquen, crea red externa:

# Crear red compartida
docker network create homelab

# En cada docker-compose.yml que necesite comunicarse:
networks:
  default:
    name: homelab
    external: true

Ejemplo: n8n comunicándose con Jellyfin

Ambos stacks usan network: homelab → n8n puede hacer requests a http://jellyfin:8096

Backups automatizados

Estrategia recomendada para proteger datos de tu homelab:

Script de backup diario

Archivo: ~/docker/backup.sh

#!/bin/bash

# Backup automático de volúmenes Docker
BACKUP_DIR=~/docker/backups
DATE=$(date +%Y%m%d_%H%M%S)

# Crear directorio de backup del día
mkdir -p "$BACKUP_DIR/$DATE"

# Servicios críticos a respaldar
SERVICES=(
  "nextcloud"
  "vaultwarden"
  "n8n"
  "grafana"
  "homeassistant"
)

echo "🔄 Iniciando backup: $DATE"

for service in "${SERVICES[@]}"; do
  echo "📦 Backup de $service..."

  # Parar servicio (opcional, recomendado para consistencia)
  cd ~/docker/stacks/*/ 2>/dev/null
  docker compose stop "$service" 2>/dev/null

  # Comprimir volumen
  tar -czf "$BACKUP_DIR/$DATE/${service}.tar.gz" \
    -C ~/docker/data "$service"

  # Reiniciar servicio
  docker compose start "$service" 2>/dev/null

  echo "✅ $service respaldado"
done

# Comprimir también docker-compose.yml de cada stack
echo "📝 Backup de configuraciones..."
tar -czf "$BACKUP_DIR/$DATE/stacks-config.tar.gz" \
  -C ~/docker stacks

# Eliminar backups antiguos (mantener últimos 7 días)
find "$BACKUP_DIR" -type d -mtime +7 -exec rm -rf {} +

echo "✅ Backup completado: $BACKUP_DIR/$DATE"
echo "📊 Tamaño total: $(du -sh $BACKUP_DIR/$DATE | cut -f1)"

Hacer ejecutable:

chmod +x ~/docker/backup.sh

Programar con cron (diario a las 3am):

crontab -e

# Añadir línea:
0 3 * * * /home/usuario/docker/backup.sh >> /home/usuario/docker/backup.log 2>&1

Backup offsite con rclone

Para copias de seguridad en la nube (Google Drive, Dropbox, B2, S3…):

# Instalar rclone
curl https://rclone.org/install.sh | sudo bash

# Configurar remote (ejemplo: Google Drive)
rclone config

# Subir backups a la nube
rclone sync ~/docker/backups remote:homelab-backups \
  --progress \
  --checksum \
  --log-file ~/docker/rclone.log

Monitorización de recursos

Ver consumo en tiempo real

# Stats de todos los contenedores
docker stats

# Stats de stack específico
cd ~/docker/stacks/media
docker compose ps -q | xargs docker stats

# Ver top 5 contenedores por CPU
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}" | sort -k2 -rh | head -6

# Ver top 5 contenedores por RAM
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}" | sort -k2 -rh | head -6

Limpiar recursos no utilizados

# Ver espacio usado por Docker
docker system df

# Eliminar contenedores parados
docker container prune -f

# Eliminar imágenes sin usar
docker image prune -a -f

# Eliminar volúmenes huérfanos (CUIDADO)
docker volume prune -f

# Limpiar todo (contenedores parados, imágenes sin usar, redes, cache)
docker system prune -a --volumes -f

# Cleanup semanal automatizado (cron)
0 4 * * 0 docker system prune -a -f >> /var/log/docker-cleanup.log 2>&1

Troubleshooting común

Problema 1: «Cannot connect to the Docker daemon»

Síntoma: docker compose up falla con error de conexión al daemon.

Soluciones:

# Verificar que Docker está corriendo
sudo systemctl status docker

# Iniciar Docker si está parado
sudo systemctl start docker

# Añadir usuario al grupo docker (logout/login después)
sudo usermod -aG docker $USER

# Verificar permisos de socket
sudo chmod 666 /var/run/docker.sock

Problema 2: Contenedor reinicia constantemente

Síntoma: docker compose ps muestra contenedor en estado «Restarting».

Diagnóstico:

# Ver logs del contenedor
docker compose logs jellyfin

# Ver últimas 50 líneas en tiempo real
docker compose logs -f --tail=50 jellyfin

# Causas comunes:
# - Permisos incorrectos en volúmenes (PUID/PGID)
# - Puerto ya en uso
# - Variable de entorno faltante/incorrecta
# - Dependencia no disponible

Solución permisos:

# Verificar UID/GID de tu usuario
id

# Ejemplo output: uid=1000(usuario) gid=1000(usuario)

# Asegúrate que PUID=1000 y PGID=1000 en docker-compose.yml

# Arreglar permisos de volúmenes
sudo chown -R 1000:1000 ~/docker/data/jellyfin

Problema 3: «Port is already allocated»

Síntoma: Error al iniciar stack porque puerto ya está en uso.

Diagnóstico:

# Ver qué está usando puerto 8096
sudo lsof -i :8096

# O con netstat
sudo netstat -tulpn | grep 8096

# Ver todos los puertos usados por Docker
docker ps --format "table {{.Names}}\t{{.Ports}}"

Soluciones:

  1. Cambiar puerto externo en docker-compose.yml: ports: - "8097:8096"
  2. Parar servicio conflictivo: sudo systemctl stop apache2
  3. Eliminar contenedor duplicado: docker rm -f contenedor_duplicado

Problema 4: Contenedor no puede acceder a internet

Síntoma: Servicios no pueden descargar actualizaciones o acceder a APIs externas.

Diagnóstico:

# Test DNS dentro del contenedor
docker compose exec jellyfin ping -c 3 google.com

# Verificar configuración DNS del contenedor
docker compose exec jellyfin cat /etc/resolv.conf

Solución:

Añadir DNS públicos en docker-compose.yml:

services:
  jellyfin:
    dns:
      - 8.8.8.8
      - 1.1.1.1

Problema 5: Volumen lleno / Sin espacio en disco

Síntoma: Errores de escritura, contenedores fallan al guardar datos.

Diagnóstico:

# Ver espacio en disco
df -h

# Ver tamaño de directorios Docker
du -sh ~/docker/data/*

# Ver espacio usado por Docker
docker system df -v

Soluciones:

# Limpiar logs antiguos de contenedores
truncate -s 0 /var/lib/docker/containers/*/*-json.log

# Limitar tamaño de logs (añadir a docker-compose.yml)
services:
  jellyfin:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

# Limpiar imágenes y contenedores no usados
docker system prune -a --volumes -f

Problema 6: Cambios en docker-compose.yml no se aplican

Síntoma: Modificas docker-compose.yml pero contenedor mantiene configuración antigua.

Solución:

# Recrear contenedores (mantiene volúmenes)
docker compose up -d --force-recreate

# Si cambias imagen o build context
docker compose up -d --build

# Borrar todo y empezar de cero
docker compose down
docker compose up -d

Mejores prácticas

Seguridad

  • Nunca usar latest en producción → Pin a versiones específicas: jellyfin:10.8.13
  • Usar .env para secretos → No hardcodear contraseñas en .yml
  • Cambiar credenciales por defecto → admin/admin es puerta abierta
  • Red bridge aislada por stack → No expongas todo a network_mode: host
  • Reverse proxy con SSL → Nginx Proxy Manager o Cloudflare Tunnel
  • Firewall activoufw allow 80/tcp && ufw enable
  • Actualizaciones regularesdocker compose pull && up -d semanal
  • Backups automáticos → Cron diario + offsite semanal

Performance

  • Limitar recursos: Evita que un contenedor consuma todo
services:
  jellyfin:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          memory: 2G
  • Healthchecks: Restart automático si servicio falla
services:
  jellyfin:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  • Logs rotativos: Evita llenar disco con logs
services:
  jellyfin:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"

Mantenibilidad

  • Nombres descriptivos de serviciosjellyfin mejor que media1
  • Comentarios en .yml → Documenta variables críticas
  • Estructura organizada → Un stack por categoría de servicios
  • Versionar en git → Historial de cambios en configuración
  • README por stack → Documenta propósito y configuración especial

Siguientes pasos

Ahora que tienes homelab completo con Docker Compose, considera:

  1. Migrar a Kubernetes (k3s) si necesitas orquestación avanzada (50+ contenedores)
  2. Implementar CI/CD con Gitea + Drone para auto-deploy de stacks
  3. Monitorización avanzada con Loki + Promtail para logs centralizados
  4. Alta disponibilidad con Docker Swarm (múltiples nodos)
  5. Integración n8n para automatizar gestión de contenedores (webhooks de actualizaciones)
  6. VPN permanente con WireGuard para acceso seguro remoto
  7. Storage distribuido con GlusterFS/Ceph para redundancia de datos

Recursos adicionales

Conclusión

Docker Compose transforma la complejidad de gestionar múltiples servicios en archivos YAML simples y reproducibles. Con los 12 stacks de esta guía tienes:

  • 🎬 Media center completo con descarga automática
  • 🤖 Automatización potente con n8n
  • ☁️ Nube privada con Nextcloud
  • 📊 Monitorización profesional con Grafana
  • 🔒 Gestor de contraseñas auto-hospedado
  • 🏠 Domótica con Home Assistant
  • 🛠️ Gestión visual con Portainer
  • 🌐 Reverse proxy con SSL automático

Todo con comandos simples, configuración versionable y recuperación rápida ante fallos.

💪 Tu homelab está listo para crecer:

Copia, pega, modifica. Estos stacks son punto de partida probado en producción. Ajusta a tus necesidades, añade servicios nuevos y comparte tus mejoras con la comunidad.

¡Bienvenido al mundo del self-hosting profesional! 🚀

Por ziru

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

El Diario IA
Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.