Guía
Docker: "bind: address already in use" en tu Mac (lsof no te va a decir qué contenedor es)
15 de julio de 20264 min de lectura
Corres docker run -p 5432:5432 postgres o docker compose up, y te aparece:
Error response from daemon: driver failed programming external connectivity on endpoint db: Bind for 0.0.0.0:5432 failed: port is already allocated
O, dependiendo de la versión de Docker:
Error starting userland proxy: listen tcp4 0.0.0.0:5432: bind: address already in use
Mismo problema, dos mensajes distintos. Algo ya está usando el puerto 5432. Tu primer instinto probablemente es correr lsof -i :5432, y en Mac, ese instinto te va a mandar por el camino equivocado.
Por qué lsof te miente en Mac
En Linux, Docker asocia los puertos a través de un proceso liviano llamado docker-proxy, y lsof lo muestra sin problema. En Mac, Docker Desktop corre los contenedores dentro de una VM liviana, y todo el networking de los contenedores pasa por el proceso backend del propio Docker Desktop. Entonces lsof -i :5432 no te muestra postgres, ni siquiera un nombre de contenedor. Muestra esto:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
com.docke 1842 ben 41u IPv4 0x... 0t0 TCP *:5432 (LISTEN)
com.docker.backend, truncado a com.docke. Eso no es el contenedor, es la capa de networking de Docker Desktop para todos tus contenedores activos. Matar ese PID no detiene el contenedor que está en conflicto, puede cortar el networking de todos ellos, y Docker Desktop lo vuelve a levantar de todas formas. Este es el punto donde la receta lsof → kill que funciona para un servidor de desarrollo normal deja de servir por completo con Docker.
Encuentra al culpable real
Pregúntale a Docker directamente, no al sistema operativo:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"
Esto lista cada contenedor activo con su nombre, imagen y puertos publicados en una tabla. Busca 5432 (o el puerto que esté en conflicto) en la columna PORTS y vas a tener el nombre real del contenedor.
Si preferís filtrar directo en vez de buscar a ojo:
docker ps --filter "publish=5432"
Detenlo de la forma correcta
Con el nombre del contenedor:
docker stop mi-postgres-viejo
No vayas directo a docker kill, misma lógica que SIGTERM vs SIGKILL en un proceso normal: docker stop envía primero una señal de cierre ordenado (SIGTERM, y SIGKILL después de un timeout), dándole al proceso del contenedor la chance de cerrar conexiones a la base de datos y guardar escrituras pendientes. docker kill va directo a SIGKILL.
Si es un stack manejado por Compose, detén el proyecto completo en vez de un contenedor:
docker compose -p mi-proyecto down
Usar docker stop <container-id> sobre un contenedor manejado por Compose muchas veces solo hace que la política de reinicio de Compose lo vuelva a levantar, directo al mismo puerto. Detén el proyecto, no el contenedor.
Ten en cuenta que docker compose down solo alcanza a los contenedores levantados por ese proyecto Compose específico. Si el conflicto viene de otro proyecto, o de un contenedor levantado con docker run suelto, este comando no lo va a tocar — vas a tener que encontrarlo y detenerlo por separado.
Cuando es un contenedor viejo, no uno activo
A veces docker ps no muestra nada para ese puerto y el error sigue pasando. Revisa también los contenedores detenidos:
docker ps -a
Un contenedor terminado (exited) puede seguir reteniendo un mapeo de puerto hasta que se elimina. Limpia los contenedores detenidos que no necesites:
docker container prune
Si nada funciona: reinicia la VM de Docker Desktop
Si ya detuviste todos los contenedores y limpiaste los viejos, y el puerto sigue reportándose como ocupado, la propia VM de Docker Desktop puede haber quedado con un binding de proxy obsoleto. Reiniciarla limpia ese estado sin tocar los datos de tus contenedores:
killall Docker && open -a Docker
Espera a que Docker Desktop reinicie por completo antes de reintentar. Trátalo como el último recurso después de docker ps/docker stop/docker container prune, no como lo primero a probar.
Cuando ni siquiera es Docker
Si ni docker ps ni docker ps -a muestran nada en ese puerto, es un proceso normal del sistema, no un contenedor, el que está ahí. Esa es otra receta: lsof -i :5432 en el host, y luego kill, explicado paso a paso en ¿Puerto 3000 ocupado en tu Mac?.
Preguntas rápidas
El puerto está ocupado pero docker ps no muestra nada corriendo.
Revisa contenedores terminados con docker ps -a, y luego docker container prune para eliminar los que no necesites. Un contenedor detenido puede seguir reteniendo su puerto hasta que se limpia.
Corrí docker compose down pero el puerto sigue ocupado.
Probablemente tenés contenedores huérfanos de una versión anterior del archivo Compose. Corre docker compose down --remove-orphans.
¿Cómo encuentro rápido qué contenedor está usando un puerto?
docker ps --filter "publish=PUERTO" — reemplaza PUERTO por el número. Una línea, sin buscar a ojo en una tabla.
Ve esto en vez de escribirlo
Bosun vive en tu barra de menú y muestra cada puerto abierto en tu Mac, en vivo, mapeado al proceso detrás. Un clic para matarlo, SIGTERM primero. Útil la primera vez que pasa esto. Realmente útil la quinta vez que pasa en una misma tarde.