Cuando es posible, siempre combino comandos que crean archivos con comandos que eliminan esos mismos archivos en un solo RUN
línea. Esto se debe a que cada RUN
línea agrega una capa a la imagen, la salida es, literalmente, los cambios del sistema de archivos que puede ver docker diff
en el contenedor temporal que crea. Si elimina un archivo que se creó en una capa diferente, todo lo que hace el sistema de archivos de unión es registrar el cambio del sistema de archivos en una nueva capa, el archivo todavía existe en la capa anterior y se envía a través de la red y se almacena en el disco. Entonces, si descarga el código fuente, lo extrae, lo compila en un binario y luego elimina los archivos tgz y fuente al final, realmente desea que todo esto se haga en una sola capa para reducir el tamaño de la imagen.
A continuación, dividí las capas personalmente en función de su potencial de reutilización en otras imágenes y el uso esperado del almacenamiento en caché. Si tengo 4 imágenes, todas con la misma imagen base (por ejemplo, Debian), puedo extraer una colección de utilidades comunes para la mayoría de esas imágenes en el primer comando de ejecución para que las otras imágenes se beneficien del almacenamiento en caché.
El orden en el Dockerfile es importante al mirar la reutilización de la memoria caché de imágenes. Miro los componentes que se actualizarán muy raramente, posiblemente solo cuando la imagen base se actualice y los coloque en el Dockerfile. Hacia el final del Dockerfile, incluyo todos los comandos que se ejecutarán rápidamente y pueden cambiar con frecuencia, por ejemplo, agregar un usuario con un UID específico del host o crear carpetas y cambiar los permisos. Si el contenedor incluye código interpretado (por ejemplo, JavaScript) que se está desarrollando activamente, se agrega lo más tarde posible para que una reconstrucción solo ejecute ese cambio único.
En cada uno de estos grupos de cambios, consolido lo mejor que puedo para minimizar las capas. Entonces, si hay 4 carpetas de código fuente diferentes, esas se colocan dentro de una sola carpeta para que se pueda agregar con un solo comando. Cualquier instalación de paquetes desde algo como apt-get se fusionan en un solo EJECUTAR cuando sea posible para minimizar la cantidad de sobrecarga del administrador de paquetes (actualización y limpieza).
Actualización para compilaciones de varias etapas:
Me preocupa mucho menos reducir el tamaño de la imagen en las etapas no finales de una construcción de varias etapas. Cuando estas etapas no se etiquetan y envían a otros nodos, puede maximizar la probabilidad de reutilización de caché dividiendo cada comando en una RUN
línea separada .
Sin embargo, esta no es una solución perfecta para aplastar capas, ya que todo lo que copia entre las etapas son los archivos y no el resto de los metadatos de la imagen, como la configuración de las variables de entorno, el punto de entrada y el comando. Y cuando instala paquetes en una distribución de Linux, las bibliotecas y otras dependencias pueden estar dispersas por todo el sistema de archivos, lo que dificulta la copia de todas las dependencias.
Debido a esto, uso compilaciones de varias etapas como reemplazo para compilar binarios en un servidor CI / CD, de modo que mi servidor CI / CD solo necesita tener las herramientas para ejecutarse docker build
y no tener un jdk, nodejs, go y cualquier otra herramienta de compilación instalada.