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

Mejores prácticas para la compilación de imágenes

Capas de imágenes

Utilizando el comando docker image history, puedes ver el comando que se utilizó para crear cada capa dentro de una imagen.

  1. Utiliza el comando docker image history para ver las capas en la imagen getting-started que creaste.

    $ docker image history getting-started
    

    Deberías obtener una salida que se parezca a lo siguiente.

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "src/index.j…    0B
    f1d1808565d6        19 seconds ago      /bin/sh -c npm install --omit=dev               85.4MB
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB

    Cada una de las líneas representa una capa en la imagen. La visualización aquí muestra la base en la parte inferior con la capa más nueva en la parte superior. Al utilizar esto, también puedes ver rápidamente el tamaño de cada capa, lo que ayuda a diagnosticar imágenes grandes.

  2. Notarás que varias de las líneas están truncadas. Si agregas la bandera --no-trunc, obtendrás la salida completa.

    $ docker image history --no-trunc getting-started
    

Caché de capas

Ahora que has visto las capas en acción, hay una lección importante que aprender para ayudar a disminuir los tiempos de compilación de las imágenes de tus contenedores. Una vez que una capa cambia, todas las capas siguientes también deben ser recreadas.

Observa el siguiente Dockerfile que creaste para la aplicación de inicio.

# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY . .
RUN npm install --omit=dev
CMD ["node", "src/index.js"]
EXPOSE 3000

Volviendo a la salida del historial de la imagen, ves que cada comando en el Dockerfile se convierte en una nueva capa en la imagen. Quizás recuerdes que cuando realizaste un cambio en la imagen, las dependencias tuvieron que reinstalarse. No tiene mucho sentido distribuir las mismas dependencias cada vez que compilas.

Para solucionarlo, necesitas reestructurar tu Dockerfile para facilitar el uso de la caché de las dependencias. Para aplicaciones basadas en Node, esas dependencias se definen en el archivo package.json. Puedes copiar primero únicamente ese archivo, instalar las dependencias y luego copiar todo lo demás. De este modo, solo volverás a crear las dependencias si hubo algún cambio en el archivo package.json.

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

    # syntax=docker/dockerfile:1
    FROM node:24-alpine
    WORKDIR /app
    COPY package.json package-lock.json ./
    RUN npm install --omit=dev
    COPY . .
    CMD ["node", "src/index.js"]
  2. Compila una nueva imagen utilizando docker build.

    $ docker build -t getting-started .
    

    Deberías ver una salida como la siguiente.

    [+] Building 16.1s (10/10) FINISHED
    => [internal] load build definition from Dockerfile
    => => transferring dockerfile: 175B
    => [internal] load .dockerignore
    => => transferring context: 2B
    => [internal] load metadata for docker.io/library/node:24-alpine
    => [internal] load build context
    => => transferring context: 53.37MB
    => [1/5] FROM docker.io/library/node:24-alpine
    => CACHED [2/5] WORKDIR /app
    => [3/5] COPY package.json package-lock.json ./
    => [4/5] RUN npm install --omit=dev
    => [5/5] COPY . .
    => exporting to image
    => => exporting layers
    => => writing image     sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25
    => => naming to docker.io/library/getting-started
  3. Ahora, realiza un cambio en el archivo src/static/index.html. Por ejemplo, cambia la etiqueta <title> a "The Awesome Todo App".

  4. Compila la imagen de Docker utilizando de nuevo docker build -t getting-started .. Esta vez, tu salida debería verse un poco diferente.

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

    En primer lugar, notarás que la compilación fue mucho más rápida. Y verás que varios pasos están utilizando capas previamente almacenadas en caché. Distribuir y descargar esta imagen, así como sus actualizaciones, también será mucho más rápido.

Compilaciones multi-etapa

Las compilaciones multi-etapa (multi-stage) son una herramienta increíblemente poderosa que te permite usar varias etapas para crear una imagen. Tienen diversas ventajas:

  • Separar las dependencias de tiempo de compilación de las dependencias de tiempo de ejecución.
  • Reducir el tamaño total de la imagen distribuyendo únicamente lo que tu aplicación necesita para ejecutarse.

Ejemplo de Maven/Tomcat

Al compilar aplicaciones basadas en Java, necesitas un JDK para compilar el código fuente a código de bytes de Java. Sin embargo, ese JDK no es necesario en producción. Además, es posible que estés utilizando herramientas como Maven o Gradle para compilar la aplicación. Estas tampoco son necesarias en tu imagen final. Las compilaciones multi-etapa ayudan en este proceso.

# syntax=docker/dockerfile:1
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps

En este ejemplo, utilizas una etapa (llamada build) para realizar la compilación real de Java usando Maven. En la segunda etapa (que comienza en FROM tomcat), copias archivos de la etapa build. La imagen final se crea a partir de la última etapa, la cual se puede anular utilizando la bandera --target.

Ejemplo de React

Al compilar aplicaciones de React, necesitas un entorno de Node para compilar el código JS (normalmente JSX), las hojas de estilo SASS y más en HTML, JS y CSS estáticos. Si no estás haciendo renderizado en el lado del servidor (SSR), ni siquiera necesitas un entorno de Node para tu compilación de producción. Puedes distribuir los recursos estáticos en un contenedor nginx estático.

# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package* ./
RUN npm install
COPY public ./public
COPY src ./src
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

En el ejemplo de Dockerfile anterior, se utiliza la imagen node:24-alpine para realizar la compilación (maximizando la caché de capas) y luego se copia la salida en un contenedor nginx.

Tips

Este ejemplo de React tiene fines ilustrativos. La aplicación de tareas de inicio es una aplicación de backend de Node.js, no un frontend de React.

Resumen

En esta sección, aprendiste algunas de las mejores prácticas para la compilación de imágenes, incluyendo la caché de capas y las compilaciones multi-etapa.

Información relacionada:

Siguientes pasos

En la siguiente sección, aprenderás sobre recursos adicionales que puedes utilizar para continuar aprendiendo sobre contenedores.

¿Qué sigue?