Traefik es un proxy inverso que lee la configuración de enrutado directamente de las etiquetas de tus contenedores Docker: al arrancar un contenedor con las etiquetas adecuadas, Traefik crea la ruta y solicita el certificado TLS sin que tengas que recargar nada. En este tutorial desplegarás Traefik v3 con Docker Compose en Ubuntu 24.04, publicarás un servicio de ejemplo con HTTPS de Let's Encrypt, balancearás carga entre varias réplicas y protegerás el panel de Traefik con autenticación básica.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS y una IP pública, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo que pertenezca al grupo docker.
  • Docker Engine y el plugin Docker Compose instalados desde el repositorio oficial de Docker.
  • Un dominio con dos registros DNS de tipo A apuntando a la IP del servidor: whoami.your_domain y traefik.your_domain. Sustituye your_domain por tu dominio en todo el tutorial.
  • Los puertos 80 y 443 libres en el servidor (sin Nginx ni Apache escuchando en ellos).

Conceptos básicos

Traefik separa la configuración en dos partes:

  • Configuración estática: se lee al arrancar (fichero traefik.yml). Define los puntos de entrada (entryPoints, los puertos en los que escucha), los proveedores (en este caso Docker) y los resolvedores de certificados.
  • Configuración dinámica: cambia en caliente. Con el proveedor Docker procede de las etiquetas de cada contenedor y define routers (qué peticiones aceptar, por ejemplo por Host), services (a qué contenedor y puerto enviarlas) y middlewares (transformaciones intermedias como autenticación o limitación de peticiones).

Paso 1: Abrir el cortafuegos

Traefik necesita el puerto 80 para el reto HTTP de Let's Encrypt y para redirigir a HTTPS, y el 443 para servir el tráfico cifrado. Si usas UFW, permite ambos junto con SSH:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Comprueba las reglas:

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere

Paso 2: Crear la red compartida

Traefik y los contenedores que publica tienen que compartir una red Docker. Crea una red externa llamada proxy, que podrán usar todos tus proyectos de Compose:

docker network create proxy
3f1c8a2d9b7e4f0a6c5d1e2b8a9f7c6d5e4b3a2c1d0e9f8a7b6c5d4e3f2a1b0c

Paso 3: Escribir la configuración estática

Crea el directorio del proyecto:

sudo mkdir -p /opt/traefik
sudo chown "$USER": /opt/traefik
cd /opt/traefik

Crea el fichero de configuración estática:

nano traefik.yml
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

providers:
  docker:
    exposedByDefault: false
    network: proxy

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@your_domain
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

api:
  dashboard: true

log:
  level: INFO

accessLog: {}

Qué hace cada bloque:

  • entryPoints: Traefik escucha en el 80 (web) y en el 443 (websecure). Todo lo que llega al 80 se redirige a HTTPS, y todos los routers de websecure usan por defecto el resolvedor letsencrypt.
  • providers.docker: con exposedByDefault: false, Traefik ignora los contenedores que no tengan la etiqueta traefik.enable=true. network: proxy indica por qué red llegar a ellos.
  • certificatesResolvers: registra una cuenta ACME con tu correo y guarda los certificados en acme.json. Cambia admin@your_domain por un correo real, ya que Let's Encrypt lo usa para avisos.
  • api.dashboard: activa el panel web, que publicarás de forma protegida en el paso 6.

Paso 4: Arrancar Traefik con Docker Compose

Crea el fichero de Compose:

nano compose.yaml
services:
  traefik:
    image: traefik:v3
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy

networks:
  proxy:
    external: true

Traefik lee por defecto /etc/traefik/traefik.yml, así que no hace falta pasarle ningún argumento. El socket de Docker se monta en solo lectura porque Traefik solo necesita consultar los contenedores.

Arranca el contenedor:

docker compose up -d

Comprueba que está en marcha y que no hay errores en el registro:

docker compose ps
docker compose logs traefik
NAME      IMAGE        COMMAND                  SERVICE   CREATED          STATUS          PORTS
traefik   traefik:v3   "/entrypoint.sh trae…"   traefik   10 seconds ago   Up 9 seconds    0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
traefik  | time="..." level=info msg="Traefik version 3.x.x built on ..."

Paso 5: Publicar un servicio con HTTPS

Como servicio de ejemplo usarás traefik/whoami, un pequeño servidor web que responde con los datos de la petición y el nombre del contenedor. Crea un proyecto aparte, como harías con una aplicación real:

mkdir -p /opt/traefik/whoami
nano /opt/traefik/whoami/compose.yaml
services:
  whoami:
    image: traefik/whoami
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.your_domain`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

networks:
  proxy:
    external: true

Las etiquetas crean un router llamado whoami que acepta peticiones HTTPS para whoami.your_domain y las envía al puerto 80 del contenedor. No hay sección ports:: el contenedor no es accesible desde fuera salvo a través de Traefik.

Arranca el servicio:

cd /opt/traefik/whoami
docker compose up -d

La primera vez, Traefik solicita el certificado a Let's Encrypt, lo que tarda unos segundos. Comprueba primero que HTTP redirige a HTTPS:

curl -sI http://whoami.your_domain | grep -i location
Location: https://whoami.your_domain/

Después comprueba la respuesta por HTTPS:

curl https://whoami.your_domain
Hostname: 8d2f4c1b7a9e
IP: 127.0.0.1
IP: 172.18.0.3
RemoteAddr: 172.18.0.2:41234
GET / HTTP/1.1
Host: whoami.your_domain
...

Si curl devuelve un error de certificado, revisa el registro de Traefik con docker logs traefik; el mensaje de ACME indica si falló la resolución DNS o el acceso al puerto 80.

Balancear carga entre réplicas

Cuando un servicio de Compose tiene varios contenedores, Traefik los añade todos al mismo backend y reparte las peticiones en round-robin. Escala whoami a tres réplicas:

docker compose up -d --scale whoami=3

Lanza varias peticiones y fíjate en el campo Hostname, que cambia en cada respuesta:

for i in 1 2 3; do curl -s https://whoami.your_domain | grep Hostname; done
Hostname: 8d2f4c1b7a9e
Hostname: 1a7c3e9f2b4d
Hostname: 5e0b8d6a3c1f

Paso 6: Publicar el panel de Traefik con contraseña

El panel muestra todos los routers, servicios y middlewares activos, así que debe ir protegido. Usarás el middleware basicAuth con un fichero de usuarios en formato htpasswd.

Instala htpasswd, que está en el paquete apache2-utils:

sudo apt install apache2-utils

Genera el usuario admin con hash bcrypt. El comando te pedirá la contraseña dos veces:

htpasswd -cB /opt/traefik/usersfile admin
chmod 600 /opt/traefik/usersfile
New password:
Re-type new password:
Adding password for user admin

Edita /opt/traefik/compose.yaml y añade el fichero de usuarios y las etiquetas del panel al servicio traefik:

nano /opt/traefik/compose.yaml
services:
  traefik:
    image: traefik:v3
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
      - ./usersfile:/etc/traefik/usersfile:ro
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.your_domain`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.usersfile=/etc/traefik/usersfile"

networks:
  proxy:
    external: true

api@internal es el servicio interno de Traefik que sirve el panel y su API. Al usar un fichero de usuarios en lugar de la etiqueta basicauth.users evitas tener que escapar los caracteres $ del hash en el YAML de Compose.

Recrea el contenedor para aplicar los cambios:

cd /opt/traefik
docker compose up -d

Comprueba que sin credenciales se deniega el acceso:

curl -I https://traefik.your_domain/dashboard/
HTTP/2 401
www-authenticate: Basic realm="traefik"

Abre https://traefik.your_domain/dashboard/ en el navegador (la barra final es necesaria), introduce el usuario admin y la contraseña, y verás los routers whoami y dashboard en la sección HTTP.

Paso 7: Añadir middlewares a un servicio

Los middlewares se declaran con etiquetas y se asignan a un router con routers.<nombre>.middlewares. Como ejemplo, limita whoami a una media de 10 peticiones por segundo por IP de origen, con ráfagas de hasta 20, y añade la cabecera HSTS. Edita /opt/traefik/whoami/compose.yaml y amplía las etiquetas:

nano /opt/traefik/whoami/compose.yaml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.your_domain`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.middlewares=whoami-ratelimit,whoami-headers"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"
      - "traefik.http.middlewares.whoami-ratelimit.ratelimit.average=10"
      - "traefik.http.middlewares.whoami-ratelimit.ratelimit.burst=20"
      - "traefik.http.middlewares.whoami-headers.headers.stsSeconds=31536000"

Aplica el cambio y comprueba que la cabecera aparece en la respuesta:

cd /opt/traefik/whoami
docker compose up -d
curl -sI https://whoami.your_domain | grep -i strict-transport
strict-transport-security: max-age=31536000

Si superas el límite, Traefik responde con 429 Too Many Requests.

Solución de problemas

  • 404 page not found al acceder al dominio: Traefik no tiene un router para ese host. Comprueba que el contenedor tiene traefik.enable=true, que está en la red proxy y que la regla Host coincide exactamente con el dominio.
  • Gateway Timeout o Bad Gateway: el contenedor está en una red a la que Traefik no llega o el puerto de loadbalancer.server.port no es el puerto en el que escucha la aplicación dentro del contenedor.
  • El navegador muestra el certificado TRAEFIK DEFAULT CERT: Let's Encrypt no pudo validar el dominio. Revisa docker logs traefik | grep -i acme; lo habitual es que el registro DNS aún no apunte al servidor o que el puerto 80 esté bloqueado.
  • Errores de límite de Let's Encrypt (too many certificates): no borres letsencrypt/acme.json para reintentar, ya que Traefik vuelve a pedir todos los certificados. Durante las pruebas puedes añadir caServer: https://acme-staging-v02.api.letsencrypt.org/directory dentro del bloque acme.

Conclusión

Tienes Traefik funcionando como punto de entrada único de tu servidor, con redirección a HTTPS, certificados de Let's Encrypt que se renuevan solos, balanceo entre réplicas y un panel protegido. Para publicar cualquier otra aplicación basta con conectarla a la red proxy y añadirle las etiquetas del paso 5. Como siguientes pasos, puedes usar el reto DNS para obtener certificados comodín, activar las métricas de Prometheus de Traefik o añadir el middleware ipAllowList para restringir el panel a tu IP.