Problema

En entornos homelab donde TrueNAS actúa como nodo de almacenamiento y también ejecuta contenedores Docker (a través de Dockhand o plugins), es frecuente querer exponer varios servicios mediante un único punto de entrada. El objetivo suele ser:

  1. Un reverse proxy que escuche en 80/443 en una IP dedicada.
  2. Certificados TLS válidos tanto para acceso externo (Let’s Encrypt) como para tráfico interno LAN, evitando que los dispositivos locales consuman ancho de banda de la nube.
  3. Resolución DNS interna que apunte los sub‑dominios al alias IP del proxy, sin interferir con la red de la ISP.
  4. Integración con herramientas de hardening (crowdsec, geoblocking, Suricata).

El bloqueo típico ocurre cuando el contenedor del proxy no puede unirse a la red que TrueNAS ha configurado (bridge, macvlan o alias). El error se manifiesta como “network not found” o “port already allocated”, y la UI de Dockhand muestra que el contenedor está detenido. Sin una red adecuada, los certificados ACME no pueden validar dominios y la resolución DNS interna no funciona.

Causa

Los fallos más comunes provienen de tres áreas:

  1. Modelo de red de Docker

    • bridge predeterminado no permite que el contenedor escuche en una IP distinta a la del host.
    • host elimina el aislamiento de puertos pero impide asignar una IP alias.
    • macvlan necesita una sub‑red libre en el switch y la VLAN correcta; si el switch (UniFi) no permite tráfico entre la VLAN del host y la del contenedor, los paquetes se pierden.
  2. Asignación de IP alias en TrueNAS

    • La IP adicional debe estar configurada en la interfaz física y marcada como “secondary”.
    • Si la máscara o la puerta de enlace no coinciden con la sub‑red del contenedor, el tráfico nunca llega al proxy.
  3. Certificados y ACME challenge

    • Cuando el contenedor está aislado, el puerto 80/443 que Let’s Encrypt necesita para el HTTP‑01 challenge no está expuesto al exterior.
    • En LAN, los clientes pueden rechazar certificados auto‑firmados si el nombre del CN no coincide con la resolución DNS interna.

Solución

1. Preparar la interfaz de red en TrueNAS

  1. Accede a Network → Interfaces.
  2. Selecciona la NIC que usa TrueNAS y crea una Alias con la IP que reservarás para el proxy (ej. 192.168.10.10/24).
  3. Aplica y verifica con ping 192.168.10.10 desde otro equipo de la LAN.

2. Crear una red Docker macvlan

El macvlan permite que el contenedor tenga su propia MAC e IP dentro de la sub‑red LAN, evitando colisiones con el host.

docker network create -d macvlan \
  --subnet=192.168.10.0/24 \
  --gateway=192.168.10.1 \
  -o parent=eth0 \
  lan_proxy
  • eth0 es la interfaz física de TrueNAS; ajústala si usas otro nombre.
  • La sub‑red debe coincidir con la de la IP alias.

3. Deploy de Traefik (ejemplo) dentro de Dockhand

En Dockhand, crea una nueva aplicación y pega el siguiente docker-compose.yml. Usa la red lan_proxy y asigna la IP alias al contenedor.

version: "3.8"
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "[email protected]"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
    ports: []   # No se exponen puertos en modo host
    networks:
      lan_proxy:
        ipv4_address: 192.168.10.10
    volumes:
      - /mnt/pool/docker/traefik/letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

networks:
  lan_proxy:
    external: true

Puntos clave

  • ports: [] evita conflicto con la IP alias; el tráfico llega directamente a la MAC del contenedor.
  • ipv4_address fija la IP del proxy a la alias configurada.
  • El volumen letsencrypt persiste los certificados entre reinicios.
  • --api.insecure solo para pruebas; en producción habilita autenticación o usa el dashboard protegido.

4. Alternativa con Caddy (más simple para TLS automático)

version: "3.8"
services:
  caddy:
    image: caddy:latest
    container_name: caddy
    networks:
      lan_proxy:
        ipv4_address: 192.168.10.10
    volumes:
      - /mnt/pool/docker/caddy/Caddyfile:/etc/caddy/Caddyfile
      - /mnt/pool/docker/caddy/data:/data
    restart: unless-stopped

networks:
  lan_proxy:
    external: true

Caddyfile básico:

{
    email [email protected]
    acme_ca https://acme-v02.api.letsencrypt.org/directory
}

*.example.com {
    reverse_proxy * http://192.168.10.20:3000
}

Caddy gestiona automáticamente los desafíos HTTP‑01 usando la IP del contenedor, sin necesidad de puertos expuestos.

5. Configurar DNS interno en UniFi

  1. En el controlador UniFi, abre Settings → Networks → DNS.
  2. Añade una entrada estática: *.example.com → 192.168.10.10.
  3. Asegúrate de que la VLAN donde está el alias IP tenga “Enable DNS Override” activo.

Con esto, cualquier sub‑dominio resuelve a la IP del proxy sin pasar por el DNS público.

6. Hardening básico

  • CrowdSec: ejecuta como contenedor independiente y enlázalo a la red lan_proxy. Configura el bouncers para que Traefik/Caddy lean la lista de IP bloqueadas y añadan reglas IPBlock.
  • Suricata en UniFi: crea una regla que bloquee tráfico a 192.168.10.10 fuera de la VLAN de gestión, evitando que dispositivos comprometidos usen el proxy como pivot.
  • TLS: habilita HSTS y TLS 1.3 en los entrypoints de Traefik (--entrypoints.websecure.http.tls.minVersion=VersionTLS13).
  • Firewall en TrueNAS: permite tráfico solo desde la sub‑red LAN a la IP alias, bloquea todo lo demás.

Cuándo aplicar esta solución

  • Entorno homelab con TrueNAS como host Docker y necesidad de exponer varios servicios bajo sub‑dominios.
  • Ancho de banda limitado en la nube; se busca que la LAN sirva contenido estático y backups.
  • Requerimientos de TLS tanto externos (Let’s Encrypt) como internos (certificados auto‑firmados).
  • Política de aislamiento: se prefiere que el proxy no comparta la pila de red del host para evitar conflictos de puertos.

No es recomendable cuando:

  • La infraestructura de red no permite macvlan (por ejemplo, switches que filtran MAC desconocidas).
  • Se necesita que el proxy acceda a recursos de la red del host mediante localhost. En ese caso, usar host network con reglas de iptables específicas es más sencillo.

Código

# 1. Crear red macvlan
docker network create -d macvlan \
  --subnet=192.168.10.0/24 \
  --gateway=192.168.10.1 \
  -o parent=eth0 \
  lan_proxy

# 2. Deploy de Traefik con Docker Compose (guarda como docker-compose.yml)
cat > /mnt/pool/docker/traefik/docker-compose.yml <<'EOF'
version: "3.8"
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "[email protected]"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
    ports: []
    networks:
      lan_proxy:
        ipv4_address: 192.168.10.10
    volumes:
      - /mnt/pool/docker/traefik/letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

networks:
  lan_proxy:
    external: true
EOF

# 3. Levantar el stack
docker compose -f /mnt/pool/docker/traefik/docker-compose.yml up -d

Verificación

  1. Ping la IP del proxy desde otro equipo: ping 192.168.10.10.
  2. Accede a http://example.com y verifica que el dashboard de Traefik (o Caddy) responde.
  3. Usa curl -I https://example.com y confirma que el certificado es válido (Let’s Encrypt) o que el certificado auto‑firmado se muestra sin errores de nombre.
  4. En UniFi, revisa la tabla de DNS estático y confirma que sub.example.com resuelve a 192.168.10.10.
  5. Revisa los logs de Traefik (docker logs traefik) para asegurarte de que los desafíos ACME se completaron sin errores.
  6. Simula un bloqueo con CrowdSec y verifica que la IP bloqueada recibe un 403 del proxy.

Notas adicionales

  • Persistencia de datos: siempre monta volúmenes externos para /letsencrypt (Traefik) o /data (Caddy); de lo contrario perderás los certificados al reiniciar el contenedor.
  • MTU: en algunos switches macvlan necesita una MTU menor (1500 → 1492) para evitar fragmentación; ajusta con `–opt com