- Traefik actúa como proxy inverso dinámico y resuelve certificados TLS, mientras Portainer ofrece una interfaz gráfica para gestionar Docker y Swarm.
- La combinación de traefik.yml, dynamic.yml y docker-compose.yml permite definir entrypoints, middlewares de seguridad y enrutado HTTP/HTTPS basado en labels.
- En escenarios Docker standalone o Swarm, basta con unir servicios a la red proxy y etiquetarlos para que Traefik los descubra y exponga de forma segura.
- El uso de Let’s Encrypt, redes dedicadas y autenticación básica en el dashboard consolida un entorno robusto para desplegar múltiples aplicaciones en un solo servidor.

Montar una pila moderna con Docker, Traefik y Portainer es, a día de hoy, una de las formas más cómodas de gestionar contenedores en un único servidor y exponer múltiples servicios con HTTPS sin volverte loco con la configuración. La gracia está en que Traefik actúa como proxy inverso dinámico, mientras que Portainer te da una interfaz web muy sencilla para administrar Docker y, si quieres, también Swarm.
En este artículo vas a ver cómo encajar Traefik como proxy inverso delante de Portainer tanto en un escenario Docker “clásico” (standalone) como en un clúster Docker Swarm, cómo integrar TLS (autofirmado o Let’s Encrypt), qué ficheros de configuración entran en juego (traefik.yml, dynamic.yml y docker-compose.yml) y algunos trucos habituales con middlewares, redes y problemas típicos de etiquetas en Traefik.
Qué roles tienen Docker, Traefik y Portainer en la arquitectura
En este tipo de setup, Docker es la capa base donde se ejecutan los contenedores, Traefik hace de proxy inverso y balanceador que recibe todas las peticiones HTTP/HTTPS en los puertos 80 y 443 y las envía al contenedor adecuado, y Portainer proporciona una interfaz gráfica para administrar imágenes, contenedores, volúmenes, redes y stacks de Docker o Swarm.
La idea es tener un único host con Docker donde Traefik se sitúa en la “puerta de entrada”: escucha en los entrypoints web (80) y websecure (443), resuelve certificados TLS (normalmente con Let’s Encrypt) y, gracias a la integración con el provider Docker, va descubriendo automáticamente los servicios que expones añadiendo labels en los contenedores.
Por otro lado, Portainer corre como un contenedor más, pero expuesto a través de un subdominio específico, por ejemplo portainer.tudominio.com, protegido por TLS y, si quieres, tras un middleware de autenticación adicional al login propio de Portainer. Todo esto se orquesta con un docker-compose que define Traefik, Portainer y la red común de tipo proxy.
Una ventaja clara de este enfoque es que puedes desplegar nuevos servicios simplemente añadiendo labels en sus contenedores: Traefik detecta el cambio en Docker, crea routers, servicios y middlewares en caliente, y en segundos tienes un nuevo subdominio o ruta funcionando sin tocar manualmente archivos de configuración estática.
Configuración estática de Traefik: traefik.yml
La configuración estática de Traefik suele ir en un archivo como traefik.yml, que se monta dentro del contenedor. Aquí se define el dashboard, los entrypoints, los providers y el resolver de certificados Let’s Encrypt, entre otras cosas.
Una configuración típica en YAML arranca activando la API y el panel de control con algo como api: dashboard: true, lo que permite acceder a la interfaz web donde ves routers, servicios y middlewares. Este panel, por defecto, no está autenticado, así que más adelante veremos cómo protegerlo con basic auth desde la configuración dinámica.
Después se definen los entrypoints. Lo normal es tener un entrypoint web en el puerto 80 y otro websecure en el 443. En el web se suele configurar una redirección automática a websecure, de forma que cualquier acceso por HTTP se reenvíe a HTTPS:
Un esquema típico incluiría una sección http.redirections.entryPoint.to: websecure para forzar esa redirección y centralizar todo el tráfico cifrado en el entrypoint seguro.
Para websecure se indica que debe usar TLS y se enlaza un certResolver (por ejemplo letsencrypt) que será el encargado de gestionar automáticamente los certificados. Todo esto se apoya en ACME, que es el protocolo que usa Let’s Encrypt para emitir y renovar certificados.
En el bloque providers se configura el provider Docker, apuntando al socket unix:///var/run/docker.sock, con una opción clave: exposedByDefault: false. Con esto, Traefik no publica por defecto todos los contenedores; solo aquellos que lleven la label traefik.enable=true. También se puede configurar un provider de archivos (file) para cargar una configuración dinámica adicional desde, por ejemplo, /configurations/dynamic.yml.
Por último, el archivo estático suele incluir la sección certificatesResolvers, donde se define el resolver ACME: correo electrónico de registro, fichero de almacenamiento (normalmente acme.json), tipo de clave (por ejemplo EC384) y el método de validación (por ejemplo httpChallenge sobre el entrypoint web). Es importante que configures un email real en esta sección porque Let’s Encrypt lo utiliza para avisos y renovaciones; si dejas un valor ficticio o mal formado puedes encontrarte errores al arrancar el contenedor de Traefik.
Además de esta configuración en YAML, hay despliegues que utilizan formato TOML (traefik.toml), pero la lógica es la misma: seccionan certificados resolvers (por ejemplo [certificatesResolvers.main.acme]), definen challenges (como dnsChallenge con provider = "route53") y añaden opciones TLS por defecto, como forzar minVersion = "VersionTLS13" o configurar una lista de cipherSuites permitidos.
Configuración dinámica: middlewares, TLS y seguridad
La parte dinámica de Traefik suele ir en un archivo como dynamic.yml, cargado por el provider de archivos. Aquí defines middlewares y opciones TLS que se pueden modificar sin reiniciar el contenedor de Traefik, lo cual viene muy bien para ajustar seguridad sobre la marcha.
Un caso típico es crear un middleware de cabeceras, por ejemplo secureHeaders, que añada políticas relacionadas con HSTS y el uso obligatorio de HTTPS. En YAML podrías encontrar parámetros como sslRedirect: true, forceSTSHeader: true, stsIncludeSubdomains: true, stsPreload: true y un stsSeconds elevado (por ejemplo un año en segundos). Con esto se refuerza la seguridad del tráfico desde el lado del navegador.
Otro middleware habitual es uno de autenticación básica, por ejemplo user-auth de tipo basicAuth, donde se declara una lista de usuarios en formato usuario:hash. El hash se suele generar con htpasswd y se incrusta en el archivo. Este middleware se suele asociar al router del dashboard de Traefik para que, además del acceso HTTPS, exija usuario y contraseña.
Dentro de la misma configuración dinámica es frecuente ver un bloque tls.options con un perfil por defecto (default) donde se fijan los cifrados soportados y la versión mínima de TLS, por ejemplo VersionTLS12. En algunos despliegues se definen varias opciones (por ejemplo un perfil llamado tls12 y otro default) para adaptar el nivel de seguridad a distintos servicios si fuera necesario.
En entornos donde se quiere usar TLS también de forma interna entre Traefik y los servicios backend (como Portainer), puede surgir el problema de que esos servicios no cuenten con un certificado reconocido por una CA pública. Ahí entra en juego la opción insecureSkipVerify, que, configurada bajo providers.http.tls o en las opciones TLS apropiadas, permite que Traefik no valide el certificado del backend. Es útil para entornos de laboratorio o certificados autofirmados internos, pero conviene usarlo con cuidado porque relaja la verificación.
Un detalle práctico: el archivo dynamic.yml se puede editar en caliente. Traefik recarga la configuración dinámica sin necesidad de reinicio, así puedes añadir o quitar middlewares, cambiar las cabeceras de seguridad o modificar la autenticación básica sin afectar a la disponibilidad del proxy.
docker-compose con Traefik y Portainer en Docker standalone
En un escenario Docker “a pelo”, sin Swarm, lo más habitual es levantar Traefik y Portainer con un único docker-compose.yml que define ambos servicios y una red compartida de tipo bridge llamada, por ejemplo, proxy.
El servicio Traefik suele usar la imagen oficial, por ejemplo traefik:latest o una versión concreta como traefik:v3.4. Se le mapean los puertos 80 y 443 para el tráfico web y, en algunos ejemplos, el 8080 para exponer el panel de control internamente. Además se le añade la opción restart: unless-stopped y se refuerza la seguridad con security_opt: - no-new-privileges:true para reducir riesgos de escalada de privilegios dentro del contenedor.
En cuanto a volúmenes, Traefik necesita montar el socket de Docker de solo lectura (/var/run/docker.sock:/var/run/docker.sock:ro) para descubrir contenedores, el archivo estático traefik.yml y el directorio donde guardará la configuración dinámica (./traefik-data/configurations:/configurations). También se monta el fichero acme.json, que es donde Traefik almacena los certificados emitidos por Let’s Encrypt. Este archivo se crea en el host y se le ponen permisos 600 para que solo lo pueda leer y escribir el usuario adecuado; si lo dejas con 644, Traefik suele quejarse.
La clave para que Traefik haga su magia son las labels del servicio. Entre otras, encontrarás: traefik.enable=true (para que el provider Docker exponga este servicio), traefik.docker.network=proxy (indica qué red debe usar Traefik para hablar con el contenedor), y un router como traefik.http.routers.traefik-secure, con labels que definen su entrypoint (websecure), la regla de host (por ejemplo Host(`traefik.tudominio.com`)), el servicio asociado (api@internal) y el middleware de autenticación (user-auth@file).
El servicio de Portainer en el mismo docker-compose.yml usa generalmente portainer/portainer-ce:latest (o la edición Enterprise si tienes licencia). Se montan volúmenes similares: el socket de Docker, la carpeta de datos (./portainer-data:/data) y, a menudo, también el /etc/localtime para que herede la zona horaria del host.
Las labels de Portainer lo registran ante Traefik: traefik.enable=true, traefik.docker.network=proxy, un router del tipo traefik.http.routers.portainer-secure con entrypoint websecure y una regla de host como Host(`portainer.tudominio.com`). Además, se define el servicio que Traefik debe usar apuntando al puerto interno de Portainer, por ejemplo con traefik.http.services.portainer.loadbalancer.server.port=9000.
Con todo esto en marcha, tras lanzar docker compose up -d en el directorio del stack, Traefik descarga las imágenes, crea la red proxy si está declarada como externa y expone los dos paneles en los subdominios configurados, con HTTPS automático gracias a Let’s Encrypt. Desde ese momento, añadir nuevos servicios es cuestión de clonar su proyecto, añadirle unas pocas labels de Traefik y conectarlo a la red proxy.
Escenario Docker Swarm: Portainer y Traefik en clúster
Cuando activas Docker Swarm la película cambia un poco, porque los servicios se despliegan como stacks y las redes suelen ser de tipo overlay. Sin embargo, la lógica base con Traefik y Portainer es muy parecida: Traefik sigue actuando de proxy inverso y descubridor dinámico de servicios, y Portainer puede funcionar tanto en modo standalone como en modo Portainer + agent para gestionar nodos remotos dentro del clúster.
En un despliegue Swarm típico, antes de aplicar el stack se crean dos redes overlay que usará Traefik para enrutar tráfico entre nodos. Además, se crea un volumen para persistir los datos de Portainer (por ejemplo portainer_data) y, si lo necesitas, uno para acme.json o el directorio donde Traefik guarda certificados y configuración.
El docker-compose.yml (que luego se despliega con docker stack deploy usando, por ejemplo, -c portainer.yml) suele definir dos servicios: Portainer agent, que corre en modo global en todos los nodos Linux, y el servicio principal de Portainer (CE o EE), que normalmente se queda en modo replicado con una constraint para que solo se ejecute en managers.
El Portainer agent monta el socket de Docker y el directorio de volúmenes (/var/lib/docker/volumes) para poder inspeccionar recursos. Se conecta a la red overlay que compartirá con Traefik, por ejemplo traefik_public. El servicio principal de Portainer se puede configurar para hablar con el agent a través de TCP (tcp://tasks.agent:9001) y, de hecho, en algunos ejemplos se muestra ese comando con --tlsskipverify comentado, para omitir la verificación TLS si hiciera falta.
En el mismo stack o en otro, Traefik se levanta con el provider Docker en modo Swarm (swarmMode = true en TOML o la opción equivalente en flags), observando los servicios etiquetados. Su configuración incluye un certificatesResolvers.main.acme para Let’s Encrypt, un dnsChallenge con proveedor como Route53 si se quiere validar certificados por DNS, y un bloque de tls.options donde, por ejemplo, se fuerza minVersion = "VersionTLS13" para el perfil por defecto y se define una lista explícita de cipherSuites para un perfil TLS 1.2.
Las labels de Portainer en Swarm siguen la misma filosofía: traefik.enable=true, un router como traefik.http.routers.portainer con Host(`portainer.midominio.com`), traefik.http.routers.portainer.entrypoints=https (o websecure según el naming), y traefik.http.routers.portainer.tls=true para activar TLS en ese router. Además se puede especificar el certresolver con traefik.http.routers.portainer.tls.certresolver=main y el esquema del backend si el servicio habla HTTPS internamente con traefik.http.services.portainer.loadbalancer.server.scheme=https.
En los logs de debug de Traefik en Swarm vas a ver la configuración generada: routers como api y portainer, servicios descubridos (por ejemplo portainer en http://10.0.1.12:9000 y un servicio traefik apuntando al puerto 8080 del dashboard), y, si hay algún problema con labels mal definidas, mensajes del estilo «Could not define the service name for the router: too many services», que suelen indicar que hay conflicto o duplicidad de servicios asociados a un router.
Ejemplo mínimo: whoami, rutas y TLS con Traefik
Antes de meter a Portainer en la ecuación, es bastante práctico probar Traefik con un backend muy sencillo, como la imagen traefik/whoami, que devuelve información sobre la petición. Esto te sirve para validar que Traefik está bien configurado y que las reglas de enrutado funcionan.
Un docker-compose.yml básico puede incluir el servicio traefik con la imagen traefik:v3.4, montando el socket Docker y definiendo los flags de arranque como --providers.docker=true, --providers.docker.exposedbydefault=false, --providers.docker.network=proxy y el entrypoint --entryPoints.web.address=:80. Los puertos 80 y 8080 se publican en el host, y se crea una red proxy que usará también el contenedor whoami.
El servicio whoami se arranca en la misma red y se marca con traefik.enable=true. Luego se define un router con la label traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`) y se indica que su entrypoint es web. Si haces una petición con curl -H "Host: whoami.docker.localhost" http://localhost/, verás una respuesta con hostname, IPs, cabeceras y demás, lo cual confirma que Traefik está enrutando bien.
A partir de ahí, es sencillo añadir routing por paths. Por ejemplo, puedes crear otro servicio whoami-api con la misma imagen, en la misma red, y etiquetarlo con una regla del tipo Host(`whoami.docker.localhost`) && PathPrefix(`/api`). Así, la raíz / irá al whoami original y las peticiones a /api las servirá whoami-api. Puedes verificarlo haciendo peticiones a / y a /api con el mismo host.
Para activar TLS en este entorno local, una opción es generar un certificado autofirmado para el dominio *.docker.localhost con openssl. Creas un directorio certs, generas local.key y local.crt, y luego añades un archivo dinámico (por ejemplo dynamic/tls.yml) con la sección tls: y las rutas certFile y keyFile apuntando a esos ficheros.
Después, actualizas la definición del servicio Traefik en docker-compose.yml para montar la carpeta ./certs y la carpeta ./dynamic dentro del contenedor, activas la API segura con --api.dashboard=true, creas el entrypoint websecure (--entryPoints.websecure.address=:443) y habilitas TLS ahí con --entryPoints.websecure.http.tls=true. También etiquetas el router del dashboard y los routers de whoami para que usen websecure y tengan tls=true.
Una vez levantado, podrás acceder a https://whoami.docker.localhost/ y al panel de Traefik con un certificado autofirmado, aceptando el aviso de seguridad del navegador. Esta experiencia es muy parecida a lo que luego tendrás en producción con Let’s Encrypt, pero sin depender de DNS públicos.
Buenas prácticas, despliegue de apps y resolución de problemas típicos
Un patrón que se repite en todos estos ejemplos es que Traefik simplifica muchísimo el despliegue de nuevas aplicaciones. Una vez tienes tu stack base (Traefik + Portainer + red proxy + certificados), añadir un nuevo servicio suele reducirse a:
Clonar el proyecto o copiar su docker-compose.yml al servidor, apuntar un subdominio hacia la IP del host mediante un registro A en tu DNS (por ejemplo app.tudominio.com), y luego añadir en el servicio las labels claves: traefik.enable=true, traefik.docker.network=proxy y un router con regla de host para ese subdominio, especificando el entrypoint seguro websecure y el servicio con el puerto interno correcto.
En muchos tutoriales se mostraría, por ejemplo, el despliegue de una pequeña app FastAPI separada: se le añaden cuatro labels, se asegura que se une a la red proxy en la sección networks del docker-compose.yml, y tras hacer docker compose up -d la aplicación aparece sola en el dashboard de Traefik con TLS activo. La auto-descubierta de Traefik hace que ni siquiera tengas que tocar su configuración estática.
Para que todo vaya fino, es importante cuidar algunos detalles: no reutilizar el mismo identificador de router en varias apps, revisar que solo haya un servicio asociado a cada router (evitando el error de «too many services»), usar nombres consistentes de redes (por ejemplo, proxy en todos los stacks que quieras exponer), y, si usas Let’s Encrypt, asegurarte de que el acme.json tiene permisos 600 y que el correo configurado es válido.
En entornos Swarm, revisa también que en la configuración de Traefik el provider Docker tenga swarmMode=true y watch=true, y que las constraints de los servicios (por ejemplo node.role == manager para Portainer) no entren en conflicto con dónde está desplegado Traefik. Los logs en modo debug son muy verbosos pero resultan clave para localizar problemas de enrutado o TLS.
En conjunto, combinar Docker, Traefik y Portainer te permite montar en un solo servidor un entorno completo de orquestación ligera, con paneles gráficos, certificados automáticos y un flujo de despliegue muy rápido, donde añadir, actualizar o retirar servicios se reduce a tocar unas pocas líneas de YAML y dejar que Traefik haga el trabajo sucio del proxy inverso y del TLS.
Tabla de Contenidos
- Qué roles tienen Docker, Traefik y Portainer en la arquitectura
- Configuración estática de Traefik: traefik.yml
- Configuración dinámica: middlewares, TLS y seguridad
- docker-compose con Traefik y Portainer en Docker standalone
- Escenario Docker Swarm: Portainer y Traefik en clúster
- Ejemplo mínimo: whoami, rutas y TLS con Traefik
- Buenas prácticas, despliegue de apps y resolución de problemas típicos
