Canonical bajo DDoS: lo que mis logs de Railway y uptime dicen sobre mi exposición real
¿Por qué asumimos que la infraestructura compartida "simplemente funciona" hasta que deja de funcionar para alguien más? Llevó años conviviendo con esa suposición antes de que el DDoS a Canonical me obligara a medirla en mis propios logs.
El miércoles pasado abrí HN y vi "Canonical under DDoS attack" con 178 puntos. Primer instinto: scrollear. Segundo instinto, el que gana: abrir Railway y revisar qué tan atado estaba yo a los servidores que alguien estaba martillando en ese momento.
La respuesta fue incómoda.
Ubuntu DDoS 2025: qué pasó y por qué importa en producción
El ataque apuntó a la infraestructura de distribución de Canonical — mirrors, repositorios APT, la red que alimenta apt-get update en millones de máquinas. No fue un breach de código. No robaron nada. Fue volumétrico: inundar los servidores hasta que apt deja de responder.
Para la mayoría de los devs en producción eso suena como "problema de sysadmin". Hasta que revisás cuántas veces por semana tus pipelines corren apt-get update en un Dockerfile.
Yo cuento al menos 6 imágenes distintas en mi stack actual. Todas con apt-get update hardcodeado en el build step.
Mi tesis es esta: los devs indie dependemos de infraestructura compartida pública mucho más de lo que admitimos en voz alta, y el DDoS a Canonical lo hizo visible de golpe. No porque el ataque nos haya afectado directamente — en mi caso no lo hizo. Sino porque por primera vez en meses me pregunté qué hubiera pasado si hubiera durado 48 horas más.
Lo que mis logs de Railway dijeron cuando fui a buscar
Primer movimiento: revisar los build logs de Railway de los últimos 30 días buscando latencia anómala en pasos que invocan mirrors de Ubuntu.
# Busco en los logs exportados de Railway cualquier timeout en apt
grep -E "(apt-get|apt |dpkg)" railway-build-logs-june.txt | \
grep -E "(timeout|could not|failed|Unable to fetch)" | \
sort | uniq -c | sort -rn
Resultado del comando: 11 ocurrencias de "Unable to fetch" distribuidas en 4 deployments distintos. Ninguna me había mandado alerta. Todas habían fallado silenciosamente y reintentado solas.
Eso es el punto que me preocupa. No que fallara — Railway reintenta. Sino que yo no tenía visibilidad de que estaba tocando mirrors públicos de Ubuntu con esa frecuencia. Era infraestructura invisible.
Después fui a los tiempos de build:
# Calculo tiempo promedio del step "RUN apt-get update" por semana
# (datos exportados desde Railway dashboard > Deployments > Build Logs)
awk '/apt-get update/{start=$1} /done/{if(start) print $1-start; start=""}' \
railway-build-steps-june.txt | \
awk '{sum+=$1; n++} END {print "Promedio:", sum/n, "segundos"}'
# Output: Promedio: 23.4 segundos
23 segundos promedio para apt-get update. En días normales. Durante el DDoS, ese número se hubiera ido a timeout (por defecto en Railway: 10 minutos de build completo antes de cancelar).
Si el DDoS hubiera durado mientras yo necesitaba deployar algo urgente un sábado a las 11pm — exactamente el tipo de horario donde aprendí a diagnosticar problemas en el cyber café de Palermo — ese apt-get update hubiera tumbado el deploy entero.
La superficie de ataque que no mapeé: dependencias que apuntan a Ubuntu sin decirlo
Acá viene la parte que más me sorprendió. Fui imagen por imagen en mis Dockerfiles buscando de dónde heredaba Ubuntu:
# Imagen 1: API principal
FROM node:20-bookworm-slim
# "bookworm" es Debian, no Ubuntu — OK, no toca mirrors de Canonical
# Imagen 2: Worker de procesamiento
FROM python:3.11-slim
# También Debian base — OK
# Imagen 3: Herramienta interna de admin
FROM ubuntu:22.04
# ACA está. Ubuntu directa, APT apunta a archive.ubuntu.com
# Imagen 4: Base para scripts de migración de DB
FROM ubuntu:20.04
# Otra más. LTS vieja que nunca actualicé porque "funciona"
Dos imágenes directas sobre Ubuntu. Más una tercera que hereda de una imagen propia que construí hace 8 meses sobre ubuntu:22.04 y la uso como base interna:
# Busco en mi registry privado de Railway cuántas imágenes heredan de mi base ubuntu
docker image inspect $(docker images -q) --format '{{.RepoTags}} {{.Config.Image}}' \
2>/dev/null | grep -i "juanchi-base"
# Output: 3 imágenes usando juanchi-base:latest como FROM
Total: 5 imágenes que en algún punto del build o runtime llaman a mirrors de Ubuntu. Nunca lo había contado. Nunca había visto un número concreto.
Esto conecta directo con lo que escribí sobre el kernel de Linux y las vulnerabilidades sin aviso a distribuciones: la cadena de dependencia upstream tiene más nodos de los que vemos en el día a día, y cada nodo es una superficie.
Simulé qué hubiera pasado con 48 horas de DDoS sostenido
No tengo manera de reproducir el volumen real. Pero puedo simular el efecto más relevante para un dev indie: que archive.ubuntu.com devuelva timeouts o errores 503 durante un deploy crítico.
Método: redirigir el DNS de archive.ubuntu.com en un contenedor local a un servidor que no responde, y medir el impacto en el flujo de build.
# En un contenedor de prueba con ubuntu:22.04
# Agrego una entrada /etc/hosts falsa para simular mirrors caídos
docker run --add-host=archive.ubuntu.com:127.0.0.1 \
--add-host=security.ubuntu.com:127.0.0.1 \
ubuntu:22.04 \
bash -c "time apt-get update 2>&1 | tail -5"
Output real de mi máquina:
Err:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Could not connect to 127.0.0.1:80 (127.0.0.1). - connect (111: Connection refused)
Err:2 http://security.ubuntu.com/ubuntu jammy-security InRelease
Could not connect to 127.0.0.1:80 (127.0.0.1). - connect (111: Connection refused)
Reading package lists... Done
W: Some index files failed to download...
real 0m18.432s
18 segundos para fallar. No inmediato — intenta varias veces antes de rendirse. En un pipeline de CI con 6 steps de apt, eso son potencialmente 108 segundos de build que termina en error aunque el código esté perfecto.
El resultado práctico: en un escenario de DDoS sostenido a Canonical, mis deployments urgentes fallarían sin ningún error en mi código. El diagnóstico sería confuso porque el log mostraría "build failed" en el step de dependencias, no en el código de aplicación.
Esto me trajo a la cabeza el post sobre supply chain attacks en dependencias de ML: el vector de falla no siempre viene del código que escribís, sino de la infraestructura que dás por sentada.
Los gotchas que encontré (y que probablemente tenés en el propio stack)
1. Las imágenes "slim" no te salvan si construís sobre ellas
node:20-slim usa Debian, sí. Pero si en algún step de build instalás algo con apt-get — tzdata, curl, libvips para sharp — estás tocando mirrors de Debian que tienen exactamente la misma dependencia de infraestructura pública centralizada. Cambia el dominio, no el patrón.
2. El caché de Railway no siempre ayuda cuando más lo necesitás
Railway cachea layers de Docker. Si el layer de apt-get update no cambió, no lo vuelve a correr. Bien. Pero si el Dockerfile cambió en cualquier cosa anterior a ese step — y eso pasa seguido — el caché se invalida y vuelve a tocar los mirrors. Justo cuando el sistema está bajo estrés.
3. apt-get update sin --fix-missing falla duro
# Esto falla silenciosamente en mirrors degradados:
RUN apt-get update && apt-get install -y curl
# Esto al menos intenta continuar:
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends curl
# Lo que realmente querés si el mirror puede estar caído:
RUN apt-get update --fix-missing || true && \
apt-get install -y --no-install-recommends curl 2>/dev/null || \
echo "ADVERTENCIA: apt degradado, continuando sin paquetes opcionales"
El || true es discutible — estás ignorando errores. Pero para paquetes no críticos en tiempo de build, es mejor un deploy con advertencia que un deploy cancelado a las 11pm de un viernes.
4. Nadie tiene mirrors privados de Ubuntu para sus deploys indie
Acá está la asimetría real. Una empresa grande tiene Nexus, Artifactory o un mirror interno. Un dev indie tiene... el mismo archive.ubuntu.com que todo el mundo. No hay capa de amortiguación. Cuando el mirror público falla, falla para todos sin distinción de escala.
Esto lo vivís diferente que cuando sos equipo grande. En el análisis de bugs que Rust no previene llegué a la misma conclusión por otro camino: las herramientas están optimizadas para equipos con redundancia, no para el indie que opera solo con Railway y un domingo libre.
FAQ: Ubuntu DDoS 2025 e impacto en producción indie
¿El DDoS a Canonical afectó deploys reales en Railway u otras plataformas?
Depende de cuándo hayas deployado durante el incidente. Railway usa imágenes que en muchos casos tocan archive.ubuntu.com o mirrors de Debian durante el build step de apt-get update. Si el build ocurrió en el pico del ataque y los mirrors estaban degradados, el step podría haber fallado o tardado mucho más de lo normal. En mi caso, los logs muestran 11 failures en el período pero ninguno bloqueó un deploy activo — el timing no coincidió. Sin embargo, la exposición existe.
¿Qué es lo primero que debería revisar en mis Dockerfiles para reducir esta dependencia?
Buscá cuántas veces aparece apt-get update en tus imágenes y sobre qué base se construyen. Si usás ubuntu:XX.XX directo, sos cliente directo de archive.ubuntu.com. Si usás imágenes oficiales de lenguajes como node, python o golang, normalmente heredan de Debian y no de Ubuntu. La diferencia importa porque son mirrors distintos.
¿Tiene sentido armar un mirror privado de Ubuntu para un indie/startup pequeña?
Para un solo dev, el overhead no justifica el beneficio. Un mirror de Ubuntu completo ocupa entre 80 GB y 200 GB dependiendo de las arquitecturas. Lo que sí tiene sentido es usar apt-get install --no-install-recommends para minimizar la cantidad de paquetes que bajás, y considerar imágenes distroless o Alpine para workloads donde no necesitás apt en absoluto en runtime.
¿Cuánto duró el DDoS a Canonical y qué tan severo fue?
El incidente fue reportado en Hacker News con 178 puntos y discusión activa. Canonical confirmó el ataque a su infraestructura de distribución. La duración exacta del impacto máximo no está documentada públicamente con precisión de horas, pero el hilo de HN muestra reportes de mirrors lentos o inaccesibles durante varias horas. Para simular el impacto en el propio stack, el método que usé en este post — redirigir DNS del mirror a localhost — es reproducible y da una idea concreta del tiempo de falla.
¿Alpine Linux soluciona el problema de dependencia de infraestructura pública?
Parcialmente. Alpine usa apk y sus propios mirrors (dl-cdn.alpinelinux.org), que son infraestructura separada de Canonical. Migrás la dependencia, no la eliminás. La ventaja real de Alpine en este contexto no es la redundancia — es el tamaño: las imágenes son más pequeñas, el tiempo de build es menor, y la frecuencia con la que necesitás tocar el package manager baja bastante. Menos surface area es mejor aunque no sea zero.
¿Railway tiene algún mecanismo de protección ante mirrors de upstream degradados?
Railway cachea layers de Docker, lo que ayuda si el layer de apt no cambió. Pero si el Dockerfile se modifica (cosa que pasa seguido en desarrollo activo), el caché se invalida. No hay un mecanismo nativo para "fallback a cache si el upstream está degradado". Eso es algo que tenés que implementar vos en el Dockerfile con flags como --fix-missing o con lógica de build condicional.
Conclusión: la dependencia invisible no desaparece porque no la midamos
El momento que me cambió la perspectiva sobre Docker no fue leer documentación — fue esa migración de 2015 donde una app que tardaba 2 días en mover se movió en 10 minutos. La magia de "funciona en cualquier lado" tiene un asterisco enorme: asume que el "cualquier lado" tiene acceso confiable a la misma infraestructura pública de donde bajaste las dependencias.
El DDoS a Canonical no me rompió nada. Mis deploys siguieron funcionando. Pero me hizo contar: 5 imágenes con dependencia directa o indirecta en mirrors de Ubuntu. 11 failures silenciosos en 30 días que yo nunca había visto. Un tiempo de falla simulado de 18 segundos por intento en mirrors caídos.
Esos números no existían antes de esta semana. Ahora existen. Y ya cambié dos Dockerfiles para usar --no-install-recommends y un || true estratégico en steps de paquetes no críticos.
Mi postura: no voy a armar un mirror privado ni a migrar todo a Alpine esta semana. Pero sí voy a agregar un check semanal en mis logs de Railway buscando failures en steps de apt, igual que ya tengo alertas para errores de aplicación. La infraestructura compartida es una realidad del stack indie — el problema no es usarla, es no medirla.
Si encontraste algo parecido en el propio stack, contame en los comentarios. Me interesa saber si el número de failures silenciosos es algo que otros devs tampoco estaban viendo.
Fuente original: Hacker News
Este artículo fue publicado originalmente en juanchi.dev
Top comments (0)