Respuesta corta: si Docker muestra no space left on device y parece que overlay2 o /var/lib/docker llenó el disco, no empieces borrando directorios ni ejecutando un prune global. Primero comprueba si se agotaron los bloques o los inodos; después identifica el directorio raíz y el backend real de Docker; por último atribuye el consumo a imágenes, caché de compilación, logs, capas escribibles o volúmenes. Esa secuencia permite liberar espacio con una acción cuyo impacto conoces.
Advertencia: crea un snapshot recuperable del VPS y un backup consistente de los datos de la aplicación antes de eliminar objetos. Un snapshot del disco no sustituye por sí solo el dump de una base de datos activa. No borres ni muevas manualmente nada dentro de /var/lib/docker: Docker administra esas rutas y su propia documentación advierte contra su manipulación directa. Los objetos eliminados con prune no tienen rollback local; las imágenes se vuelven a descargar o construir, pero un volumen eliminado solo vuelve desde un backup.
Árbol de decisión para recuperar el host
df -hTestá al 100 %: faltan bloques. Averigua qué filesystem contiene Docker y qué categoría consume el espacio.df -ihestá al 100 %: faltan inodos. Busca cantidades enormes de archivos pequeños; borrar un único log grande no resolverá ese caso.- Ambos tienen margen: revisa cuotas, archivos borrados que siguen abiertos, un filesystem distinto al que estabas observando o el límite de la capa escribible del contenedor.
docker system df -vmuestra espacio recuperable: limpia la categoría concreta, de menor a mayor impacto.- El comando muestra 0 B recuperables: no repitas prune. Investiga logs, volúmenes, bind mounts, capas escribibles, archivos abiertos y el almacén de containerd.
Síntomas habituales y qué significan
El mismo mensaje puede aparecer al extraer una imagen, crear una capa, escribir un log, iniciar una base de datos o crear un archivo temporal. overlay2 puede figurar en el error porque es el punto de montaje que recibió la escritura, no porque haya una carpeta huérfana que deba borrarse. En el driver clásico, OverlayFS combina capas de imagen de solo lectura con una capa superior escribible por contenedor; la explicación de Docker sobre overlay2 detalla esa relación entre lowerdir, upperdir y merged.
- Bloques agotados: imágenes antiguas, caché de builds, logs sin rotación, datos persistentes o archivos creados dentro de la capa escribible.
- Inodos agotados: millones de archivos pequeños en capas, cachés de aplicación o volúmenes.
- El root filesystem se llenó aunque Docker usa otro disco: logs del sistema, archivos borrados aún abiertos o el almacén de containerd quedó en su ruta predeterminada.
- Un contenedor falla y el host no: revisa límites, cuotas y el filesystem concreto montado en ese contenedor.
Prerrequisitos y red de seguridad
Este runbook está pensado para Docker Engine y Docker Compose sobre Ubuntu o Debian. Necesitas una cuenta con acceso a Docker y sudo, conocer qué servicios son stateful y disponer de una ventana para recrear un contenedor si su log o capa escribible es el problema. No presupone que los comandos se hayan ejecutado en infraestructura de Teramont.
Antes de una limpieza, toma un snapshot desde el proveedor si está disponible y verifica que conoces el procedimiento de restauración. Para PostgreSQL, MySQL u otra base activa, realiza además un backup consistente con la herramienta de la aplicación. Confirma dónde viven uploads, bases y configuraciones: volumen nombrado, bind mount, almacenamiento remoto o capa escribible. Si un dato importante solo está en la capa del contenedor, copiarlo o respaldarlo es prioritario; recrear el contenedor lo perderá.
Guarda también un inventario. Ayuda a reconstruir referencias, pero no es un backup de los datos:
umask 077
inventory="$HOME/docker-recovery-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$inventory"
docker ps -a --no-trunc > "$inventory/containers.txt"
docker image ls --digests --no-trunc > "$inventory/images.txt"
docker volume ls > "$inventory/volumes.txt"
ids=$(docker ps -aq)
[ -z "$ids" ] || docker inspect $ids > "$inventory/container-inspect.json"
Diagnóstico paso a paso
1. Distingue bloques, inodos y archivos abiertos
Empieza por el host, no por Docker. Ejecuta las dos variantes de df sobre los filesystems montados:
df -hT
df -ih
findmnt -T /var/lib/docker
sudo lsof +L1
En df -hT, observa Use%; en df -ih, IUse%. findmnt muestra qué filesystem respalda la ruta, aunque más adelante debes confirmar si esa es realmente el Docker Root Dir. Si df reporta mucho más uso que du, lsof +L1 puede revelar archivos ya borrados que un proceso mantiene abiertos. Reiniciar de forma controlada solo el proceso propietario libera esos bloques; no reinicies servicios a ciegas.
2. Consulta versión, Docker Root Dir y backend
docker version --format 'Client={{.Client.Version}} Server={{.Server.Version}}'
docker info --format 'DockerRootDir={{.DockerRootDir}}'
docker info --format 'StorageDriver={{.Driver}}'
docker info --format 'DriverStatus={{json .DriverStatus}}'
Una guía centrada únicamente en overlay2 puede no corresponder a un host nuevo. En instalaciones nuevas, Docker Engine 29.0 o posterior usa por defecto el containerd image store; un host actualizado desde una versión anterior sigue normalmente con los graph drivers clásicos hasta que se habilita la migración. containerd usa snapshotters —overlayfs por defecto— en vez de overlay2 como driver clásico. La página de selección de storage driver explica la transición.
Busca io.containerd.snapshotter.v1 en DriverStatus para identificar el almacén de containerd. En el backend clásico verás normalmente StorageDriver=overlay2. No cambies de backend como maniobra de limpieza: al cambiar, imágenes y contenedores del backend anterior pueden quedar ocultos pero seguir ocupando disco. Además, containerd conserva capas comprimidas y extraídas y puede usar una ruta separada del directorio de datos de Docker; si se configuró data-root en otro disco, la ruta de containerd no se mueve automáticamente.
3. Mide el filesystem correcto
docker_root=$(docker info --format '{{.DockerRootDir}}')
printf 'Docker Root Dir: %s\n' "$docker_root"
df -hT "$docker_root"
df -ih "$docker_root"
sudo du -xhd1 "$docker_root" 2>/dev/null | sort -h
[ ! -d /var/lib/containerd ] || sudo du -xhd1 /var/lib/containerd 2>/dev/null | sort -h
du aquí es un inventario de solo lectura, no una invitación a eliminar subdirectorios. El modificador -x evita cruzar a otros filesystems. Si containerd tiene una ruta personalizada, sustituye /var/lib/containerd por la que defina su servicio. No asumas que el mayor directorio es prescindible.
4. Atribuye el consumo que Docker conoce
docker system df
docker system df -v
docker ps -a --size --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Size}}'
docker system df separa imágenes, contenedores, volúmenes locales y caché de build; con -v muestra el detalle y distingue tamaño compartido y único de las imágenes. La columna RECLAIMABLE es una estimación bajo las reglas de Docker, no una promesa de que todo pueda borrarse sin impacto.
docker system df no cuenta de forma completa todo lo que puede llenar el host: los logs de contenedores, bind mounts, datos externos, archivos borrados pero abiertos y ciertas rutas del almacén de containerd requieren comprobaciones adicionales.
5. Separa capas escribibles de datos persistentes
docker ps -a --size
docker inspect --format '{{range .Mounts}}{{println .Type .Name .Source .Destination}}{{end}}' NOMBRE_CONTENEDOR
docker inspect --size --format 'SizeRw={{.SizeRw}} SizeRootFs={{.SizeRootFs}}' NOMBRE_CONTENEDOR
SizeRw aproxima lo escrito por el contenedor sobre su imagen. Si crece, la aplicación quizá está guardando uploads, caché, temporales o una base dentro de la capa efímera. Mueve datos duraderos a un volumen o bind mount mediante una migración respaldada; no copies a mano desde overlay2. Los volúmenes de Docker tienen un ciclo de vida independiente del contenedor y son el mecanismo preferido para datos persistentes administrados por Docker.
6. Localiza logs grandes sin manipularlos
docker ps -aq | while read -r id; do
name=$(docker inspect --format '{{.Name}}' "$id")
path=$(docker inspect --format '{{.LogPath}}' "$id")
[ -n "$path" ] || continue
bytes=$(sudo stat -c '%s' "$path" 2>/dev/null || printf '0')
printf '%s\t%s\t%s\n' "$bytes" "$name" "$path"
done | sort -nr
Este bucle solo consulta la ruta administrada por Docker y lee metadatos con stat. No abras para escribir, trunques, rotes con logrotate ni borres esos archivos. Docker advierte que los archivos del driver json-file deben ser accedidos exclusivamente por el daemon; una intervención externa puede romper el sistema de logging. La solución durable es configurar rotación y recrear de forma controlada el contenedor.
Matriz causa, evidencia, acción y riesgo
| Causa probable | Evidencia | Acción selectiva | Riesgo principal |
|---|---|---|---|
| Contenedores detenidos | docker ps -a y capas SizeRw relevantes | Eliminar por nombre tras validar su función | Perder datos que quedaron solo en la capa escribible |
| Imágenes dangling | docker image ls --filter dangling=true | docker image prune | Necesidad de reconstruir alguna capa de build |
| Imágenes etiquetadas sin uso | Sin contenedor asociado; tamaño único alto | image prune -a con filtro de antigüedad, solo si son repullables | Pull lento, tag mutable o imagen local irreproducible |
| Caché de compilación | Build cache alto en system df | docker builder prune con filtro | Build siguiente más lento; caché difícil de reconstruir offline |
| Log json-file sin rotación | LogPath grande y driver json-file | Configurar límite y recrear el contenedor | Ventana de reinicio y pérdida del historial de log local anterior |
| Volumen no referenciado | docker system df -v, labels y mounts verificados | Backup y docker volume rm por nombre | Pérdida irreversible de datos sin backup |
| Capa escribible en crecimiento | SizeRw alto; faltan mounts esperados | Migrar datos a volumen/bind y recrear | Pérdida de datos o corte si la migración no es consistente |
| Inodos agotados | df -ih cerca del 100 % | Identificar y retirar de forma administrada objetos con muchos archivos | Un prune de objetos grandes puede recuperar bytes pero casi ningún inodo |
| Archivo borrado aún abierto | lsof +L1 con tamaño significativo | Reiniciar ordenadamente el proceso propietario | Interrupción del servicio |
Limpieza selectiva, de menor a mayor impacto
La documentación de pruning de Docker explica que los objetos no se eliminan automáticamente y que cada tipo tiene su propio comando. Usa esa granularidad. Antes y después de cada acción, registra df y docker system df; así sabrás qué recuperó realmente espacio.
1. Contenedores detenidos que ya identificaste
docker ps -a --filter status=exited --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}'
docker inspect NOMBRE_CONTENEDOR
docker rm NOMBRE_CONTENEDOR
Elimina por nombre o ID después de comprobar mounts, labels y propósito. docker container prune --filter "until=168h" es una alternativa por lote, pero alcanza todos los contenedores detenidos que coincidan; revisa primero la lista. Un contenedor detenido todavía puede conservar una capa escribible o servir como referencia para una imagen.
2. Imágenes: primero dangling, después las no usadas
Opera tus contenedores sobre una base preparada
Conoce las opciones de VPS Hosting de Teramont para desplegar y administrar tus servicios Docker.


docker image ls --filter dangling=true
docker image prune
docker image prune -a --filter "until=168h"
El primer prune elimina solo imágenes dangling. La variante -a alcanza todas las imágenes no usadas por algún contenedor, incluidas imágenes etiquetadas; ejecútala únicamente si puedes volver a descargarlas o construirlas y has revisado el filtro. No uses -f durante una recuperación manual: la confirmación es una última oportunidad para detectar un alcance inesperado.
3. Caché de build
docker system df -v
docker builder prune --filter "until=168h"
La caché suele ser recuperable sin tocar datos de aplicación, pero el siguiente build será más lento y puede fallar si dependía de artefactos que ya no están disponibles. Si usas builders Buildx separados, inspecciónalos con docker buildx ls y docker buildx du antes de aplicar el prune correspondiente.
4. Volúmenes: inventario, backup y borrado explícito
docker volume ls --filter dangling=true
docker volume inspect NOMBRE_VOLUMEN
docker volume rm NOMBRE_VOLUMEN
Que un volumen aparezca como no referenciado no demuestra que sus datos sean prescindibles. Revisa su nombre, labels, proyecto Compose y contenido lógico; realiza un backup restaurable y elimina solo el nombre aprobado. Evita docker volume prune en la primera respuesta a un incidente.
docker system prune no elimina volúmenes por defecto. Sin embargo, añadir --volumes cambia el riesgo, y combinar -a --volumes amplía mucho el alcance. No presentes ni uses docker system prune -a --volumes como atajo inicial: no existe un “deshacer” local para los volúmenes borrados.
¿Por qué prune recuperó 0 B?
Prune solo elimina objetos que cumplen sus criterios. Si devuelve 0 B, una de estas explicaciones suele ser más útil que repetirlo:
- El espacio está en logs de contenedores activos;
system dfno los refleja como imágenes recuperables. - Los datos están en un volumen en uso o en un bind mount. No deben considerarse basura.
- Una aplicación escribe en la capa de un contenedor activo; el objeto no es prunable.
- El filesystem está sin inodos, aunque los objetos candidatos ocupen pocos bytes.
- Un proceso retiene un archivo borrado; el nombre desapareció, pero los bloques no.
- El almacén de containerd o su ruta separada ocupa la partición, o un cambio de backend dejó datos anteriores ocultos.
- Docker comparte capas: el tamaño virtual parece alto, pero el tamaño único que realmente se libera es pequeño.
Si no hay objetos seguros que eliminar, la opción correcta puede ser ampliar el filesystem, adjuntar almacenamiento y planificar el traslado del data root, o corregir la aplicación. No sacrifiques un volumen de producción para ganar unos minutos.
Prevenir que los logs vuelvan a llenar el disco
Docker usa json-file por defecto por compatibilidad y, sin opciones, no rota los logs. La guía oficial de configuración de logging recomienda el driver local para muchos casos porque rota y usa un formato más eficiente. Si necesitas conservar json-file por integración, limita max-size y max-file.
Primero respalda el archivo existente. Si ya contiene otras opciones —registry mirrors, data-root, métricas o configuración del snapshotter— combina las nuevas claves; no reemplaces el documento completo.
sudo install -d -m 0755 /etc/docker
if sudo test -f /etc/docker/daemon.json; then
sudo cp -a /etc/docker/daemon.json "/etc/docker/daemon.json.bak.$(date +%Y%m%d-%H%M%S)"
fi
sudoedit /etc/docker/daemon.json
Una política conservadora con json-file puede ser:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Todos los valores de log-opts en daemon.json deben ser strings. Los límites son un ejemplo operativo, no una cifra universal: ajústalos a tu retención, tasa de logs y sistema de observabilidad. Como alternativa, el driver local rota y comprime por defecto.
Valida sintaxis y configuración antes de reiniciar:
sudo python3 -m json.tool /etc/docker/daemon.json >/dev/null
sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
docker info --format 'LoggingDriver={{.LoggingDriver}}'
Un JSON válido aún puede contener opciones incompatibles o duplicadas con flags de systemd; por eso se incluye la validación de dockerd. Si falla, no reinicies: corrige el archivo o restaura el backup.
Compose: el valor por defecto no cambia contenedores existentes
Reiniciar el daemon no actualiza el driver de logging de contenedores ya creados. La nueva configuración por defecto se aplica a contenedores nuevos. Para hacer explícita la política por servicio en Compose:
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Valida el archivo y recrea un servicio a la vez, después de confirmar que sus datos persistentes están montados y respaldados:
docker compose config
docker compose up -d --no-deps --force-recreate app
docker compose ps
La recreación elimina la capa escribible y el log administrado del contenedor anterior. Eso recupera un log grande sin truncarlo a mano, pero también elimina cualquier dato que la aplicación hubiese guardado fuera de volúmenes o bind mounts. No uses docker compose down -v: -v solicita eliminar volúmenes del proyecto.
Verificación posterior
docker_root=$(docker info --format '{{.DockerRootDir}}')
df -hT "$docker_root"
df -ih "$docker_root"
docker system df
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker info --format 'LoggingDriver={{.LoggingDriver}}'
Confirma cinco cosas: hay margen tanto en bloques como en inodos; los contenedores esperados están sanos; la aplicación responde; los mounts persistentes siguen presentes; y el consumo deja de crecer a la velocidad anterior. Revisa también la ruta de containerd si vive en otro filesystem. Una recuperación no está cerrada hasta probar escritura y lectura de la aplicación, no solo hasta ver un porcentaje menor en df.
Rollback
Si Docker no inicia tras editar daemon.json, restaura el backup más reciente, vuelve a validar y arranca el daemon. Si cambiaste únicamente el logging por defecto, restaurarlo no convierte automáticamente los contenedores ya recreados: cada contenedor conserva el driver asignado al crearse y requerirá otra recreación planificada.
Los contenedores, imágenes y cachés eliminados no se “restauran” desde Docker: recrea los servicios desde Compose, vuelve a hacer pull o build, y recupera datos solo desde backups verificados. Si una eliminación afectó un volumen, detén las escrituras de la aplicación antes de restaurarlo para no mezclar estados.
Monitoreo y mantenimiento
- Alerta por porcentaje de bloques y de inodos en el filesystem que contiene Docker y en la ruta separada de containerd.
- Registra periódicamente
docker system df, tamaño de logs ySizeRwde contenedores; observa la tendencia, no solo un umbral. - Define retención de logs en el daemon o por servicio y envía los eventos importantes a un sistema externo.
- Automatiza solo prunes acotados, con filtros de antigüedad y exclusiones mediante labels, después de probar qué builders e imágenes necesita el despliegue.
- Ensaya restauraciones de volúmenes y bases. Un backup que nunca se restauró es una hipótesis.
Troubleshooting rápido
Docker no arranca porque el disco está completamente lleno
Recupera primero un margen pequeño fuera de los datos administrados por Docker: limpia caches del sistema que conozcas, rota logs del sistema con sus herramientas o amplía temporalmente el filesystem. Después usa las acciones selectivas de este runbook. No hagas rm dentro de /var/lib/docker para “hacer que arranque”.
du y df no coinciden
Revisa archivos abiertos y borrados con lsof +L1, puntos de montaje ocultos, snapshots del filesystem y si du cruzó o excluyó otro mount. Docker puede no ser el consumidor real aunque el fallo aparezca durante una operación Docker.
El directorio overlay2 sigue grande tras limpiar
Las capas en uso, compartidas por varias imágenes o referenciadas por contenedores deben permanecer. Compara tamaño único, contenedores asociados y capa escribible. Si el backend cambió, puede haber datos del almacén anterior que quedaron ocultos; documenta el cambio y planifica su retiro con backup, no borres directorios por comparación visual.
Preguntas frecuentes
¿Es seguro docker system prune?
Es seguro solo si su alcance coincide con lo que puedes reconstruir. Por defecto elimina contenedores detenidos, redes no usadas, imágenes dangling y caché de build; no volúmenes. Aun así, un contenedor detenido puede contener datos en su capa escribible. Lee la confirmación y prefiere los prunes por tipo.
¿Puedo borrar /var/lib/docker/overlay2 manualmente?
No. Los IDs de directorio no son una lista fiable de “sobrantes” y Docker administra referencias y mounts. Usa la CLI y un procedimiento de migración o reinstalación respaldado si realmente necesitas reconstruir el almacén.
¿Borrar una imagen elimina mis datos?
Una imagen no debería contener los datos persistentes de una aplicación, pero puede ser una compilación local irreproducible. Docker no elimina una imagen que usa un contenedor existente con el prune normal; aun así, conserva el Dockerfile, tags inmutables o digest y acceso al registro.
¿Qué hago si se agotaron los inodos?
Identifica qué objeto genera muchos archivos pequeños. Elimina de forma administrada el contenedor, caché o volumen aprobado, o migra la carga a un filesystem dimensionado para ese patrón. Liberar un log de varios gigabytes puede devolver bloques y cero inodos útiles.
¿Containerd significa que ya no uso OverlayFS?
No exactamente. El almacén de containerd puede usar el snapshotter overlayfs; lo que cambia es la arquitectura de almacenamiento y las rutas, no necesariamente el mecanismo del kernel. Por eso debes consultar docker info y no deducir el backend a partir de un nombre visto en un error.








