Cache de build Docker para un agente IA: capas bien ordenadas antes de cache externo
Resumen
El build cache de Docker reutiliza capas cuando la instrucción y sus archivos no cambian. Orden de capas de menos a más frecuente, COPY del lockfile antes del código, cache mounts para pnpm/pip/go, docker builder prune y --cache-to/--cache-from para CI. Docs Docker build cache, 6 sep 2026.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
El build cache de Docker guarda el resultado de cada instrucción del Dockerfile y lo reutiliza solo si la instrucción y los archivos que toca no cambiaron desde el último build. Para un agente IA que se despliega en contenedor —multi-stage, pnpm, un runtime en Fly o VPS—, el cache es la diferencia entre un push que builda en dos minutos y uno que reinstala todo el mundo de dependencias en cada corrida. Fuentes oficiales Docker consultadas el 6 de septiembre de 2026.
La regla base es cruel y simple: cuando una capa se invalida, todas las capas siguientes se re-construyen, aunque su resultado no cambiaría en absoluto. Un Dockerfile mal ordenado convierte un cambio de una línea en el código en una reinstalación completa de dependencias.
Cómo invalida el cache el builder
El builder revisa la imagen base y luego compara cada instrucción contra las capas cacheadas:
ADD,COPYyRUN --mount=type=bindcalculan un checksum de los archivos involucrados. Si cambia el contenido (o los metadatos que sí cuentan), la capa se invalida.RUNsin bind mount solo compara el string del comando.RUN pnpm installes cache hit eterno aunque el registry haya publicado diez releases nuevas: el cache no mira dentro del contenedor.- El
mtimede los archivos copiados no invalida el cache. Copiar los mismos archivos con otra fecha es un hit. - Los build secrets no participan del checksum de contenido: cambiar el valor de un secret no invalida nada por sí solo (los IDs y rutas de mount del secret sí).
- Cambiar
SOURCE_DATE_EPOCHentre builds invalidaWORKDIRy todo lo que sigue. Setearlo al timestamp del commit rompe el cache con cada commit.
La consecuencia práctica: RUN apk add curl congelado en cache no significa curl actualizado. Rebuild la semana siguiente y sigue el mismo paquete. Para forzar re-ejecución: cambiar algo en una capa anterior, docker builder prune, o --no-cache / --no-cache-filter <stage> para invalidar solo un stage concreto.
Orden de capas: de menos frecuente a más frecuente
Es la técnica de mayor retorno y no requiere nada extra. Los pasos caros y estables van arriba; lo que cambia seguido, abajo.

El anti-patrón canónico:
FROM node:24-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
Cambiar una coma en el código invalida el COPY . ., y con él npm install vuelve a descargar todo. La versión cache-friendly divide el COPY:
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
El pnpm install solo se re-corre cuando cambian el manifest o el lockfile. Para un agente con dependencias pesadas esto ahorra minutos por build.
Mantén el contexto pequeño
El build context es lo que el builder envía a procesar cada instrucción. Un contexto gordo hace builds lentos y aumenta la probabilidad de invalidaciones. El .dockerignore es la herramienta: excluye node_modules, tmp*, logs, builds locales y —sobre todo— el .env, que no va en la imagen ni en el contexto que el builder checksuma (detalle en dockerignore).
Cache mounts: el cache que sobrevive al rebuild
Las capas normales son exact-match: si el layer se invalida, su trabajo se tira. Un cache mount es una ubicación persistente que sobrevive al rebuild de la capa: aunque el install se re-ejecute, solo descarga los paquetes nuevos o cambiados.

Para pnpm, el store es lo que se cacha:
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
El patrón es el mismo para otros runtimes que docs documenta: Go (/go/pkg/mod + /root/.cache/go-build), pip (/root/.cache/pip), Cargo (/app/target + /var/cache/cargo con CARGO_HOME para coordinar locks) y Composer (/tmp/cache). Apt necesita acceso exclusivo a sus datos: usa sharing=locked en los mounts de /var/cache/apt y /var/lib/apt para que builds paralelos se esperen en vez de corromper el cache.
Y una nota de límites: los cache mounts no se exportan con --cache-to en la mayoría de backends —en GitHub Actions en particular, BuildKit no los preserva—. El workaround documentado es reproducible-containers/buildkit-cache-dance. Úsalo solo si el cache mount te ahorra minutos reales; para la mayoría de agentes el cache de capas + lockfile primero es suficiente.
Cache externo para CI: --cache-to / --cache-from
En CI el builder es efímero: cada job arranca con cache vacío y build minutes caros. El cache interno de BuildKit no se comparte entre builders. La solución es exportar a una ubicación remota con docker buildx build:
--cache-to type=registry,ref=registry/app:buildcacheexporta el cache como imagen aparte en el registry.--cache-from type=registry,ref=registry/app:buildcachelo importa al inicio del build.mode=maxcachea todas las capas, incluidas las intermedias; el defaultmode=minsolo las que llegan a la imagen final.maxda más hits a costa de storage.- El backend
type=ghaguarda el cache en el GitHub Actions cache, pero solo funciona dentro de un workflow (los atributosurlytokensolo se pueblan en ese contexto) y requiere Buildx ≥ 0.21 / BuildKit ≥ 0.20 desde el apagón de la API v1 de abril 2025. inlineincrusta el cache en la imagen de salida: solomode=miny solo con el exporter de imagen.
En GitHub Actions con docker/build-push-action el patrón registry queda así:
- name: Build and push
uses: docker/build-push-action@v7
with:
push: true
tags: user/app:latest
cache-from: type=registry,ref=user/app:buildcache
cache-to: type=registry,ref=user/app:buildcache,mode=max
Y un warning de docs que muerde: ninguna ubicación de cache se puede escribir dos veces sin sobrescribir. Cache por rama = refs distintos (:buildcache-<branch>). El patrón multi-cache —--cache-from de la rama actual y otro --cache-from de main— da hits cuando la rama es nueva.
El backend type=local con actions/cache sigue vivo como alternativa; pasa reset=true (Buildx ≥ 0.35) en --cache-to porque el directorio crece con cada corrida dejando blobs huérfanos.
Comparativa rápida
| Técnica | Qué cachea | Sobrevive rebuild de capa | Dónde brilla | Trampa |
|---|---|---|---|---|
| Orden de capas | El layer exacto | No | Todo proyecto | Lockfile copiado tarde = reinstall eterno |
.dockerignore | No cachea: recorta el contexto | N/A | Contextos gordos | Sin él, el .env entra al contexto |
| Cache mounts | Paquetes del package manager | Sí | Installs pesados, Go/Rust | No se exportan a GHA; apt necesita sharing=locked |
--cache-to/from registry | Todas las capas (mode=max) | Sí, vía import | CI efímero | Escribir 2× la misma ref sobrescribe |
type=gha | Todas las capas | Sí, vía Actions cache | Workflows GH | Requiere Buildx ≥ 0.21; solo dentro de GH |
docker builder prune | Limpia el cache interno | — | Disco lleno, forzar refresh | Borra todo el cache local |
Checklist antes de culpar al cache
- El primer
COPYlleva manifest + lockfile, y el install corre antes de copiar el código. -
.dockerignoreexcluyenode_modules, builds locales, logs y.env. - El install pesado usa
--mount=type=cacheapuntando al store real del package manager (pnpm store, pip cache, GOMODCACHE). - apt con cache mount usa
sharing=lockeden/var/cache/apty/var/lib/apt. - En CI,
--cache-toy--cache-fromapuntan a la misma ref del registry (otype=ghadentro de Actions). Cache por rama usa refs distintas con fallback al cache demain. -
RUN apk add/apt install"actualizado" es una ilusión de cache: los paquetes se congelan hasta invalidar la capa a propósito. - Ningún secret viaja por
ARGoCOPY—los secrets no invalidan cache, peroCOPYlos incrusta en la capa para siempre.
Errores típicos
El install corre cada build pese al cache. Casi siempre es el COPY . . antes del install: cualquier cambio de código invalida el install. Divide el COPY: manifest primero, código después.
El cache mount de pnpm no acelera nada. El target debe ser el store real de pnpm; un target genérico /cache que el package manager nunca lee no cachea nada. Verifica con pnpm store path dentro del contenedor.
El cache de CI nunca da hits. Los builders de CI son efímeros: sin export/import explícito no hay cache que sobreviva. Revisa que --cache-from esté en el build actual, no solo --cache-to en el anterior. Si el workflow falla con el error de "legacy service", tus tools son viejos para la API v2 del cache de GitHub: Buildx ≥ 0.21, BuildKit ≥ 0.20, o usa setup-buildx-action.
Cambié el secret y el build sigue igual. Correcto: el contenido de secrets no participa del checksum. Invalida con un ARG CACHEBUST cuyo valor cambies junto con el secret, o con --no-cache-filter sobre el stage que lo usa.
El build es más lento con cache externo. mode=max en un monorepo gigante puede hacer el export más caro que los hits que trae. Prueba mode=min o invalida por stage con --no-cache-filter.
Relacionados
- Multi-stage Docker para un agente IA: el cache ordena el build; multi-stage evita que los tools de build lleguen al runtime.
- .dockerignore para un agente IA: el contexto pequeño es la mitad del cache hit.
- pnpm para coding agents:
--frozen-lockfilees lo que hace reproducible el layer del install. - CI/CD para agentes IA y el hub de seguridad, coste y operación. Sin agente: curso.
Siguiente paso: si el build ya es rápido y prod sigue lento, revisa límites de CPU y cold starts. Si el disco del builder se llena, docker builder prune es la válvula —con la regla de que borra el cache local completo.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.

Build secrets Docker para un agente IA: el token no va en el layer

SBOM y attestations Docker para un agente IA: qué hay dentro, cómo se construyó

Docker Bake para un agente IA: un archivo, varios targets, cero CLI eterno
