Ejecutar coturn en Docker parece sencillo hasta que la primera llamada no se conecta y te das cuenta de que el contenedor nunca tuvo una ruta real para el tráfico de retransmisión. La imagen es pequeña y la instalación es de verdad rápida, cinco minutos si nada falla, pero casi todo el mundo choca con el mismo muro en su primer intento: la red por defecto de Docker y los requisitos de puertos de un servidor TURN no se llevan bien.

Aquí está el docker-compose.yml que funciona de verdad, los ajustes de turnserver.conf que importan y la única decisión de red que determina si tu contenedor de coturn en Docker aguanta una carga de llamadas real o solo una prueba rápida en local.

Respuesta rápida

Descarga la imagen oficial coturn/coturn, monta tu propio turnserver.conf (la imagen no acepta el realm, el secreto ni las credenciales como variables de entorno) y ejecuta el contenedor con network_mode: host. La publicación de puertos normal de Docker no puede manejar el amplio rango de retransmisión UDP que necesita TURN sin lanzar miles de procesos proxy.

Requisitos previos

Antes de empezar

  • Docker Engine y Docker Compose instalados en un host Linux (la red del host, el modo que usa esta guía, es una función de Docker solo para Linux y se comporta de otra forma en Docker Desktop para Mac/Windows).
  • Un servidor con una dirección IP pública y estática. La retransmisión TURN no funciona detrás de CGNAT ni con una IP residencial que cambia.
  • Un nombre de dominio que apunte a esa IP si quieres compatibilidad con TLS (turns:), además de un certificado para él.
  • Los puertos 3478 y 5349 (TCP y UDP) y el rango de puertos de retransmisión que elijas abiertos en el cortafuegos del host y en cualquier grupo de seguridad de la nube que haya delante.

La imagen oficial de coturn para Docker

La imagen que hay que usar es coturn/coturn en Docker Hub, mantenida directamente por el proyecto coturn y no por un tercero. Se publica en una variante basada en Debian y en otra más pequeña basada en Alpine (con etiquetas como 4.14-alpine), y ambas se actualizan al ritmo de las versiones de coturn. Fija una etiqueta concreta en producción; no uses latest para descubrir que cambió el comportamiento de la retransmisión la misma semana que un incidente.

La imagen ofrece tres formas de configurar el servidor: un turnserver.conf montado, opciones de línea de comandos añadidas después del nombre de la imagen o un pequeño conjunto de variables de entorno para la detección automática de IP. En esa tercera opción es donde más gente tropieza; lo vemos más abajo.

Coturn Docker Compose: la configuración

Este es un docker-compose.yml que funciona de verdad, no la versión recortada que solo cubre la parte STUN:

services:
  coturn:
    image: coturn/coturn:4.14-alpine
    container_name: coturn
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./turnserver.conf:/etc/coturn/turnserver.conf:ro
      - ./certs:/etc/coturn/certs:ro
      - coturn-data:/var/lib/coturn

volumes:
  coturn-data:

Hay dos cosas que conviene notar. No hay un bloque ports:, y no es un error: la red del host lo hace innecesario y Compose lo ignoraría de todos modos. Y coturn-data es un volumen con nombre en lugar de un bind mount, porque lo único que vive en /var/lib/coturn/ es estado de sesión transitorio que no necesitas revisar desde el host.

Levántalo de la forma habitual:

docker compose up -d
docker compose logs -f coturn

Configurar turnserver.conf

Este es el archivo que hace el trabajo de verdad. Una versión mínima pero usable en producción:

listening-port=3478
tls-listening-port=5349
min-port=49152
max-port=65535

external-ip=203.0.113.10
realm=turn.example.com

use-auth-secret
static-auth-secret=replace-this-with-a-long-random-string

cert=/etc/coturn/certs/fullchain.pem
pkey=/etc/coturn/certs/privkey.pem

no-cli
no-multicast-peers
log-file=stdout

Algunos de estos ajustes importan más de lo que parece:

  • external-ip: ponlo con la IP pública real de tu servidor, no lo dejes en blanco. Detrás de NAT (técnicamente lo están la mayoría de las máquinas virtuales en la nube, aunque tengan una IP pública asignada), hay que decirle a coturn qué dirección anunciar a los clientes, o los candidatos de retransmisión vuelven con la IP privada y nada se conecta.
  • realm: es uno de los términos que la gente busca directamente, y es sencillo: solo es una cadena, normalmente tu dominio, que delimita el ámbito de autenticación. No necesita que el DNS apunte a ningún sitio para que TURN funcione, aunque tu certificado TLS sí lo necesita.
  • use-auth-secret / static-auth-secret: es el mecanismo de credenciales a largo plazo (pares de usuario y contraseña basados en HMAC y de duración limitada), el mismo enfoque que usa la propia AMI de Coturn para AWS de Meetrix. Es más fácil de operar que una lista estática de usuarios y contraseñas y no necesita base de datos.
  • no-cli: desactiva la CLI de administración de coturn por telnet en el puerto 5766. No hay motivo para exponerla dentro de un contenedor, y dejarla activa es una superficie de ataque innecesaria.

Variables de entorno frente al archivo de configuración

Si buscabas variables de entorno de coturn en Docker para definir TURN_REALM o algo parecido, esta es la respuesta directa: la imagen oficial no las tiene. Solo admite cuatro variables de entorno, y todas tratan de la detección automática de IP, no del comportamiento de TURN:

Variable Función
DETECT_EXTERNAL_IP Detecta automáticamente al arrancar la dirección IPv4 externa del contenedor
DETECT_RELAY_IP Detecta automáticamente la dirección IPv4 que se usará para las asignaciones de retransmisión
DETECT_EXTERNAL_IPV6 Lo mismo que la anterior, para IPv6
DETECT_RELAY_IPV6 Lo mismo que la anterior, para la retransmisión IPv6

Todo lo demás, el realm, el static-auth-secret, los certificados TLS, el rango de puertos, tiene que pasar por turnserver.conf o por una opción añadida a la línea de comandos de docker run o de Compose. Si has visto un artículo o una respuesta de Stack Overflow donde una variable de entorno TURN_REALM= funciona, casi seguro que se trata de una imagen de terceros, no de la oficial, y no te servirá si cambias de imagen más adelante.

Por qué importa la red del host

Esta es la parte que rompe la mayoría de los primeros intentos, incluidos los copiados de tutoriales que se la saltan. TURN necesita un amplio rango de puertos UDP abierto para las asignaciones de retransmisión, de 49152 a 65535 por defecto, más de 16.000 puertos. La forma normal de Docker de exponer puertos, una opción -p o un bloque ports: por puerto, lanza un proceso docker-proxy en espacio de usuario para cada uno. Dieciséis mil de esos dejan a un host de rodillas mucho antes de que llegue tráfico real.

network_mode: host esquiva el problema por completo: el contenedor comparte directamente la pila de red del host, sin NAT, sin proxy por puerto y sin una capa de traducción contra la que coturn tenga que pelear cuando intenta averiguar su propia IP externa.

Si no puedes usar la red del host

Algunos entornos, como Docker Desktop en Mac o Windows o ciertas plataformas de contenedores gestionadas, no admiten la red del host en absoluto. En ese caso, reduce el rango de retransmisión en turnserver.conf (min-port=49152, max-port=49352 es un buen punto de partida para pruebas) y publica explícitamente ese rango más pequeño. Funciona, pero impone un techo fijo de sesiones retransmitidas simultáneas, y vuelves a pelear con la traducción NAT para la IP externa.

Una cosa más que conviene saber antes de desplegar: una incidencia de coturn en GitHub (#966) documenta justo este problema, que se manifiesta como llamadas que se conectan y luego se cortan, con "allocation watchdog determined stale session state" en los registros, y cuyo origen es que el contenedor adivina mal la IP externa con la red del host. Definir external-ip explícitamente, como en la configuración de arriba, es lo que lo evita.

Probar el contenedor

Con el contenedor en marcha, genera un usuario y una contraseña temporales a partir de tu static-auth-secret:

secret=replace-this-with-a-long-random-string && \
time=$(date +%s) && \
expiry=86400 && \
username=$(( $time + $expiry )) && \
echo username:$username && \
echo password:$(echo -n $username | openssl dgst -binary -sha1 -hmac $secret | openssl base64)

Después introduce la dirección del servidor, el usuario y la contraseña generados en la herramienta de prueba Trickle ICE, pulsa para recopilar candidatos y busca un candidato de tipo relay en los resultados. Si no aparece ningún candidato relay, el contenedor está funcionando pero todavía no sirve como servidor TURN; suele ser un problema de external-ip, del cortafuegos o del rango de puertos, no de coturn en sí. Nuestro probador ICE de WebRTC hace la misma comprobación y explica en lenguaje claro cada candidato que encuentra.

Solución de problemas

  • El contenedor arranca pero no aparece ningún candidato relay: comprueba que external-ip en turnserver.conf coincide con la IP pública real del servidor, y confirma que el grupo de seguridad o el cortafuegos que hay delante permite todo el rango UDP de min-port a max-port, no solo 3478 y 5349.
  • Funciona en local pero falla para usuarios remotos: casi siempre es un hueco del cortafuegos o del grupo de seguridad en el rango de puertos de retransmisión, o el contenedor se está ejecutando sin la red del host y solo con 3478/5349 publicados.
  • Falla el handshake TLS en el 5349: la ruta de montaje del volumen de certificados tiene que coincidir exactamente con lo que apuntan cert y pkey dentro del contenedor. Un error tipográfico en una ruta relativa aquí falla sin hacer ruido en los registros a menos que los vigiles de cerca.
  • CPU alta bajo carga: si no estás usando la red del host, muy a menudo es la sobrecarga de docker-proxy descrita arriba y no coturn el que tiene problemas.

Cuándo saltarse la instalación propia

Ejecutar coturn en Docker es la decisión correcta cuando quieres control total sobre la configuración, ya usas una pila basada en Docker o estás haciendo pruebas antes de comprometerte con una infraestructura. También es un servicio más del que pasas a ser responsable: actualizar la imagen, vigilar el disco y la memoria, rotar certificados y ser quien recibe el aviso cuando un cambio de red del que nadie te habló bloquea el rango de puertos de retransmisión.

Si prefieres no hacerte cargo de esas guardias, Meetrix mantiene una AMI de Coturn para AWS preconfigurada y una imagen para GCP equivalente, ambas con la red, el TLS y las credenciales a largo plazo ya configurados correctamente de serie. También hay una opción de despliegue con un clic si quieres el ahorro del autoalojamiento sin tocar ningún docker-compose.yml. Y si el servidor TURN es específicamente para un despliegue de Jitsi Meet, la guía de configuración de TURN para Jitsi y la guía de Jitsi en AWS (o su equivalente en GCP) cubren las diferencias de configuración que surgen en ese caso.

Preguntas frecuentes

¿Cuál es la imagen oficial de coturn para Docker?

Es coturn/coturn en Docker Hub, mantenida por el propio proyecto coturn. Se publica en variantes basadas en Debian y en Alpine (las etiquetas alpine son bastante más pequeñas). También existen imágenes de terceros como instrumentisto/coturn, pero la oficial es la primera opción.

¿Admite la imagen Docker de coturn variables de entorno como TURN_REALM?

No. La imagen oficial solo expone unas pocas variables de entorno para la detección automática de IP (DETECT_EXTERNAL_IP, DETECT_RELAY_IP y sus equivalentes IPv6). El realm, el static-auth-secret y cualquier otro ajuste de TURN vienen de un archivo turnserver.conf montado o de opciones de línea de comandos, no de variables de entorno.

¿Por qué coturn necesita network_mode: host en Docker?

Coturn retransmite medios por un amplio rango de puertos UDP, de 49152 a 65535 por defecto. Publicar todo ese rango con el mapeo de puertos normal de Docker lanza un proceso docker-proxy por puerto, lo que ahoga al host con una carga de llamadas real. La red del host se salta esa capa por completo.

¿Puedo ejecutar coturn en Docker sin la red del host?

Sí, si antes reduces el rango de retransmisión. Fija min-port y max-port en algo como 49152 a 49352 en turnserver.conf y publica ese rango más pequeño, además de 3478 y 5349, en tu archivo compose. Funciona, pero limita cuántas llamadas retransmitidas simultáneas puede manejar el servidor.

¿Cómo genero credenciales TURN para probar un contenedor de coturn?

Con use-auth-secret y un static-auth-secret definidos en turnserver.conf, genera un usuario y una contraseña de duración limitada con un script HMAC-SHA1 (openssl dgst -hmac) y pruébalos contra el contenedor con la herramienta Trickle ICE de WebRTC antes de conectarlo a tu aplicación.

¿Dónde guarda coturn sus datos en Docker?

La imagen escribe por defecto en /var/lib/coturn/, sobre todo el estado SQLite del proceso turnserver. Si solo usas credenciales a largo plazo mediante static-auth-secret en lugar de una base de datos de usuarios, puedes montar esa ruta como tmpfs, ya que no hay nada que merezca conservarse entre reinicios.

Olvídate por completo de la instalación con Docker

Lanza un servidor coturn preconfigurado en AWS con la red, el TLS y las credenciales ya resueltos correctamente.

Consigue Coturn en AWS Marketplace