SSD Nodes Learn 🎉 VPS desde $5.50/mes
Guías Matt ConnorPor Matt Connor

Docker prune: liberar espacio en disco en un VPS

Si el disco de tu VPS está lleno, identifica si lo ocupan imágenes, contenedores, caché o volúmenes y limpia solo lo necesario sin perder datos.

Determine qué está ocupando el espacio en disco antes de eliminar nada

Docker ocupa espacio en disco en un VPS en cuatro lugares: imágenes, contenedores detenidos, caché de compilación y volúmenes locales. Ejecute primero docker system df para saber cuál de ellos ocupa el espacio. Después, ejecute la limpieza más específica que lo libere. El orden es importante porque el último comando de esta guía, docker volume prune -a, elimina datos y no se puede deshacer.

Empiece por el sistema de archivos, no por Docker.

df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -h

df indica la gravedad del problema. du muestra dónde se ha usado el espacio. La opción -x mantiene du en un solo sistema de archivos. Así, no sigue un montaje hasta un volumen independiente ni lo cuenta dos veces. Aquí importan cinco directorios: overlay2 contiene las capas de imágenes y contenedores, volumes contiene los datos de los volúmenes, containers contiene los metadatos de los contenedores y los archivos de registro, buildkit contiene la caché de compilación y image contiene los metadatos de las capas.

Una observación sobre sudo y los comodines del shell, porque hacen perder mucho tiempo. /var/lib/docker pertenece a root y no se puede leer con el usuario normal, por lo que ls /var/lib/docker devuelve Permission denied. Un comando como sudo du -sh /var/lib/docker/* también falla porque el shell expande * antes de que se ejecute sudo, y el shell no puede leer ese directorio. Por ese motivo, todos los comandos siguientes usan find o --max-depth en lugar de un comodín.

Ahora, la vista del propio Docker.

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          24        6         12.4GB    8.91GB (71%)
Containers      31        6         1.42GB    1.39GB (97%)
Local Volumes   14        5         6.03GB    4.11GB (68%)
Build Cache     212       0         9.87GB    9.87GB

Esas cifras proceden de una máquina y no dicen nada sobre la suya. Observe la estructura de los datos. TOTAL cuenta los objetos, ACTIVE cuenta los que están en uso actualmente y RECLAIMABLE es la estimación de Docker de lo que se podría liberar con una limpieza en esa fila.

Hay dos aspectos de RECLAIMABLE que suelen causar confusión. Cuenta las capas de imagen compartidas una vez por cada imagen que las usa, por lo que la fila de imágenes normalmente indica más espacio del que realmente se puede liberar. Además, nunca incluye los archivos de registro de los contenedores porque Docker no considera un archivo de registro un objeto recuperable. Cuando du muestra un directorio mucho más grande de lo que reconoce docker system df, la causa son los archivos de registro. Más adelante hay una sección sobre este tema.

Añada -v para obtener el desglose por objeto.

docker system df -v

Esto divide el resumen en una sección por cada tipo de objeto. La sección de imágenes añade las columnas SHARED SIZE y UNIQUE SIZE, para que pueda ver cuánto ocupa realmente una sola imagen. La sección de volúmenes añade un recuento LINKS, que indica cuántos contenedores están asociados a ese volumen. Recuerde LINKS: un valor de 0 es la única comprobación que necesitan los comandos de limpieza de volúmenes.

Imágenes colgantes frente a imágenes no utilizadas

Estas dos expresiones parecen intercambiables, pero no lo son. Los filtros se comportan de forma diferente porque los objetos son distintos.

Una imagen colgante es una imagen sin etiqueta. Aparece como <none> en docker images. Se crea una en cada reconstrucción: docker build -t myapp:latest . mueve la etiqueta myapp:latest a la imagen nueva, y la imagen anterior conserva todas sus capas, pero pierde su nombre. Nada hace referencia a ella y ningún proceso la elimina automáticamente.

Una imagen no utilizada es cualquier imagen, tenga etiqueta o no, a la que ningún contenedor haga referencia actualmente. Un postgres:16 que descargó el mes pasado y que no está ejecutando ahora no se utiliza, pero no está colgante.

docker image prune        # dangling images only
docker image prune -a     # every image no container refers to

La segunda opción pregunta primero.

WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]

Lea atentamente ese aviso. «Asociados a ellos» significa un objeto de contenedor existente, en ejecución o detenido. Si ejecutó docker compose down, los contenedores desaparecieron. Por tanto, todas las imágenes que utilizaban esos servicios dejan de estar en uso y -a las elimina todas. No se pierde nada que no pueda recuperar, pero el siguiente docker compose up -d vuelve a descargarlas o reconstruirlas todas. En una VPS pequeña, esto consume ancho de banda y tiempo de compilación. Por eso es práctico saber qué elimina docker compose down y qué deja en ejecución stop antes de eliminar imágenes.

Un filtro excluye del análisis las imágenes recientes.

docker image prune -a --filter "until=240h"

Esto elimina las imágenes no utilizadas creadas hace más de 240 horas (10 días) y conserva las más recientes. El valor until acepta una cadena de duración de Go, como 240h, o una marca de tiempo absoluta, como 2026-08-01T00:00:00.

Qué es la caché de compilación y por qué crece sin límite

BuildKit es el compilador que Docker usa de forma predeterminada para docker build y docker compose build desde Docker Engine 23.0. Guarda en caché el resultado de cada paso de cada Dockerfile que ejecuta, y conserva esa caché en /var/lib/docker/buildkit. La caché explica por qué la segunda compilación termina en segundos, así que cumple su función. El problema es que, de forma predeterminada, nada caduca las entradas antiguas. Compile la misma imagen cincuenta veces con un paso COPY que cambie cada vez y conservará cincuenta conjuntos de capas.

La caché de compilación es invisible para docker image prune. Es un tipo de objeto independiente con su propio comando.

docker builder prune                        # dangling cache
docker builder prune -a                     # all unused cache
docker builder prune --filter until=168h    # cache untouched for 7 days

Ninguno de estos comandos afecta a sus imágenes ni a sus datos. El único coste de borrar la caché de compilación es que la siguiente compilación se ejecuta lentamente una sola vez. En un VPS que recompila imágenes con frecuencia, Build Cache suele ser la fila más grande de docker system df, lo que lo convierte en el elemento grande más seguro que puede eliminar.

Los comandos de limpieza, ordenados de menor a mayor riesgo

Siga esta lista y deténgase en cuanto df -h / vuelva a mostrar un estado normal. Cada comando muestra una línea Total reclaimed space: al terminar.

  1. docker container prune elimina los contenedores detenidos. También elimina sus capas de escritura, por lo que se borra todo lo que un contenedor haya escrito fuera de un volumen. Los volúmenes no se modifican.
  2. docker image prune elimina únicamente las imágenes huérfanas. Es el comando de imágenes más seguro.
  3. docker builder prune elimina la caché de compilación huérfana. El coste es una compilación lenta.
  4. docker image prune -a elimina todas las imágenes que no utiliza ningún contenedor. El coste es volver a descargarlas o compilarlas.
  5. docker system prune ejecuta los tres primeros pasos a la vez y añade las redes no utilizadas.
  6. docker volume prune elimina los volúmenes anónimos no utilizados.
  7. docker volume prune -a elimina los volúmenes no utilizados, incluidos los volúmenes con nombre. Este es el comando que elimina bases de datos.

docker system prune indica su propio alcance antes de ejecutarse.

WARNING! This will remove:
        - all stopped containers
        - all networks not used by at least one container
        - all dangling images
        - unused build cache
Are you sure you want to continue? [y/N]

Los volúmenes se excluyen deliberadamente de esa lista. Añadir --volumes incluye de nuevo los volúmenes anónimos. Añadir -a amplía el paso de imágenes, de las imágenes huérfanas a todas las imágenes no utilizadas. Ejecutar un docker system prune -a --volumes -f completo en un host de producción es una forma de perder datos al intentar liberar espacio.

Por qué la limpieza de volúmenes elimina la base de datos

Esta es la sección que debe leer dos veces.

Un volumen se considera no utilizado cuando ningún contenedor está conectado a él. Esa es toda la comprobación. Docker no verifica si el volumen está vacío, si un archivo compose todavía lo declara ni si contiene la única copia de su base de datos. LINKS 0 en docker system df -v significa que se puede limpiar, y no significa nada más.

Ahora ejecute dos acciones habituales seguidas. Ejecuta docker compose down para reiniciar una pila de forma limpia. Ese comando elimina los contenedores y deja intactos los volúmenes con nombre, que es exactamente lo que indica su documentación. Su volumen de Postgres ya no está conectado a ningún contenedor. Diez minutos después ejecuta docker volume prune -a para liberar espacio, y la base de datos desaparece. Ambos comandos funcionaron correctamente. La secuencia destruyó los datos.

Desde Docker Engine 23.0 (versión de API 1.42), el comando simple es más restrictivo que antes.

WARNING! This will remove anonymous local volumes not used by at least one container.

Un volumen anónimo es un volumen que Docker crea automáticamente, normalmente porque una imagen declara VOLUME y usted nunca le asignó un nombre. Estos volúmenes suelen contener datos que no tenía intención de conservar. Un volumen con nombre, como el que escribió en su archivo compose, sólo se elimina cuando añade -a. Las versiones antiguas de Docker eliminaban ambos tipos con el comando simple. Por tanto, no confíe en hábitos adquiridos en un equipo que después haya actualizado. La diferencia sólo se entiende cuando sabe en qué se diferencian los volúmenes con nombre de los bind mounts, porque un bind mount no es un volumen de Docker y ningún comando prune lo tocará.

Revise antes de eliminar. Sustituya myapp_pgdata por el nombre del volumen que está comprobando.

docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_data

El filtro dangling=true aplicado a un volumen significa sin referencias, no vacío. Mostrar _data le permite ver qué contiene realmente. Si encuentra un directorio pgdata o mysql, deténgase y haga una copia antes de continuar. La misma destrucción puede producirse mediante docker compose down -v, que elimina todos los volúmenes declarados por el archivo compose sin pedir confirmación.

Un volumen es el único elemento de un host de Docker que una reconstrucción no puede volver a crear. Por eso los datos de los volúmenes deben incluirse en un backup de restic que se ejecute fuera del servidor, donde un indicador escrito por error no pueda afectarlos.

Cuando no se libera espacio: los archivos de registro de los contenedores

Ha eliminado todo lo que se podía podar, docker system df muestra que casi no hay espacio recuperable y el disco sigue lleno. Revise los registros.

sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10

Cada contenedor escribe su salida estándar y su salida de error estándar en un archivo JSON bajo /var/lib/docker/containers/. En una instalación predeterminada, max-size no está definido, lo que significa que no hay límite. Por tanto, un solo contenedor atrapado en un bucle de fallos puede escribir hasta llenar la partición. Ningún comando de poda elimina estos archivos, porque los contenedores que los generan están en ejecución y, por definición, no se pueden podar.

No elimine el archivo. Ejecutar rm sobre un archivo de registro abierto no libera espacio, porque el daemon de Docker mantiene abierto un descriptor de archivo y el kernel conserva esos bloques asignados hasta que se cierre ese descriptor. df no cambiará en absoluto. En su lugar, trunque el archivo. Así se conserva el mismo inode y el daemon puede seguir escribiendo.

sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /

Es una solución temporal. docker logs para esos contenedores ya no devuelve nada y los archivos vuelven a crecer de inmediato. La solución real es configurar la rotación, que se explica en la sección siguiente.

Mida antes y después, siempre

No suponga lo que hizo un prune. Tome una medición, ejecute un comando y tome otra medición.

df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /

Compare las dos salidas de df. Ese es el único número que determina si el servidor sigue prestando servicio. docker system df indica qué fila cambió realmente, y cada prune muestra su propia cifra de Total reclaimed space:.

Si df no cambió, pero docker system df indica que se liberó espacio, un descriptor de archivo abierto mantiene los bloques eliminados. Ese es el problema del archivo de registro descrito arriba. Si ambos cambiaron y el disco vuelve a llenarse en un día, tiene un problema de crecimiento, no de limpieza. La solución es configurar la rotación y un trabajo programado.

Cómo evitar que el disco vuelva a llenarse

Limite el tamaño de los registros. Cree o edite /etc/docker/daemon.json.

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Esto limita a 30 MB los registros de cada contenedor. Todos los valores de log-opts deben ser cadenas, incluidos los valores numéricos. Compruebe que el archivo se pueda analizar antes de reiniciar, porque un daemon.json incorrecto impide que el daemon se inicie y deja sin servicio a todos los contenedores.

python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'

docker info ahora debería mostrar Logging Driver: json-file. Los límites aparecen en la sección LogConfig de docker inspect para un contenedor creado después del reinicio. Este es el detalle importante: esta configuración sólo se aplica a los contenedores nuevos. Los contenedores existentes conservan la configuración con la que se crearon, por lo que debe recrearlos.

docker compose up -d --force-recreate

El mismo límite se puede definir por servicio en un archivo compose. Esta es la mejor opción cuando un servicio genera demasiados registros y necesita un valor propio.

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Programe una limpieza selectiva. Ejecútela semanalmente y limítela a imágenes huérfanas y a la caché de compilación antigua. No incluya -a ni --volumes en una tarea programada, porque una tarea que se ejecute mientras una pila está detenida eliminará las imágenes de esa pila y, con --volumes, empezará a actuar sobre sus datos.

sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-prune

La última línea ejecuta el script una vez manualmente para que pueda ver su salida antes de que se ejecute sin supervisión. El archivo debe ser ejecutable y su nombre no debe contener un punto, porque run-parts omite todo lo que no sea ejecutable y todo lo que tenga una extensión.

Configure una alerta de espacio libre. Una limpieza que se ejecuta después de que el disco se llena sirve para recuperar el servicio. Una alerta al 80 por ciento sirve para prevenir el problema.

usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"

Añádala a cron con el notificador que ya utilice. El espacio libre sólo muestra una parte del problema, así que combine la alerta con la supervisión del estado del disco en su VPS, porque un disco defectuoso y un disco lleno detienen los contenedores, pero requieren soluciones distintas.

Todo lo anterior presupone una instalación estándar con la raíz de datos en /var/lib/docker. Si la cambió mediante la clave data-root en daemon.json, sustituya la ruta en todos los comandos. Configurar correctamente esta estructura en un servidor nuevo forma parte de la configuración de Docker en un VPS, y es mucho más fácil decidirlo antes de tener 40 GB de contenedores en la partición incorrecta.

FAQ

¿docker system prune elimina mis volúmenes?

No. El comando básico elimina los contenedores detenidos, las redes no utilizadas, las imágenes huérfanas y la caché de compilación no utilizada. Su solicitud de confirmación enumera exactamente ese conjunto. Los volúmenes sólo se incluyen cuando añade --volumes. Desde Docker Engine 23.0, esa opción incluye los volúmenes anónimos, no los nominales. Los volúmenes nominales se eliminan con docker volume prune -a y docker compose down -v. Esos son los dos comandos que requieren especial cuidado.

¿Por qué el disco sigue lleno después de ejecutar docker prune?

Hay dos causas habituales. La primera son los archivos de registro de los contenedores en /var/lib/docker/containers/. Ningún comando prune los toca y crecen sin límite hasta que configura max-size. La segunda es un archivo eliminado que un proceso mantiene abierto. Si eliminó un registro con rm mientras su contenedor estaba en ejecución, el daemon conserva el descriptor de archivo y el kernel no libera los bloques. Por eso df no muestra ningún cambio. Compare sudo du -xh --max-depth=1 /var/lib/docker con docker system df para determinar cuál de las dos situaciones tiene.

¿Cuál es la diferencia entre docker image prune y docker image prune -a?

El comando básico elimina sólo las imágenes huérfanas, es decir, las imágenes que han perdido su etiqueta, casi siempre debido a una recompilación. La forma -a elimina todas las imágenes a las que ningún contenedor existente hace referencia, incluidas las imágenes etiquetadas que descargó deliberadamente. Después de ejecutar docker compose down, los contenedores han desaparecido, por lo que -a también eliminará las imágenes de esa pila. No se pierde nada de forma permanente, porque el siguiente inicio las vuelve a descargar o recompilar. Sin embargo, con una conexión lenta, la espera puede ser larga.

¿Cómo evito que los registros de Docker llenen el disco?

Configure max-size y max-file en log-opts dentro de /etc/docker/daemon.json. Después, reinicie el daemon con sudo systemctl restart docker. La configuración sólo se aplica a los contenedores creados después de ese reinicio. Por tanto, vuelva a crear los que estén en ejecución con docker compose up -d --force-recreate. Puede establecer las mismas dos opciones para cada servicio en un archivo compose, dentro de una clave logging. Esto es útil cuando un servicio genera muchos más registros que los demás.

¿Es seguro ejecutar docker system prune desde una tarea cron?

El comando docker system prune -f básico es seguro en un host donde todas las pilas permanecen en ejecución. Sin embargo, elimina los contenedores detenidos. Por tanto, eliminará un contenedor que haya detenido deliberadamente y que pensaba iniciar de nuevo más adelante. La tarea programada más segura es docker image prune -f junto con docker builder prune -f --filter until=168h. Libera los dos recursos que crecen más rápido y no puede tocar ningún volumen. Nunca programe -a ni --volumes.