Docker 29 Rompió Mi VPS. Claude Code Encontró Dos Bugs que No Sabía que Existían
Abrí mi dashboard de SaaS y me encontré con esto:
Web server is down - Error code 521
You → Cloudflare → myapp.example.com
Browser Dallas Host
✅ Working ✅ Working ❌ Error
En resumen: Una actualización rutinaria de Docker rompió silenciosamente dos cosas en mi servidor de producción. Mi reverse proxy ya no podía descubrir servicios, y apareció un conflicto de puertos de la nada. Claude Code diagnosticó ambos problemas por SSH desde mi máquina local, sin estar instalado en el servidor. Tiempo total desde "está caído" hasta HTTP 200: 25 minutos.

Primer reflejo: el dominio. Abrí mi registrar para verificar si el dominio seguía activo. Sí estaba. Luego probé otro subdominio en el mismo dominio. Cargó perfecto. Así que el dominio no era el problema.
Segundo pensamiento: n8n se colgó. Pasa. Los contenedores de Docker mueren, las bases de datos se quedan sin memoria, los sospechosos de siempre. Ya me estaba preparando mentalmente para un docker restart y un café.
Tercer pensamiento: espera. Si el otro subdominio funciona, y ambos pasan por Cloudflare al mismo servidor, entonces el servidor está respondiendo. Lo que significa que no es n8n. Es el reverse proxy. Traefik.
Genial (para nada genial). Traefik.
Nadie se despierta pensando "qué bueno, mi servidor de producción está caído, no tenía nada planeado para hoy de todas formas. -jaja". Debuggear un reverse proxy en un servidor en vivo no es algo que hagas casualmente. Son capas sobre capas: socket de Docker, descubrimiento de servicios, certificados TLS, reglas de enrutamiento. El tipo de problema donde googleas el error, obtienes 15 respuestas que se acercan pero no son tu configuración, y pierdes dos horas antes de encontrar siquiera el hilo correcto que tirar.
Había estado usando Claude Code durante meses en trabajo de desarrollo. Construyendo funcionalidades, refactorizando, debuggeando código de aplicación. Me había impresionado lo suficiente como para pensar: veamos si puede manejar esto. Lo abrí en mi Mac, me conecté por SSH al servidor, y describí el síntoma.
Veinticinco minutos después, dos bugs estaban arreglados. No uno. Dos. Misma causa raíz, síntomas completamente diferentes. Cada paso a continuación.
Tres Contenedores Corriendo. Cero Sirviendo Tráfico.
Claude verificó los contenedores primero. Triaje estándar. Si la app está caída, ve qué está realmente corriendo.
sudo docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
NAMES STATUS PORTS
n8n Up 2 minutes 5678/tcp
n8n_postgres Up 2 minutes (healthy) 5432/tcp
traefik Up 2 minutes
Los tres arriba. App loggeando "ready on port 5678", base de datos saludable, reverse proxy corriendo. Pero los tres mostrando "Up 2 minutes." Acababan de reiniciarse. Algo los tumbó.
Ah y el primer docker ps falló. Permiso denegado en el socket de Docker. Claude no sabía mágicamente que necesitaba sudo. Lo intentó, obtuvo el error, agregó sudo, siguió adelante. Esto no es magia. Es iteración.
Así que la app está saludable. El reverse proxy está corriendo. Pero Cloudflare no puede alcanzar el origen. Claude fue directo a los logs de Traefik. Porque si tres contenedores saludables no pueden llegar a internet, la capa de enrutamiento es el sospechoso.
sudo docker logs traefik --tail=30
Repitiéndose cada segundo:
ERR "client version 1.24 is too old. Minimum supported API version
is 1.44, please upgrade your client to a newer version"
providerName=docker
Ahí está la pistola humeante. Traefik usa el socket de Docker para auto-descubrir servicios. Consulta el daemon, encuentra contenedores con las etiquetas correctas, construye rutas dinámicamente. Si no puede hablar con el daemon, no descubre nada. Sin servicios, sin rutas, Cloudflare golpea el origen, no obtiene nada, lanza 521.
Dos comandos. Cero visibilidad a la causa raíz. Eso me habría tomado 45 minutos de saltar entre logs y conjeturas erróneas por mi cuenta, probablemente más si soy honesto conmigo mismo.
Docker 29 Cambió las Reglas. Nadie Envió un Memo.
Claude verificó versiones:
sudo docker version
Docker Engine 29.2.1. Versión de API 1.53. Mínimo soportado: 1.44.
Mi imagen de Traefik: traefik:v3.0, lanzada en abril 2024. Casi dos años sin actualizar un reverse proxy en producción. No mi momento más orgulloso, pero también - espera, déjame reformular eso. Sabía que estaba viejo. Solo seguía diciéndome "si funciona no lo toques" que es el tipo de pensamiento que envejece muy bien hasta que un día no.
Traefik v3.0 hardcodea la versión de API 1.24 cuando habla con el socket de Docker. Funcionó bien con Docker 28 y todas las versiones anteriores. Docker 29 subió el piso, y el handshake se rechaza. La ironía: fijas una versión específica para evitar sorpresas, y dos años después esa misma decisión rompe todo. Pero si corres latest, obtienes un sabor diferente de sorpresa. Elige tu veneno.
Sin advertencia de deprecación al momento de actualizar. Sin "oye, esto podría romper tu reverse proxy." Solo un aumento silencioso de versión mínima que mata el descubrimiento de servicios para cualquiera corriendo un Traefik más viejo.
"Critica Tu Propio Diagnóstico. Esto Es Producción."
No lo acepté sin más. Tengo otros servidores corriendo la misma configuración de Traefik sin problemas. Si el diagnóstico era correcto, esos también deberían estar rotos.
Así que le dije a Claude: este es un servidor de producción. Critica tu propio razonamiento. Dime por qué mis otros servidores no están fallando. Y dame un prompt que pueda correr en la otra caja para verificar.
Esta es la parte que no esperaba. Claude generó un script de verificación independiente. No un vago "ve a verificar la versión de Docker." Un conjunto real de comandos, con comentarios de contexto, que podía copiar y pegar en una sesión SSH en el segundo servidor. Sin necesidad de instalar Claude Code allá. Solo SSH, pegar, leer la salida.
docker version --format \
'Docker {{.Server.Version}} - API min: {{.Server.MinAPIVersion}}'
docker inspect traefik --format 'Image: {{.Config.Image}}'
docker logs traefik --tail=10 2>&1 \
| grep -i "api version" || echo "No API version error"
Resultados del Servidor B: Docker 28.3.3, API mínimo 1.24, Traefik v3.6.2, cero errores.
Dos diferencias explicaron todo. Docker 28 todavía acepta la API vieja. Y Traefik v3.6.1+ incluye un fix, WithAPIVersionNegotiation(), que auto-negocia en lugar de hardcodear 1.24. El Servidor A tenía el Docker viejo y el Traefik viejo. El Servidor B tenía el Docker viejo pero un Traefik más nuevo.
Cualquier buen ingeniero puede seguir un rastro de logs. Esa parte no es difícil. La parte difícil es saber cuándo dudar de la conclusión. He visto suficientes incidentes de producción donde "la respuesta obvia" explica el síntoma pero se pierde completamente el contexto. Forzar a Claude a defender su razonamiento contra un punto de datos contradictorio convirtió un diagnóstico plausible en uno confirmado.
También es exactamente el flujo de trabajo que uso cuando construyo herramientas CLI en lugar de depender de capas de abstracción. Cuando algo se rompe en una cadena de descubrimiento, no confías en el dashboard. Vas a inspeccionar el handshake real.
Un Fix Aplicado. Un Bug Nuevo Creado.
Fix obvio: actualizar Traefik a una versión que pueda negociar con Docker 29.
sudo sed -i 's|image: traefik:v3.0|image: traefik:v3.6.2|' \
~/traefik/docker-compose.yml
sudo docker compose -f ~/traefik/docker-compose.yml up -d --pull always
Imagen descargada. Contenedor recreado. Y:
failed to bind host port 0.0.0.0:443/tcp: address already in use
Fantástico 💀
Claude corrió ss -tlnp | grep ':443' y encontró Tailscale Serve escuchando en la IP de VPN, puerto 443. Traefik estaba tratando de bindear 0.0.0.0:443, todas las interfaces. Docker viejo permitía esta superposición: una IP específica y un wildcard en el mismo puerto coexistían bien. Docker 29 hizo el binding de puertos más estricto. Misma actualización, segundo cambio que rompe.
Fix: bindear Traefik a la IP pública explícitamente en lugar del wildcard. Claude editó el archivo compose, reinició, verificó. Cada servicio en su propia IP. Sin conflicto.
curl -sI https://myapp.example.com | head -1
HTTP/2 200
De vuelta online. Logs de Traefik: cero líneas. Cuando Traefik no tiene de qué quejarse, no dice nada. Así es como sabes que está funcionando.
Una actualización. Dos cambios que rompen. Cero advertencias.
Lo Que Realmente Importa si Manejas Servidores
Claude Code no estaba instalado en el servidor. Corre en mi Mac. Me conecté por SSH, Claude vio la terminal, corrió comandos, leyó salida, razonó, corrió el siguiente comando. Sin Node.js que mantener en cajas de producción, sin tokens de API en máquinas expuestas. Cuando Anthropic mató mi infraestructura de OpenClaw de la noche a la mañana, reconstruí todo desde una máquina. Mismo principio. Centraliza el cerebro, distribuye el acceso.
Ahora la parte vergonzosa. Mientras escribía este artículo, me di cuenta de algo. El día antes del crash, había reiniciado el VPS. Ubuntu estaba mostrando "System restart required" y simplemente lo hice sin pensarlo dos veces. Ese reinicio es casi seguramente lo que activó que la actualización de Docker 29 tomara efecto. Y nunca hice la conexión. No verifiqué nada después del reinicio. Solo reinicié, vi que los contenedores volvían a subir, y seguí con mi vida. El bug ya estaba ahí, esperando silenciosamente a que Traefik se reiniciara y tratara de hablar con la nueva API de Docker.
Si eres ingeniero DevOps, sysadmin, o simplemente un dev que maneja su propia infra, esto es lo que sacaría de esto:
Después de cada reinicio, verifica. No solo verifiques que los contenedores estén arriba. Verifica que los servicios sean realmente alcanzables. Aquí está el prompt que uso ahora:
Acabo de reiniciar mi VPS. Conéctate por SSH y verifica que mis
procesos críticos estén realmente corriendo - me refiero a n8n,
mi base de datos, y Traefik. No solo verifiques docker ps.
Confirma que sean alcanzables y no estén tirando errores en los logs.
(Cambia n8n/database por lo que corras. El punto es: dile a Claude qué te importa, deja que figure los checks.)
Después de cada actualización mayor de paquetes, verifica tus contratos de API. Docker, Kubernetes, Traefik, Nginx, Postgres. Una actualización que no rompe tu app puede romper el pegamento entre tus servicios. Aquí está el prompt:
Acabo de actualizar Docker (o: apt upgrade mi servidor). Verifica
que todos mis servicios puedan seguir hablando entre ellos. Mira
la compatibilidad de versión de API entre Docker y cualquier
contenedor que use el socket de Docker. Marca cualquier cosa que
pueda romperse silenciosamente.
Desafía el diagnóstico antes de aplicar el fix. Si tienes múltiples servidores, úsalos. Genera un script de verificación, córrelo en la caja saludable, compara los resultados. Aquí es donde Claude brilla: no solo diagnostica, produce checks portables que puedes correr en cualquier lugar por SSH.
Deja de memorizar comandos. Empieza a describir problemas. Solía pensar que mejorar en servidores significaba conocer la sintaxis correcta de iptables de memoria o qué archivo de config vive dónde. La habilidad real ahora es describir lo que ves con suficiente claridad para que Claude pueda seguir la cadena causal. Error 521, contenedores corriendo, logs limpios, origen inalcanzable. Cuatro capas de profundidad en 25 minutos. No porque no sepa Linux. Porque rastrear dependencias a través de cuatro capas en un servidor en vivo requiere el tipo de memoria de trabajo que los humanos queman rápido y los LLMs no.
Al servidor no le importa quién escribió el comando. Pero a mí me importa que tomó 25 minutos en lugar de 3 horas.
Escribo sobre las herramientas y desastres que moldean cómo los devs indie realmente shipean. Si tu stack incluye un VPS al que le tienes un poco de miedo, te sentirás como en casa.
Suscríbete →
*Sí, esa imagen de portada es generada por IA. Puedo diagnosticar un desajuste de API de Docker pero no puedo dibujar un rack de servidores ni para salvar mi vida.
Cuando Docker 29 destruye tu infraestructura y Claude Code se convierte en tu mejor aliado de troubleshooting. Un viaje real por los laberintos de la infraestructura en producción.