Siempre ocurre en el peor momento: tu nodo está sano, tu despliegue está en verde y de repente el disco llega al 100%.
De pronto Docker no puede tirar imágenes, tu runtime no puede escribir logs y todo parece “misteriosamente inestable”.
La tentación es ejecutar un heroico docker system prune -a --volumes y cruzar los dedos.
Confiar en la esperanza no es una estrategia de limpieza. Esta es la forma práctica y segura para producción de recuperar espacio en Docker manteniendo lo que realmente necesitas: las imágenes correctas, los volúmenes correctos y tu capacidad de explicar qué pasó después.
Un modelo mental de dónde Docker guarda espacio (y por qué te sorprende)
El uso de disco de Docker no es “una sola cosa”. Son varios cubos que crecen por distintas razones, y “limpiar Docker”
significa elegir qué cubos estás dispuesto a vaciar.
Los cubos que importan
- Imágenes: capas descargadas desde registries, además de tus imágenes construidas localmente.
- Contenedores: la capa escribible (cambios sobre la imagen), además de los metadatos del contenedor.
- Volúmenes: datos duraderos. Las bases de datos los adoran. También los accidentes.
- Cache de build: capas intermedias y metadatos de cache. En CI esto se convierte en una pila de compost.
- Logs: logs de contenedores (normalmente archivos JSON) y logs de la aplicación que escribes dentro de los contenedores.
- “Otros”: redes, plugins, objetos de swarm y restos de drivers de almacenamiento antiguos.
La mayoría de los incidentes vienen de confundir estos cubos. La gente trata los volúmenes como cache. No lo son.
La gente trata la cache de build como “se autogestionará”. No lo hará. La gente ignora los logs hasta que se convierten en una denegación de servicio de almacenamiento.
Por qué overlay2 hace que el uso de disco parezca embrujado
En Linux, el driver de almacenamiento más común de Docker es overlay2. Almacena capas de imagen y capas escribibles
de contenedores como directorios bajo la raíz de datos de Docker (a menudo /var/lib/docker).
Ese directorio puede volverse enorme y no es obvio de inmediato qué contenedor “posee” qué partes.
OverlayFS es eficiente, pero también muy literal: cada cambio que un contenedor hace en su sistema de archivos es un nuevo
archivo en su capa escribible. Si tu app escribe 50GB de datos “temporales” en /tmp dentro del contenedor,
eso no es temporal. Son 50GB en el host, y te los encontrarás otra vez cuando el disco se llene.
Regla práctica: si quieres que los datos sobrevivan a la eliminación del contenedor, ponlos en un volumen. Si quieres que los datos sean fáciles de
borrar, ponlos en un lugar que puedas eliminar de forma determinista: idealmente un volumen que destruyas intencionalmente, o una ruta en el host con una política de retención.
Una cita para tener en la pared. La frase de Gene Kranz es famosa en círculos de fiabilidad: “Failure is not an option.”
También es un recordatorio de que la limpieza es parte de las operaciones, no un hobby que haces después de un incidente.
Datos interesantes y breve historia (porque explica el lío actual)
- Los drivers de almacenamiento por defecto de Docker cambiaron con el tiempo. Sistemas antiguos usaban AUFS o Device Mapper; muchas distribuciones modernas se decidieron por
overlay2por mejor rendimiento y simplicidad. - Las “imágenes colgantes” surgieron porque las capas se comparten. Docker mantiene capas que podrían seguir siendo referenciadas. No las borrará hasta estar seguro de que no se usan.
- BuildKit cambió lo que significa “cache de build”. Con BuildKit activado, la cache puede vivir en formas distintas y puede exportarse/importarse, lo cual es genial—hasta que nunca la podas.
- Los logs de contenedores por defecto son archivos JSON en muchas instalaciones. Eso es conveniente, pero escribe indefinidamente a menos que configures rotación.
- Los fallos por disco lleno en hosts de contenedores suelen ser fallos secundarios. El primer fallo es “algún proceso escribió demasiado”, Docker solo lo hizo más fácil de ocultar.
- Se añadieron comandos “prune” porque la limpieza manual era demasiado arriesgada. Docker introdujo operaciones de alto nivel para evitar manipular directamente
/var/lib/docker(lo cual sigue siendo una mala idea). - La reutilización de capas es a la vez la optimización y la trampa. Reconstruir imágenes con muchas capas únicas hace que pull/build sea rápido a veces, y que el crecimiento de disco sea siempre rápido.
- Los runners de CI popularizaron de nuevo los “hosts mascota” por accidente. Un runner “sin estado” que nunca se reimageniza se convierte en un museo de capas cacheadas.
Chiste #1: El uso de disco de Docker es como un cajón de trastos—todo lo que hay ahí fue “temporal” en su momento.
Guía de diagnóstico rápido: primeras/segundas/terceras comprobaciones
Cuando el disco grita, no tienes tiempo para un debate filosófico sobre capas. Necesitas una secuencia rápida
que te diga dónde están los bytes y qué puedes borrar con seguridad.
Primero: confirma que el host está realmente sin disco (y dónde)
- Comprueba el uso de sistema de archivos (
df) y el uso de inodos (df -i). - Identifica qué montaje está lleno. Si
/varestá separado, Docker podría estar bien y tus logs no.
Segundo: mide los cubos de Docker antes de borrar nada
docker system dfpara obtener el desglose de alto nivel.- Busca el mayor contribuyente: imágenes, contenedores, volúmenes, cache de build.
Tercero: identifica “escritores inesperados”
- Logs de contenedores enormes (archivos JSON) y logs de app dentro de las capas escribibles del contenedor.
- Volúmenes que se inflan porque una base de datos o una cola retuvo datos.
- Crecimiento de cache de build en runners de CI.
Si estás en una emergencia real (disco 100%), prioriza restaurar la capacidad de escritura: rota/trunca logs desbocados,
detén los mayores infractores o mueve datos fuera del host. Luego realiza una limpieza segura con trazabilidad.
Tareas prácticas (comandos + significado de la salida + decisión)
Estas tareas suponen que tienes acceso shell al host de Docker. Ejecútalas en orden cuando puedas.
Si estás en un momento de “disco lleno, todo arde”, salta a las tareas marcadas como amigables en emergencia.
Tarea 1: Verifica qué sistema de archivos está lleno
cr0x@server:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 80G 78G 1.2G 99% /
tmpfs 16G 92M 16G 1% /run
/dev/nvme1n1p1 200G 40G 160G 20% /data
Qué significa: El filesystem raíz está casi lleno. Ahí es típicamente donde vive /var/lib/docker.
Decisión: Enfócate en Docker y logs del sistema en /, no en el amplio montaje /data.
Tarea 2: Comprueba agotamiento de inodos (traicionero, común)
cr0x@server:~$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 5242880 5110000 132880 98% /
Qué significa: Casi te quedas sin inodos. Esto puede ocurrir con millones de archivos pequeños (cache de build, node_modules, etc.).
Decisión: “Liberar disco” podría no arreglarlo; necesitas borrar muchos archivos pequeños, normalmente caches o artefactos de build.
Tarea 3: Encuentra la raíz de datos de Docker
cr0x@server:~$ docker info --format '{{ .DockerRootDir }}'
/var/lib/docker
Qué significa: Este es el directorio cuyo crecimiento estás gestionando.
Decisión: Si tu filesystem raíz es pequeño, planifica migrar la raíz de datos de Docker más adelante. Por ahora, diagnostica el uso dentro de él.
Tarea 4: Obtén la contabilidad de disco propia de Docker
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 52 12 38.6GB 21.4GB (55%)
Containers 19 7 2.4GB 1.1GB (45%)
Local Volumes 34 15 96.2GB 18.0GB (18%)
Build Cache 184 0 22.8GB 22.8GB (100%)
Qué significa: Tus volúmenes son el mayor trozo (96GB), pero la cache de build es totalmente recuperable (22GB).
Decisión: Empieza por podar la cache de build (relativamente segura), luego imágenes/contenedores no usados. Trata los volúmenes con cuidado.
Tarea 5: Lista las imágenes más grandes (detecta a los culpables)
cr0x@server:~$ docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}' | head
REPOSITORY TAG IMAGE ID SIZE
myapp/api prod 9c1a2d4e0b2f 1.21GB
myapp/api staging 62f13c9d1a11 1.19GB
postgres 15 2b6d1f2aa0c1 413MB
node 20 1c2d0a41d2aa 1.11GB
Qué significa: Tienes varias etiquetas grandes para el mismo repo. Es común cuando los despliegues etiquetan cada commit.
Decisión: Conserva las que están en ejecución; elimina etiquetas antiguas si no se usan. Más adelante: implementa retención en tu pipeline.
Tarea 6: Identifica contenedores que aún usan esas imágenes
cr0x@server:~$ docker ps --format 'table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}'
CONTAINER ID IMAGE STATUS NAMES
c8b2d9f2a4aa myapp/api:prod Up 6 days api-prod-1
e12a9a0110bb postgres:15 Up 12 days pg-main
Qué significa: Solo myapp/api:prod está en uso activo; staging podría ser peso muerto.
Decisión: No elimines imágenes usadas por contenedores en ejecución. Elimina las no usadas tras confirmar que ningún otro host depende de ellas localmente.
Tarea 7: Muestra contenedores detenidos y sus tamaños
cr0x@server:~$ docker ps -a --size --format 'table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Size}}\t{{.Names}}' | head
CONTAINER ID IMAGE STATUS SIZE NAMES
a1b2c3d4e5f6 myapp/api:staging Exited (137) 3 days ago 1.2GB (virtual 2GB) api-staging-1
f1e2d3c4b5a6 node:20 Exited (0) 9 days ago 600MB (virtual 1GB) build-job-77
Qué significa: Los contenedores finalizados pueden mantener capas escribibles grandes.
Decisión: Elimina contenedores finalizados antiguos si no necesitas su sistema de archivos para análisis forense.
Tarea 8: Elimina contenedores detenidos (bajo riesgo)
cr0x@server:~$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
a1b2c3d4e5f6
f1e2d3c4b5a6
Total reclaimed space: 1.8GB
Qué significa: Recuperaste las capas escribibles de contenedores que no estaban corriendo.
Decisión: Buen primer paso de limpieza. Si el espacio sigue apretado, pasa a podar imágenes/cache de build.
Tarea 9: Podar la cache de build (normalmente seguro, a veces costoso)
cr0x@server:~$ docker builder prune
WARNING! This will remove all dangling build cache.
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
l8p4m2n7k0q1
u2v3w4x5y6z7
Total reclaimed space: 22.8GB
Qué significa: Eliminaste cache de build no usada. Las compilaciones pueden volverse más lentas hasta que la cache se reconstruya.
Decisión: En runners de CI, a menudo es el intercambio correcto. En servidores de build, considera podas programadas con umbrales.
Tarea 10: Previsualiza la poda de imágenes antes de ejecutarla
cr0x@server:~$ docker image prune --all --force --filter 'until=240h'
Deleted Images:
untagged: myapp/api@sha256:1a2b...
deleted: sha256:9c1a2d4e0b2f...
Total reclaimed space: 12.3GB
Qué significa: Se eliminaron imágenes no usadas por ningún contenedor y con más de 10 días (240h).
Decisión: Usa filtros por tiempo en producción. “Borrar todo lo no usado” está bien; “borrar todo lo que no está corriendo ahora” es cómo arruinas tu próximo despliegue.
Tarea 11: Inspecciona el uso de volúmenes e identifica candidatos riesgosos
cr0x@server:~$ docker volume ls
DRIVER VOLUME NAME
local pgdata_main
local redisdata
local myapp_cache
local old_test_volume_42
Qué significa: Los volúmenes existen independientemente de los contenedores. Algunos son claramente datos de producción.
Decisión: Nunca podas volúmenes a ciegas. Identifica qué contenedores los montan y qué almacenan.
Tarea 12: Mapea volúmenes a contenedores (evita borrar datos en vivo)
cr0x@server:~$ docker ps -a --format '{{.ID}} {{.Names}}' | while read id name; do
> echo "== $name ($id) =="
> docker inspect --format '{{range .Mounts}}{{.Name}} -> {{.Destination}} ({{.Type}}){{"\n"}}{{end}}' "$id"
> done
== api-prod-1 (c8b2d9f2a4aa) ==
myapp_cache -> /var/cache/myapp (volume)
== pg-main (e12a9a0110bb) ==
pgdata_main -> /var/lib/postgresql/data (volume)
Qué significa: Ahora sabes qué volúmenes monta cada contenedor.
Decisión: Solo considera eliminar volúmenes que sean (a) no montados y (b) confirmados como prescindibles.
Tarea 13: Mide tamaños reales de volúmenes en disco
cr0x@server:~$ sudo du -sh /var/lib/docker/volumes/*/_data 2>/dev/null | sort -h | tail
2.1G /var/lib/docker/volumes/myapp_cache/_data
18G /var/lib/docker/volumes/redisdata/_data
71G /var/lib/docker/volumes/pgdata_main/_data
Qué significa: Postgres es el de mayor peso. Eso puede ser esperado, o puede ser retención fuera de control.
Decisión: No “limpies Docker” para arreglar el crecimiento de la base de datos. Arregla la retención en la base de datos y planifica capacidad.
Tarea 14 (amigable en emergencia): Encuentra logs JSON de contenedores con tamaño excesivo
cr0x@server:~$ sudo find /var/lib/docker/containers -name '*-json.log' -printf '%s %p\n' 2>/dev/null | sort -n | tail
21474836480 /var/lib/docker/containers/c8b2d9f2a4aa.../c8b2d9f2a4aa...-json.log
5368709120 /var/lib/docker/containers/e12a9a0110bb.../e12a9a0110bb...-json.log
Qué significa: Un log de contenedor tiene 20GB. Eso no es “observabilidad”, es una situación de rehenes.
Decisión: Rota logs correctamente. En una urgencia, puedes truncar el archivo (ver tarea siguiente), pero trátalo como una medida de emergencia.
Tarea 15 (ruptura de cristal): Trunca un log JSON desbocado sin reiniciar el contenedor
cr0x@server:~$ sudo truncate -s 0 /var/lib/docker/containers/c8b2d9f2a4aa.../c8b2d9f2a4aa...-json.log
Qué significa: Recuperaste espacio al instante. Docker sigue escribiendo en el mismo file handle.
Decisión: Haz esto solo cuando la presión de disco es crítica. Luego arregla la rotación de logs para no repetirlo.
Tarea 16: Configura la rotación de logs de Docker (la solución real)
cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
EOF
cr0x@server:~$ sudo systemctl restart docker
Qué significa: Los contenedores nuevos tendrán crecimiento de logs limitado. Los contenedores existentes pueden necesitar reinicio para aplicar opciones.
Decisión: Adopta esto como línea base en todas partes. Si quieres logs centralizados, envíalos—pero aún así limita el crecimiento local.
Tarea 17: Identifica crecimiento dentro de las capas escribibles del contenedor
cr0x@server:~$ docker exec c8b2d9f2a4aa sh -lc 'du -sh /tmp /var/tmp /var/log 2>/dev/null | sort -h'
120M /var/log
5.6G /tmp
32M /var/tmp
Qué significa: La app está usando el sistema de archivos del contenedor para datos temporales voluminosos.
Decisión: Mueve esa ruta a un volumen o tmpfs, o corrige la limpieza de la app. Si no, volverá a crecer tras cada “limpieza”.
Tarea 18: Usa un system prune filtrado por tiempo (con cuidado, pero práctico)
cr0x@server:~$ docker system prune --force --filter 'until=168h'
Deleted Networks:
old_ci_net
Deleted Images:
deleted: sha256:62f13c9d1a11...
Total reclaimed space: 6.4GB
Qué significa: Limpiaste objetos no usados con más de 7 días. Los volúmenes no se tocan a menos que añadas --volumes.
Decisión: Este es el “prune seguro por defecto” para muchos hosts. Reduce riesgo mientras controla el crecimiento.
Tarea 19: Verifica la recuperación de espacio y confirma que el host está estable
cr0x@server:~$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 80G 52G 25G 68% /
Qué significa: Recuperaste margen saludable.
Decisión: No te detengas aquí. Pon guardarraíles: rotación de logs, poda programada y política de cache de build.
Tres micro-historias corporativas desde las trincheras de disco lleno
1) El incidente provocado por una suposición equivocada: “Los volúmenes son solo cache, ¿no?”
Una empresa mediana gestionaba una pequeña flota de hosts Docker para herramientas internas. Nada sofisticado: un contenedor Postgres,
una app web y un worker en background. No era Kubernetes. Era “simple”. Esa palabra tiene víctimas.
Un host llegó al 95% de disco. Un ingeniero entró, vio docker system df y notó que los volúmenes eran la parte más grande.
Habían limpiado recientemente cache de build e imágenes, y el tamaño de volúmenes seguía alto. Así que hicieron lo que siempre sugiere internet cuando estás cansado: docker system prune -a --volumes.
El comando se ejecutó rápido. El disco se liberó dramáticamente. Los dashboards parecían aliviados. Entonces sonó el teléfono de on-call otra vez, más fuerte, porque la base de datos volvió vacía. El contenedor Postgres se reinició con un directorio de datos nuevo. La app web empezó a crear cuentas “primer usuario” admin. Esto no era una nueva funcionalidad.
La causa raíz no fue Docker. La causa raíz fue la suposición de que los volúmenes eran descartables. En ese entorno,
los volúmenes eran el único almacenamiento duradero. La solución fue tanto procedimental como técnica: etiquetar volúmenes por propósito, mapearlos a servicios y proteger los volúmenes de producción de operaciones de prune. También implementaron backups en host porque confiar en “no ejecutaremos el comando malo” no es una estrategia.
La lección perdurable: el comando de limpieza más peligroso es el que ejecutas cuando estás estresado y crees saber qué hace.
2) La optimización que se volvió en contra: “Cacheemos cada build para siempre”
Otro equipo gestionaba un monorepo con builds Docker pesados. Activaron BuildKit y apostaron fuerte por el rendimiento:
cache mounts, builds multi-stage y reutilización agresiva de capas. Su CI se volvió más rápido. El resumen ejecutivo tenía barras más felices. Todos felicitaron la pipeline por estar “optimizada”.
Seis semanas después, el CI empezó a fallar en ráfagas. No eran fallos consistentes—esos serían demasiado amables. Pulls de imágenes fallaban aleatoriamente.
Builds fallaban a mitad de camino. A veces apt no podía escribir archivos temporales. A veces Docker no podía crear capas.
Los fallos se movían entre runners. Los ingenieros culparon al registry. Luego a la red. Luego a los rayos cósmicos.
El problema real: los runners eran “ganado”, pero nadie los reimaginaba. La cache de build creció sin techo.
La raíz de datos de Docker se llenó hasta que el filesystem alcanzó sus bloques reservados y todo degradó. La “optimización”
no era incorrecta; estaba incompleta. El trabajo de rendimiento que ignora la capacidad se convierte en trabajo de fiabilidad, te guste o no.
La parte que salió mal fue cultural: el equipo creía que la cache de build era puramente beneficiosa e inofensiva para producción.
Trataron el disco como infinito porque era invisible. Cuando el disco se llenó, sus “builds rápidos” dejaron de funcionar por completo.
La solución fue aburrida y efectiva: establecer un calendario de poda con umbrales, añadir monitorización de /var/lib/docker,
y reimaginear runners periódicamente. Mantuvieron la mayoría de las mejoras de rendimiento y los fallos dejaron de ser misteriosos.
3) La práctica aburrida pero correcta que salvó el día: “Medir, luego podar, luego verificar”
Un servicio relacionado con pagos corría en un puñado de hosts Docker detrás de un balanceador. El equipo no tenía mucho glamour, pero tenía disciplina. Cada host tenía un pequeño runbook: comprobar disco, comprobar cubos Docker, comprobar logs y solo entonces borrar. También tenían rotación de logs configurada por defecto.
Un fin de semana, el tráfico subió por un problema de un partner. El servicio no cayó por CPU; cayó por logs.
La aplicación empezó a registrar un error por petición. Ese tipo de bug no es sutil: convierte tráfico de usuarios en uso de disco a escala.
El ingeniero de guardia vio la tendencia del disco. Antes de que llegara al 100%, frenó la hemorragia desplegando un cambio de configuración que redujo la verbosidad de logs. Como los logs de Docker rotaban, el host no murió. Luego usaron docker system df
y podaron cache de build e imágenes antiguas de forma controlada, con filtros de tiempo, en toda la flota.
No hubo comando heroico a medianoche. No hubo archivos editados a mano dentro de /var/lib/docker. Solo una respuesta medida que mantuvo margen y restauró operaciones normales. La práctica aburrida y correcta salvó el día—otra vez.
Una estrategia de limpieza segura que funciona en producción
El plan de limpieza correcto depende del tipo de host que tengas. Un runner de CI no es lo mismo que un host con base de datos.
Un portátil de desarrollo no es lo mismo que un nodo de producción. El truco es ajustar la retención al trabajo.
Define lo que nunca debe borrarse automáticamente
En la mayoría de entornos de producción, estas son “clases protegidas”:
- Volúmenes nombrados que contienen datos (bases de datos, colas, cargas de usuario).
- Imágenes necesarias para rollback (al menos una versión anterior).
- Evidencia durante un incidente (contenedores detenidos que planeas inspeccionar).
Si no puedes listar estos para tu entorno, no estás listo para una poda agresiva. Empieza con un prune filtrado por tiempo que no toque volúmenes.
Usa filtros por tiempo para reducir sorpresas
El mejor truco para una limpieza más segura es --filter until=.... Crea un buffer: “no borres nada creado recientemente.”
Ese buffer cubre despliegues en curso, ventanas de rollback y el hecho de que los humanos olvidan qué lanzaron el martes pasado.
Elige una postura de limpieza por rol de host
Postura para runners de CI
- Podar cache de build agresivamente (diario o al superar un umbral).
- Podar imágenes más antiguas que una ventana corta (unos días), porque son reproducibles.
- Preferir reimaginear runners periódicamente; es el recolector de basura más limpio.
- Mantener volúmenes al mínimo; trata cualquier volumen como sospechoso.
Postura para hosts de aplicaciones en producción
- Habilitar rotación de logs en la configuración del daemon de Docker.
- Usar system prune filtrado por tiempo sin volúmenes, en un horario.
- Conservar imágenes de rollback por una ventana definida.
- Alertar sobre uso de disco y crecimiento de la raíz de datos de Docker.
Postura para hosts con estado (bases de datos en Docker)
- No podar volúmenes automáticamente. Nunca. Si debes, usa listas blancas explícitas.
- Planificar la capacidad del crecimiento de volúmenes. “La limpieza de Docker” no es una política de retención de base de datos.
- Usar backups y restauraciones probadas. Los errores al limpiar aquí limitan carreras profesionales.
Chiste #2: Si ejecutas bases de datos en Docker y tu plan de backups es “seremos cuidadosos”, no eres cuidadoso—eres optimista.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: Disco lleno, pero docker system df no muestra suficiente uso
Causa raíz: El espacio está en rutas fuera de Docker (logs del sistema, core dumps), o la raíz de datos de Docker no está donde crees, o el filesystem se quedó sin inodos.
Solución: Revisa df -h y df -i. Encuentra directorios grandes con sudo du -xhd1 /var (y otros montajes). Confirma la raíz de Docker con docker info.
2) Síntoma: Archivos /var/lib/docker/containers/*-json.log son enormes
Causa raíz: No hay rotación de logs configurada para el logger json-file de Docker, o la app está logueando demasiado.
Solución: Configura las opciones de logs en daemon.json; reduce la verbosidad de la app; envía logs fuera del host si es necesario. Trunca solo como medida de emergencia.
3) Síntoma: La limpieza no libera espacio inmediatamente
Causa raíz: Archivos borrados aún están abiertos por un proceso (común con logs), o bloques reservados del filesystem, o peculiaridades de thin-provisioning en ciertos drivers.
Solución: Identifica archivos eliminados abiertos usando lsof (no mostrado en tareas, pero es la jugada). Reinicia el proceso si es seguro. Verifica el espacio liberado real con df, no con esperanzas.
4) Síntoma: Podaste imágenes y ahora un despliegue falla al tirar/compilar
Causa raíz: Eliminaste imágenes locales en las que se apoyaban rollouts rápidos, o tu registry/ruta de red es más lenta/inyestable, o podaste demasiado agresivamente durante una ventana de despliegue.
Solución: Usa filtros por tiempo; coordina horarios de prune fuera de ventanas de despliegue; conserva una ventana de imágenes para rollback; asegura el acceso al registry.
5) Síntoma: El disco crece constantemente aunque podas semanalmente
Causa raíz: El crecimiento está en volúmenes (estado) o dentro de las capas escribibles del contenedor (apps escribiendo datos temporales), no en imágenes/cache.
Solución: Mide tamaños de volúmenes; corrige rutas de la app para usar volúmenes/tmpfs; implementa retención en la capa de aplicación/datos.
6) Síntoma: Tras docker system prune, un servicio pierde datos
Causa raíz: Se podaron volúmenes (--volumes) o un volumen “nombrado” era realmente descartable pero no estaba respaldado; no había guardarraíles.
Solución: Nunca uses --volumes en producción sin mapeo explícito y backups. Usa etiquetas, inventarios y backups con pruebas de restauración.
7) Síntoma: El host está bien, pero operaciones Docker fallan con “no space left on device”
Causa raíz: El filesystem de Docker (o metadatos del driver de almacenamiento) está constreñido, o los inodos están agotados, o el montaje /var está lleno aunque / no lo esté.
Solución: Confirma montajes; comprueba inodos; asegúrate de que la raíz de Docker esté en un filesystem suficientemente grande; considera mover la raíz de datos de Docker.
Listas de verificación / plan paso a paso
Lista de emergencia (disco > 95%, impacto en producción)
- Detén la hemorragia: identifica escritores desbocados (logs, archivos temporales). Si son logs, limita la verbosidad y rota/trunca como medida de emergencia.
- Consigue margen: poda contenedores detenidos, luego cache de build, luego imágenes no usadas con filtro por tiempo.
- Verifica: confirma con
df -hque tienes espacio libre real otra vez. - Estabiliza: añade rotación de logs de Docker si falta. Programa trabajo de seguimiento.
Rutina segura para producción (semanal o diaria, según churn)
- Mide:
docker system dfy regístralo (incluso en un ticket). El trending vence a la conjetura. - Poda:
docker system prune --force --filter 'until=168h'(sin volúmenes). - Poda la cache de build por separado si hay mucho build:
docker builder prune --force --filter 'until=168h'. - Revisa: lista las imágenes top y confirma que la política de retención coincide con la realidad.
- Verifica: comprueba
df -hy los umbrales de alerta.
Plan de endurecimiento “Que no me vuelvan a despertar”
- Habilitar rotación de logs a nivel del daemon de Docker.
- Alertar sobre disco (uso de filesystem) y sobre crecimiento de la raíz de datos de Docker.
- Establecer políticas para runners CI: reimaging periódico + poda agresiva de cache de build.
- Etiquetar e inventariar volúmenes: saber cuáles son datos y cuáles descartables.
- Hacer explícito el rollback: conservar las últimas N imágenes de despliegue, podar el resto por tiempo.
- Planificar capacidad de volúmenes con estado: si la BD crece, es una decisión de producto, no de Docker.
Preguntas frecuentes (FAQ)
1) ¿Es docker system prune seguro en producción?
Con guardarraíles: sí, a menudo. Usa --filter until=... y no añadas --volumes a menos que estés muy seguro.
docker system prune por defecto elimina contenedores detenidos, redes no usadas, imágenes colgantes y cache de build.
2) ¿Cuál es la diferencia entre imágenes colgantes y no usadas?
“Dangling” o colgantes son típicamente capas sin etiqueta dejadas por builds. “No usadas” significa que no están referenciadas por ningún contenedor.
docker image prune apunta por defecto a las colgantes; docker image prune -a apunta también a las no usadas.
3) ¿Por qué Docker mantiene imágenes que no he usado en semanas?
Porque Docker no conoce tu intención. Las imágenes pueden estar ahí para rollbacks rápidos o arranques futuros. Si quieres retención,
debes definirla: filtros por tiempo, podas programadas o políticas en CI.
4) ¿Puedo borrar archivos directamente en /var/lib/docker para ahorrar espacio?
No. Desincronizarás los metadatos de Docker y crearás fallos más difíciles de diagnosticar. Usa los comandos de Docker.
La única excepción que la gente toma en emergencias es truncar logs, y aun eso debe seguirse con rotación adecuada.
5) ¿Por qué la poda no liberó espacio aunque reportó GB recuperados?
Comúnmente porque los archivos eliminados aún están abiertos, especialmente logs. El filesystem no reclamará espacio hasta que
se cierre el último handle. También verifica que estés mirando el montaje correcto y que los inodos no sean el factor limitante.
6) ¿Debería usar docker volume prune?
Solo cuando entiendas exactamente qué significa “volumen no usado” en tu entorno. Los volúmenes no referenciados por
ningún contenedor se consideran no usados—incluso si contienen tu única copia de datos importantes.
7) ¿Cuál es una buena rotación de logs por defecto para Docker?
Para muchos servicios: max-size alrededor de 50–100MB y max-file 3–10. Ajusta según tus necesidades de respuesta a incidentes.
Si los logs son importantes, envíalos fuera. Si los logs son spam, arregla el spam primero.
8) ¿Por qué la cache de build crece tanto en CI?
CI produce muchas capas únicas: commits distintos, dependencias distintas, cache mounts y builds en paralelo.
Sin poda o reimaging, la cache solo crece. No expira automáticamente.
9) ¿Es mejor podar regularmente o reimaginar hosts?
Para runners CI: reimaginear es excelente porque garantiza una base limpia. Para hosts estables de producción: poda regular y conservadora
más monitorización suele ser mejor que reimaginar con frecuencia. Haz ambas cosas cuando tenga sentido.
10) ¿Cuánto espacio libre debo mantener en un host Docker?
Mantén suficiente margen para tu mayor pull/build esperado más picos operacionales (logs, ráfagas de despliegue).
Prácticamente: apunta a al menos 15–25% libre en el filesystem que aloja la raíz de datos de Docker, más en nodos con mucho build.
Siguientes pasos que puedes hacer esta semana
Si quieres menos sorpresas por disco lleno, haz esto en orden:
- Activa la rotación de logs de Docker en
/etc/docker/daemon.jsony reinicia Docker en una ventana de mantenimiento. - Incluye
docker system dfen tu rutina de monitorización: sigue imágenes, volúmenes y crecimiento de cache de build con el tiempo. - Adopta podas filtradas por tiempo en un horario: comienza con
until=168hy ajusta según la cadencia de despliegues. - Haz los volúmenes explícitos: sabe cuáles son datos, quién los posee y cómo se respaldan.
- Arregla a los verdaderos escritores: si tu app escribe datos temporales enormes en el FS del contenedor o logs sin parar, la limpieza es solo una reunión recurrente con el mismo problema.
El disco es barato. Las interrupciones no lo son. Limpia Docker como si gestionaras un sistema de producción: mide primero, borra segundo, verifica siempre.