Compartir comentarios
Las respuestas se generan en base a la documentación.

Utilizar la caché de compilación

Explicación

Considera el siguiente Dockerfile que creaste para la aplicación getting-started.

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "./src/index.js"]

Cuando ejecutas el comando docker build para crear una nueva imagen, Docker ejecuta cada instrucción en tu Dockerfile, creando una capa para cada comando y en el orden especificado. Para cada instrucción, Docker verifica si puede reutilizar la instrucción de una compilación anterior. Si detecta que ya has ejecutado una instrucción similar anteriormente, Docker no necesita volver a hacerlo. En su lugar, utilizará el resultado almacenado en la caché. De esta manera, tu proceso de compilación se vuelve más rápido y eficiente, ahorrándote valioso tiempo y recursos.

El uso eficaz de la caché de compilación te permite lograr compilaciones más rápidas al reutilizar resultados de compilaciones anteriores y omitir trabajo innecesario. Para maximizar el uso de la caché y evitar reconstrucciones costosas en tiempo y recursos, es importante comprender cómo funciona la invalidación de la caché. Aquí tienes algunos ejemplos de situaciones que pueden causar la invalidación de la caché:

  • Cualquier cambio en el comando de una instrucción RUN invalida esa capa. Docker detecta el cambio e invalida la caché de compilación si hay alguna modificación en un comando RUN en tu Dockerfile.

  • Cualquier cambio en los archivos copiados en la imagen con las instrucciones COPY o ADD. Docker supervisa cualquier alteración en los archivos dentro del directorio de tu proyecto. Ya sea un cambio en el contenido o en propiedades como los permisos, Docker considera estas modificaciones como desencadenantes para invalidar la caché.

  • Una vez que se invalida una capa, todas las capas siguientes también se invalidan. Si alguna capa anterior, incluyendo la imagen base o las capas intermedias, se ha invalidado debido a cambios, Docker se asegura de que las capas posteriores que dependan de ella también se invaliden. Esto mantiene el proceso de compilación sincronizado y evita inconsistencias.

Cuando escribas o edites un Dockerfile, presta atención a los fallos de caché innecesarios para garantizar que las compilaciones se ejecuten de la manera más rápida y eficiente posible.

Pruébalo

En esta guía práctica, aprenderás a utilizar la caché de compilación de Docker de forma eficaz para una aplicación Node.js.

Compilar la aplicación

  1. Descarga e instala Docker Desktop.

  2. Abre una terminal y clona esta aplicación de muestra.

    $ git clone https://github.com/dockersamples/todo-list-app
    
  3. Navega al directorio todo-list-app:

    $ cd todo-list-app
    

    Dentro de este directorio, encontrarás un archivo llamado Dockerfile con el siguiente contenido:

    FROM node:22-alpine
    WORKDIR /app
    COPY . .
    RUN yarn install --production
    EXPOSE 3000
    CMD ["node", "./src/index.js"]
  4. Ejecuta el siguiente comando para compilar la imagen de Docker:

    $ docker build .
    

    Aquí tienes el resultado del proceso de compilación:

    [+] Building 20.0s (10/10) FINISHED
    

    La primera línea indica que todo el proceso de compilación tomó 20.0 segundos. La primera compilación puede tardar algún tiempo ya que instala dependencias.

  5. Vuelve a compilar sin realizar cambios.

    Ahora, vuelve a ejecutar el comando docker build sin realizar ningún cambio en el código fuente ni en el Dockerfile, como se muestra:

    $ docker build .
    

    Las compilaciones posteriores a la inicial son más rápidas debido al mecanismo de almacenamiento en caché, siempre que los comandos y el contexto permanezcan sin cambios. Docker almacena en caché las capas intermedias generadas durante el proceso de compilación. Cuando vuelves a compilar la imagen sin realizar cambios en el Dockerfile o en el código fuente, Docker puede reutilizar las capas almacenadas en caché, acelerando significativamente el proceso de compilación.

    [+] Building 1.0s (9/9) FINISHED                                                                            docker:desktop-linux
     => [internal] load build definition from Dockerfile                                                                        0.0s
     => => transferring dockerfile: 187B                                                                                        0.0s
     ...
     => [internal] load build context                                                                                           0.0s
     => => transferring context: 8.16kB                                                                                         0.0s
     => CACHED [2/4] WORKDIR /app                                                                                               0.0s
     => CACHED [3/4] COPY . .                                                                                                   0.0s
     => CACHED [4/4] RUN yarn install --production                                                                              0.0s
     => exporting to image                                                                                                      0.0s
     => => exporting layers                                                                                                     0.0s
     => => exporting manifest
    

    La compilación posterior se completó en solo 1.0 segundo aprovechando las capas almacenadas en caché. No es necesario repetir pasos que consumen mucho tiempo como la instalación de dependencias.

    PasosDescripciónTiempo requerido (1.ª ejecución)Tiempo requerido (2.ª ejecución)
    1Cargar la definición de compilación desde el Dockerfile0.0 segundos0.0 segundos
    2Cargar metadatos para docker.io/library/node:22-alpine2.7 segundos0.9 segundos
    3Cargar .dockerignore0.0 segundos0.0 segundos
    4Cargar el contexto de compilación

    (Tamaño del contexto: 4.60MB)

    0.1 segundos0.0 segundos
    5Establecer el directorio de trabajo (WORKDIR)0.1 segundos0.0 segundos
    6Copiar el código local en el contenedor0.0 segundos0.0 segundos
    7Ejecutar yarn install --production10.0 segundos0.0 segundos
    8Exportar capas2.2 segundos0.0 segundos
    9Exportar la imagen final3.0 segundos0.0 segundos

    Volviendo a la salida de docker image history, ves que cada comando en el Dockerfile se convierte en una nueva capa en la imagen. Tal vez recuerdes que cuando realizabas un cambio en la imagen, las dependencias de yarn debían reinstalarse. ¿Hay alguna manera de solucionar esto? No tiene mucho sentido reinstalar las mismas dependencias cada vez que compilas, ¿verdad?

    Para solucionar esto, reestructura tu Dockerfile de modo que la caché de dependencias siga siendo válida a menos que realmente deba invalidarse. Para aplicaciones basadas en Node, las dependencias se definen en el archivo package.json. Querrás reinstalar las dependencias si ese archivo cambia, pero usar dependencias en caché si el archivo no ha cambiado. Por lo tanto, comienza copiando únicamente ese archivo primero, luego instala las dependencias y finalmente copia todo lo demás. De este modo, solo necesitarás recrear las dependencias de yarn si hubo un cambio en el archivo package.json.

  6. Actualiza el Dockerfile para copiar primero el archivo package.json, instalar las dependencias y luego copiar todo lo demás.

    FROM node:22-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    EXPOSE 3000
    CMD ["node", "src/index.js"]
  7. Crea un archivo llamado .dockerignore en la misma carpeta que el Dockerfile con el siguiente contenido:

    node_modules
  8. Compila la nueva imagen:

    $ docker build .
    

    A continuación, verás una salida similar a la siguiente:

    [+] Building 16.1s (10/10) FINISHED
    => [internal] load build definition from Dockerfile                                               0.0s
    => => transferring dockerfile: 175B                                                               0.0s
    => [internal] load .dockerignore                                                                  0.0s
    => => transferring context: 2B                                                                    0.0s
    => [internal] load metadata for docker.io/library/node:22-alpine                                  0.0s
    => [internal] load build context                                                                  0.8s
    => => transferring context: 53.37MB                                                               0.8s
    => [1/5] FROM docker.io/library/node:22-alpine                                                    0.0s
    => CACHED [2/5] WORKDIR /app                                                                      0.0s
    => [3/5] COPY package.json yarn.lock ./                                                           0.2s
    => [4/5] RUN yarn install --production                                                           14.0s
    => [5/5] COPY . .                                                                                 0.5s
    => exporting to image                                                                             0.6s
    => => exporting layers                                                                            0.6s
    => => writing image
    sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25        0.0s
    => => naming to docker.io/library/node-app:2.0                                                 0.0s
    

    Verás que todas las capas se reconstruyeron. Lo cual está perfectamente bien ya que cambiaste bastante el Dockerfile.

  9. Ahora, realiza un cambio en el archivo src/static/index.html (por ejemplo, cambia el título para que diga "The Awesome Todo App").

  10. Compila la imagen de Docker. Esta vez, tu salida debería verse un poco diferente.

    $ docker build -t node-app:3.0 .
    

    A continuación, verás una salida similar a la siguiente:

    [+] Building 1.2s (10/10) FINISHED
    => [internal] load build definition from Dockerfile                                               0.0s
    => => transferring dockerfile: 37B                                                                0.0s
    => [internal] load .dockerignore                                                                  0.0s
    => => transferring context: 2B                                                                    0.0s
    => [internal] load metadata for docker.io/library/node:22-alpine                                  0.0s
    => [internal] load build context                                                                  0.2s
    => => transferring context: 450.43kB                                                              0.2s
    => [1/5] FROM docker.io/library/node:22-alpine                                                    0.0s
    => CACHED [2/5] WORKDIR /app                                                                      0.0s
    => CACHED [3/5] COPY package.json yarn.lock ./                                                    0.0s
    => CACHED [4/5] RUN yarn install --production                                                     0.0s
    => [5/5] COPY . .                                                                                 0.5s
    => exporting to image                                                                             0.3s
    => => exporting layers                                                                            0.3s
    => => writing image
    sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda       0.0s
    => => naming to docker.io/library/node-app:3.0                                                 0.0s
    

    En primer lugar, deberías notar que la compilación fue mucho más rápida. Verás que varios pasos están utilizando capas almacenadas en caché previamente. Esas son buenas noticias; estás utilizando la caché de compilación. Subir y bajar esta imagen, así como sus actualizaciones, también será mucho más rápido.

Al seguir estas técnicas de optimización, puedes hacer que tus compilaciones de Docker sean más rápidas y eficientes, lo que se traduce en ciclos de iteración más cortos y una mejor productividad en el desarrollo.

Recursos adicionales

Siguientes pasos

Ahora que entiendes cómo usar la caché de compilación de Docker de manera efectiva, estás listo para aprender sobre las compilaciones multietapa.

Compilaciones multietapa