Saltar al contenido principal
Teramont Logo
Docker llena el disco: diagnostica overlay2 y recupera espacio con limpieza selectiva
Volver al blog

Docker llena el disco: diagnostica overlay2 y recupera espacio con limpieza selectiva

Mizael Segovia

30/7/2026 ·Mizael Segovia· 17 min de lectura ·

3 visualizaciones

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

  1. df -hT está al 100 %: faltan bloques. Averigua qué filesystem contiene Docker y qué categoría consume el espacio.
  2. df -ih está al 100 %: faltan inodos. Busca cantidades enormes de archivos pequeños; borrar un único log grande no resolverá ese caso.
  3. 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.
  4. docker system df -v muestra espacio recuperable: limpia la categoría concreta, de menor a mayor impacto.
  5. 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 probableEvidenciaAcción selectivaRiesgo principal
Contenedores detenidosdocker ps -a y capas SizeRw relevantesEliminar por nombre tras validar su funciónPerder datos que quedaron solo en la capa escribible
Imágenes danglingdocker image ls --filter dangling=truedocker image pruneNecesidad de reconstruir alguna capa de build
Imágenes etiquetadas sin usoSin contenedor asociado; tamaño único altoimage prune -a con filtro de antigüedad, solo si son repullablesPull lento, tag mutable o imagen local irreproducible
Caché de compilaciónBuild cache alto en system dfdocker builder prune con filtroBuild siguiente más lento; caché difícil de reconstruir offline
Log json-file sin rotaciónLogPath grande y driver json-fileConfigurar límite y recrear el contenedorVentana de reinicio y pérdida del historial de log local anterior
Volumen no referenciadodocker system df -v, labels y mounts verificadosBackup y docker volume rm por nombrePérdida irreversible de datos sin backup
Capa escribible en crecimientoSizeRw alto; faltan mounts esperadosMigrar datos a volumen/bind y recrearPérdida de datos o corte si la migración no es consistente
Inodos agotadosdf -ih cerca del 100 %Identificar y retirar de forma administrada objetos con muchos archivosUn prune de objetos grandes puede recuperar bytes pero casi ningún inodo
Archivo borrado aún abiertolsof +L1 con tamaño significativoReiniciar ordenadamente el proceso propietarioInterrupció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.

Premium Character
Ver VPS Hosting
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 df no 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 y SizeRw de 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.

Docker llena el disco: diagnostica overlay2 y recupera espacio con limpieza selectiva
GeneralDockerAdministración de servidoresLinuxVPS
¿Te gustó este artículo?Compártelo:

Sobre el autor

Mizael Segovia

Mizael Segovia

CEO & Desarrollador Full Stack y DevOps en Teramont Host

Continúa explorando guías, noticias y análisis relacionados.

CTA Pattern

¿Necesitas ayuda con tu servidor?

Nuestro equipo está listo para ayudarte con cualquier duda o problema que tengas.

Contáctenos