Ambos comandos instalan el mismo PearlPBX2 — el mismo panel de administración, las mismas colas, el mismo dashboard. La diferencia no está en el producto, sino en todo lo que lo rodea: TLS, copias de seguridad, el firewall, cron y cómo llega realmente el tráfico de medios a tu teléfono.
Dos comandos
# Sandbox en Docker — clona en ./PearlPBX2
curl -fsSL https://pearlpbx2.com/quickstart-docker.sh | bash
# Instalación de producción — clona en /opt/PearlPBX2
curl -fsSL https://pearlpbx2.com/quickstart-install.sh | sudo bash
El primero tarda lo que Docker necesite para descargar las imágenes. El segundo tarda entre 15 y 30 minutos, porque Asterisk 22 se compila desde el código fuente. Ambos terminan mostrando un usuario administrador y una contraseña generada.
Qué instala realmente cada ruta
quickstart-install.sh es un envoltorio sobre Ansible. Nueve roles instalan PostgreSQL, Redis,
Asterisk 22, nginx con TLS, unidades de systemd para los servicios, reglas de nftables y cron.
La
instalación es totalmente desatendida: detecta el puerto SSH y lo registra en el log antes de
aplicar las reglas del firewall, sin pedir confirmación.
quickstart-docker.sh verifica que Docker y Compose v2 estén disponibles, genera un .env y
levanta el stack: Django, andrius/asterisk:22, PostgreSQL 17, Redis 7, FastAGI y el
dashboard-listener. Django escucha en el puerto 8000 sin nginx y sin TLS, en modo
DEVMODE=Development. Asterisk expone los puertos 5060/udp, 5060/tcp, 10000-10099/udp y
8088/tcp en la red bridge. Los datos de demostración ya están cargados: usuarios 201–210, una
prueba de eco en 130, un IVR en 140 y colas 141 y 142.
SIP, NAT y por qué depende de tu sistema operativo
Aquí está la verdadera línea de falla, y no tiene nada que ver con PearlPBX2 — tiene que ver con cómo está construido Docker en cada sistema.
En Linux, la red de contenedores es una interfaz normal en el host. La subred 172.x es
enrutable, así que un softphone en la misma máquina puede llegar a Asterisk directamente o a
través de los puertos publicados — ambas formas funcionan.
En macOS y Windows, Docker Desktop mantiene los contenedores dentro de una máquina virtual Linux
oculta. La subred de los contenedores no es visible desde el host en absoluto — solo funciona lo
que está publicado en localhost. La señalización SIP pasa sin problema porque el puerto 5060
está publicado. Los medios, en cambio, se rompen de la forma clásica: Asterisk coloca en el SDP
la dirección que ve de sí mismo — la dirección del contenedor — y el softphone intenta enviar RTP
a ningún lado.
La solución es la misma que para cualquier NAT frente a Asterisk: configurar
external_media_address y external_signaling_address en el transporte PJSIP con una dirección
a la que el cliente pueda llegar realmente, y confiar en RTP simétrico para que Asterisk responda
al lugar de donde vinieron los paquetes. Es decir, “SIP en Docker en una laptop” no está roto —
solo necesita la misma configuración de NAT que cualquier instalación detrás de un router.
Un efecto secundario que conviene conocer de antemano: cien puertos UDP publicados para RTP
ralentizan notablemente docker compose up en Docker Desktop.
Por qué WebRTC se comporta distinto
WebRTC no cae en esta trampa, y por eso el sandbox de Docker es una buena forma de mostrar llamadas desde el navegador.
Un navegador no confía a ciegas en la dirección del SDP como lo hace un softphone normal: reúne candidatos ICE, envía medios primero, y Asterisk responde a la dirección desde la que llegaron los paquetes. No hay ninguna dirección externa que configurar a mano. Además, los medios en WebRTC siempre van cifrados — DTLS-SRTP no es algo que se pueda olvidar activar, es un requisito de la especificación.
Una aclaración: Compose publica el puerto 8088, que es ws, no wss. En localhost eso basta,
porque los navegadores tratan localhost como un contexto seguro y de todas formas conceden
acceso al micrófono. En el momento en que quieras conectarte desde otra máquina, necesitarás un
transporte WSS y un certificado real — justo lo que la ruta de producción ya configura por ti.
Lo que falta por completo en Docker: cron
Esta es la diferencia menos evidente, y la más cara de descubrir seis meses después. Ansible instala siete tareas periódicas; el stack de Docker no tiene ninguna:
- Copia de seguridad de PostgreSQL — diaria a las 01:30.
- Copia de seguridad de
/etc/asterisk— diaria a las 02:30. - Conversión de grabaciones wav → mp3 — cada 15 minutos.
- Descarga de mp3 a almacenamiento externo — diaria a las 03:00.
- Monitoreo del sistema — cada 15 minutos.
- Rotación de logs de Asterisk — diaria.
- Informe por correo de las llamadas más largas — diario a las 06:00, si hay destinatarios configurados.
En la práctica esto significa que en el sandbox las grabaciones se quedan en wav y el disco crece linealmente con el volumen de llamadas, no hay copia de seguridad de la base de datos y nadie recorta los logs de Asterisk. Para una semana de pruebas, nada de esto importa. Para un sistema que alguien usa a diario, sí importa.
Diferencias más pequeñas que conviene conocer
- Zona horaria. Ansible configura
Europe/Kyiv; los contenedores usan UTC por defecto — se nota en los CDR y en los informes. - El demonio de devoluciones de llamada está desactivado por defecto en Docker: realiza llamadas
salientes reales, así que hay que activarlo explícitamente con
--profile callback. - Los datos viven en volúmenes con nombre.
docker compose downlos conserva;down -vborra todo, incluida la base de datos. - No hay nada con qué actualizar ni revertir:
update.shyrollback.sh -ndependen de un registro de despliegues que simplemente no existe en la ruta de Docker.
Cuál elegir
Para ver cómo se ve, montar un flujo de llamadas, mostrarle WebRTC a un colega o probar el dialplan — Docker. Para eso está hecho, y funciona en cualquier sistema operativo.
Para recibir llamadas reales, conservar grabaciones y dormir tranquilo — install.sh en un
Debian o Ubuntu limpio. La diferencia no está en PearlPBX2 en sí, sino en quién vigila las copias
de seguridad, el disco y los certificados. En la ruta de producción, eso ya está resuelto.