Docker build cache llena el disco: docker builder prune

El disco de un servidor Linux está al 60 % y no sabes por qué. Las bases de datos pesan unos pocos MB, la aplicación apenas ocupa espacio y aun así el sistema de archivos raíz va llenándose con cada despliegue:

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       193G  115G   78G  60% /

Antes de contratar un servidor más grande, revisa lo que Docker guarda en el disco. Cuando los despliegues construyen las imágenes en el propio servidor, la caché de compilación (build cache) puede acumular decenas de GB sin que ningún contenedor ni imagen lo muestre.

Por qué pasa

BuildKit, el motor de compilación de Docker, guarda el resultado intermedio de cada paso de un build para reutilizarlo en el siguiente. Esa caché es independiente de las imágenes, los contenedores y los volúmenes: no aparece en docker images ni en docker ps, y el tamaño de tus bases de datos no influye. Cada build que cambia capas añade registros nuevos.

La documentación de Docker explica que BuildKit tiene un recolector de basura que limpia la caché de forma periódica según unas políticas, a diferencia de docker builder prune, que lo hace a petición. Aun así, en la práctica la caché puede llegar a ocupar decenas de GB, como en este ejemplo, así que conviene medirla en lugar de asumir que se limpia sola. El comando que lo muestra es este:

$ docker system df
TYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE
Images          ...
Containers      ...
Local Volumes   ...
Build Cache     ...     ...      107.7GB   100.1GB

En este ejemplo, la fila Build Cache ocupa 107,7 GB y 100,1 GB son recuperables. Las demás filas se omiten porque lo relevante es la diferencia de tamaño.

Solución

  1. Diagnostica qué ocupa el espacio.

    df -h /
    docker system df
    docker system df -v
    docker buildx du

    docker system df -v detalla el uso de cada imagen, contenedor y volumen. docker buildx du lista los registros de la caché de compilación. Si docker system df no explica el espacio, el consumo está fuera de Docker: revisa con du -xh --max-depth=1 /var | sort -h.

  2. Confirma qué necesitas conservar. docker builder prune solo elimina caché de compilación: no toca contenedores, imágenes ni volúmenes. Si guardas etiquetas de reversión (rollback), son imágenes y se conservan; compruébalas con docker image ls. El costo es que el siguiente build no tendrá caché y tardará más.

  3. Elimina la caché de compilación.

    docker builder prune -af

    -a amplía la limpieza a toda la caché sin uso (la referencia de buildx lo describe como incluir las imágenes internas y de frontend) y -f evita la confirmación interactiva. Si prefieres conservar la caché reciente para que los próximos builds sigan siendo rápidos, usa un filtro por antigüedad:

    docker builder prune -af --filter until=168h

    El valor es una duración (por ejemplo 24h); este ejemplo conserva lo usado en los últimos 7 días. También hay opciones por espacio, como --reserved-space 10GB. En documentación antigua aparece --keep-storage; el CLI reciente la marca como obsoleta y la reemplaza por --reserved-space. Confirma las opciones de tu versión con docker builder prune --help.

  4. Programa la limpieza para que no vuelva a acumularse. Puedes hacerlo al final del script o del flujo de despliegue:

    docker compose up -d --build
    docker builder prune -f --filter until=168h

    O con una tarea mensual en cron (en el crontab de un usuario con acceso a Docker), por ejemplo el día 1 a las 03:00:

    0 3 1 * * docker builder prune -af --filter until=720h

    Otra opción es ajustar el recolector de basura de BuildKit en /etc/docker/daemon.json, con un límite de tamaño para la caché. La documentación de Docker muestra esta forma:

    {
      "builder": {
        "gc": {
          "enabled": true,
          "defaultKeepStorage": "20GB"
        }
      }
    }

    Elige un valor acorde con tu disco y consulta en la documentación cómo aplicar el cambio de configuración del demonio.

Cómo verificar que quedó resuelto

df -h /
docker system df
docker ps --format "table {{.Names}}\t{{.Status}}"
curl -s -o /dev/null -w "%{http_code}\n" https://example.com

El uso del disco debe bajar de forma notable y la fila Build Cache de docker system df debe quedar cerca de cero. En el caso de referencia, el disco pasó de 115 GB usados (60 %) a 15 GB (8 %). Los contenedores deben seguir con el mismo tiempo en marcha (Up 3 weeks, no Up 5 seconds), lo que confirma que no se reiniciaron. Por último, las páginas deben responder 200. Cambia https://example.com por la URL de tu aplicación.

Variantes y errores relacionados

La diferencia con docker system prune

Según la documentación, docker system prune elimina contenedores detenidos, redes sin uso, imágenes huérfanas (dangling) y caché de compilación sin uso. Con -a también borra todas las imágenes que no tenga asociadas un contenedor: en un servidor que conserva imágenes de reversión, eso las elimina. Con --volumes añade los volúmenes anónimos sin uso. Por defecto no borra volúmenes, pero conviene no ejecutarlo a ciegas en producción. docker builder prune es la opción acotada: solo caché.

Los logs de los contenedores

Si docker system df no muestra una caché grande, revisa el tamaño de los logs. Con el controlador json-file, max-size viene sin límite por defecto. Puedes activar la rotación en daemon.json con log-opts (max-size y max-file); la documentación indica que se aplica a los contenedores creados después de reiniciar Docker, no a los existentes.

Imágenes antiguas

docker image prune elimina las imágenes huérfanas; con -a elimina todas las que no usa ningún contenedor. Antes de usar -a, asegúrate de que no necesitas esas imágenes para volver a una versión anterior.

Otro constructor de buildx

Si usas un constructor distinto del predeterminado, la limpieza se aplica al constructor seleccionado. La opción --builder permite indicar otro. En versiones recientes del CLI, la ayuda de docker builder prune muestra el uso docker buildx prune, por lo que la referencia de buildx citada abajo también aplica.

Resumen

  • Un disco lleno con datos pequeños suele deberse a la caché de compilación; confírmalo con docker system df (fila Build Cache).
  • docker builder prune -af elimina esa caché sin tocar contenedores, imágenes ni volúmenes; el siguiente build será más lento.
  • Con --filter until=... o --reserved-space conservas la caché reciente; revisa las opciones con --help en tu versión.
  • Programa la limpieza (fin del despliegue o cron mensual) o limita el recolector de BuildKit en daemon.json.
  • No uses docker system prune -a a ciegas: puede borrar imágenes de reversión.

Referencias

Comentarios