Optimización de imágenes de Docker: de 1.16 GB a 22.4 MB

Imagen sacada de www.docker.com

Docker es una plataforma para desarrolladores de software y administradores de sistemas que permite compilar, ejecutar y compartir aplicaciones con contenedores. Un contenedor es un proceso que se ejecuta en un entorno aislado, en su propio sistema de archivos; ese sistema de archivos se construye usando una imagen de Docker. Las imágenes incluyen todo lo necesario para ejecutar una aplicación (código compilado, dependencias, librerías, etc.). Las imágenes se definen usando un fichero llamado Dockerfile.

Los términos dockerización o contenedorización suelen usarse para definir el proceso de crear un contenedor de Docker.

Los contenedores son populares porque son:

  • Flexibles: incluso las aplicaciones más complejas se pueden contener en contenedores.
  • Ligeros: los contenedores comparten el kernel del host, lo que los hace mucho más eficientes que las máquinas virtuales.
  • Portátiles: pueden compilarse localmente y ejecutarse en cualquier lugar.
  • Tienen Acoplamiento Bajo: los contenedores están encapsulados, lo que permite reemplazar o actualizar uno sin interrumpir a los demás.
  • Seguros: los contenedores aplican restricciones y aislamientos agresivos a los procesos sin que se requiera ninguna configuración por parte del usuario.

En este post voy a centrarme en la optimización de imágenes de Docker para que sean ligeras.

Vamos a partir de un ejemplo en el que construimos una aplicación de React y la queremos dockerizar. Después de ejecutar el comando npx y crear el Dockerfile, nos queda una estructura de archivos como la de la Imagen 1.

npx create-react-app app --template typescript
Imagen 1: estructura de archivos.

Si compilamos un Dockerfile básico, como el siguiente, acabamos teniendo una imagen que pesa 1.16 GB:

Imagen 2: el tamaño inicial de la imagen es 1.16GB.

Primera Optimización: usar una imagen base ligera

En Docker Hub (el repositorio público de Docker) existen varias imágenes disponibles para descargar, cada una con diferentes características y tamaños.

Normalmente, las imágenes basadas en Alpine o BusyBox tienen un tamaño extremadamente reducido si se comparan con otras las basadas en otras distribuciones de linux, como Ubuntu. Esto se debe a que Alpine y esas otras imágenes han sido optimizadas para incluir los paquetes mínimos e imprescindibles. En la siguiente imagen se puede ver una comparativa entre los tamaños de Ubuntu, Alpine, Node y Node basado el alpine.

Imagen 3: diferentes tamaños de imágenes base.

Al modificar el Dockerfile y usar Alpine como base, el tamaño final de nuestra imagen es de 330 MB:

Imagen 4: el tamaño de la imagen después de la primera optimización es de 330MB.

Segunda Optimización: Multi-stage build

Con la compilación por etapas, podemos usar varias imágenes base en el Dockerfile y copiar, de una etapa a otra, artefactos, archivos de configuración, etc., de modo que podemos desechar lo que no nos haga falta.

En este ejemplo, lo que necesitamos para desplegar la aplicación de React es el código compilado; no necesitamos los archivos fuente, ni el directorio node_modules, ni el package.json, etc.

Al cambiar el Dockerfile por el siguiente, el tamaño final de nuestra imagen es de 91.5 MB. Hay que tener en cuenta que la imagen de la etapa anterior (líneas 1–4) no se elimina automáticamente, Docker la conserva en cache para ejecutarse más deprisa si usamos la misma etapa en otra compilación, de modo que hay que eliminarla de forma manual.

Imagen 5: el tamaño de la imagen después de la segunda optimización es de 91.5MB.

Ahora tenemos un Dockerfile con dos etapas: en la primera compilamos el proyecto y en la segunda desplegamos la aplicación en el servidor web. Sin embargo, un contenedor de Node no es la mejor opción para servir páginas web (ficheros HTML, CSS, JavaScript, imágenes, etc.), la mejor opción sería usar un servidor como Nginx o Apache. En este caso usaré Nginx.

Al cambiar el Dockerfile por el siguiente, el tamaño final de nuestra imagen es de 22.4 MB. Si ejecutamos el contenedor, comprobamos que la web funciona sin problemas (Imagen 7).

Imagen 6: el tamaño de la imagen después de la tercera optimización es de 22.4MB.
Imagen 7: resultado de ejecutar el contenedor final.

Referencias

--

--