diff --git a/TODO.md b/TODO.md
index 4627acf..0aa2d93 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1969,6 +1969,33 @@ resolvidos sem nunca registrar um SIP de verdade
(Discagem interna corrigida + `*8`) foram mantidas — são
configuração funcional de verdade, não lixo de teste
+## PHASE 54 — Publicar SIP/RTP no host (usuário tentou registrar um ramal
+de fora da rede Docker e não conseguiu)
+- [x] achado real: nenhuma porta SIP/RTP do FreeSWITCH estava publicada no
+ host — só o Event Socket (8021), e mesmo esse só na rede interna do
+ compose. O teste ponta a ponta da PHASE 53 só funcionou porque os
+ softphones de teste rodavam DENTRO da mesma rede Docker
+ (`b2bcall_default`); um softphone de verdade fora do Docker não tem
+ como alcançar o FreeSWITCH
+- [x] `infrastructure/freeswitch/Dockerfile`: RTP restrito a um range fixo
+ de 200 portas (16384-16584) via `sed` em `switch.conf.xml` — o
+ default vanilla (16384-32768, ~16k portas) é inviável de publicar
+ uma a uma no host
+- [x] `docker-compose.yml`: publicado `5060/udp`+`5060/tcp` (SIP) e
+ `16384-16584/udp` (RTP) pro container. Event Socket continua NUNCA
+ publicado (só uso interno)
+- [x] Confirmado via `iptables -t nat -L DOCKER`: as 201 portas RTP + a
+ porta SIP têm DNAT correto pro container; `ss -lunp` confirma
+ `docker-proxy` escutando 5060 no host. `sofia status profile
+ internal` confirma que o STUN (`external_rtp_ip`/`external_sip_ip`
+ em `vars.xml`, já configurado desde antes) resolve corretamente pro
+ IP público de verdade da VM (`191.240.175.55`) — SDP vai anunciar o
+ IP certo, não só o registro deve funcionar, o áudio também
+- [ ] Não verificado (fora do alcance daqui): se a VM está atrás de um
+ firewall de provedor de nuvem (security group), as mesmas portas
+ precisam estar liberadas lá também — só o `iptables` local foi
+ confirmado
+
---
## Riscos conhecidos
diff --git a/docker-compose.yml b/docker-compose.yml
index 8172523..074044f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -91,9 +91,15 @@ services:
# host e precisa enxergar os mesmos arquivos que o FreeSWITCH grava
# (agente.md secao 90-91) — ver docs/RECORDING.md.
- ./data/recordings-spool:/recordings
- # Nenhuma porta publicada no host: SIP/RTP ainda não têm troncos reais
- # configurados, e o Event Socket (8021) só deve ser alcançável por outros
+ # Event Socket (8021) NUNCA publicado — só alcançável por outros
# containers na rede interna do compose (agente.md secao 22).
+ # SIP (5060) e RTP (16384-16584, range fixo no Dockerfile) publicados
+ # a partir da PHASE 54 — necessário pra registrar um softphone/telefone
+ # de fora da rede Docker (achado testando registro de verdade).
+ ports:
+ - "5060:5060/udp"
+ - "5060:5060/tcp"
+ - "16384-16584:16384-16584/udp"
healthcheck:
test: ["CMD-SHELL", "fs_cli -p \"$$ESL_PASSWORD\" -x status | grep -q 'is ready'"]
interval: 10s
diff --git a/infrastructure/freeswitch/Dockerfile b/infrastructure/freeswitch/Dockerfile
index 4ce43c5..5539cc0 100644
--- a/infrastructure/freeswitch/Dockerfile
+++ b/infrastructure/freeswitch/Dockerfile
@@ -78,10 +78,25 @@ RUN sed -i \
/etc/freeswitch/sip_profiles/internal.xml \
&& ! grep -q "force-register-domain\|force-subscription-domain\|force-register-db-domain" /etc/freeswitch/sip_profiles/internal.xml
+# RTP num range fixo e pequeno (PHASE 54, docs/EXTENSIONS.md) — o range
+# vanilla default (16384-32768, ~16k portas) é inviável de publicar uma a
+# uma no host; achado ao tentar registrar um softphone de fora da rede
+# Docker pela primeira vez (nenhuma porta SIP/RTP estava publicada até
+# aqui — só o Event Socket interno). Restrito a 200 portas, suficiente
+# pra alguns testes/chamadas simultâneas em laboratório.
+RUN sed -i \
+ -e 's###' \
+ -e 's###' \
+ /etc/freeswitch/autoload_configs/switch.conf.xml \
+ && grep -q '' /etc/freeswitch/autoload_configs/switch.conf.xml \
+ && grep -q '' /etc/freeswitch/autoload_configs/switch.conf.xml
+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
EXPOSE 8021
+EXPOSE 5060/udp 5060/tcp
+EXPOSE 16384-16584/udp
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/usr/bin/freeswitch", "-nonat"]