## Escaneo de Seguridad Cuando haya creado una imagen, es una buena práctica escanearla en busca de vulnerabilidades de seguridad utilizando el comando `docker scan`. Docker se ha asociado con [Snyk](http://snyk.io) para proporcionar el servicio de análisis de vulnerabilidades. Por ejemplo, para escanear la imagen `getting-started` que creó anteriormente en el tutorial, simplemente escriba ```bash docker scan getting-started ``` El análisis utiliza una base de datos de vulnerabilidades que se actualiza constantemente, por lo que el resultado que ve variará a medida que se descubran nuevas vulnerabilidades, pero podría verse así: ```plaintext ✗ Low severity vulnerability found in freetype/freetype Description: CVE-2020-15999 Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641 Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2 From: freetype/freetype@2.10.0-r0 From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0 Fixed in: 2.10.0-r1 ✗ Medium severity vulnerability found in libxml2/libxml2 Description: Out-of-bounds Read Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791 Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1 From: libxml2/libxml2@2.9.9-r3 From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3 From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3 Fixed in: 2.9.9-r4 ``` El resultado enumera el tipo de vulnerabilidad, una URL para obtener más información y, lo que es más importante, qué versión de la biblioteca relevante corrige la vulnerabilidad. Hay varias otras opciones, sobre las que puede leer en [documentación docker scan](https://docs.docker.com/engine/scan/). Además de escanear su imagen recién construida en la línea de comando, también puede [configurar Docker Hub](https://docs.docker.com/docker-hub/vulnerability-scanning/) para escanear todas las imágenes recién enviadas automáticamente, y luego puede ver los resultados tanto en Docker Hub como en Docker Desktop. ![Escaneo de vulnerabilidades del hub](hvs.png){: style=width:75% } {: .text-center } ## Capas de imagen ¿Sabías que puedes mirar lo que compone una imagen? Usando el comando `docker image history`, puede ver el comando que se usó para crear cada capa dentro de una imagen. 1. Utilice el comando `docker image history` para ver las capas en la imagen de `getting-started` que creó anteriormente en el tutorial. ```bash docker image history getting-started ``` Debería obtener un resultado que se parezca a esto (los identificadores de fechas pueden ser diferentes). ```plaintext 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 yarn install --production 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 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B 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 pantalla aquí muestra la base en la parte inferior con la capa más nueva en la parte superior. Con esto, también puede ver rápidamente el tamaño de cada capa, lo que ayuda a diagnosticar imágenes grandes. 1. Notarás que varias de las líneas están truncadas. Si agrega el indicador `--no-trunc`, obtendrá la salida completa (sí ... es curioso cómo usa una bandera truncada para obtener una salida no truncada, ¿eh?) ```bash docker image history --no-trunc getting-started ``` ## Almacenamiento en caché de capas Ahora que ha visto las capas en acción, hay una lección importante que aprender para ayudar a reducir los build times de las imágenes de su contenedor. > Una vez que cambia una capa, todas las capas posteriores también deben volver a crearse Veamos el Dockerfile que estábamos usando una vez más ... ```dockerfile FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"] ``` Volviendo a la salida del historial de la imagen, vemos que cada comando en el Dockerfile se convierte en una nueva capa en la imagen. Quizás recuerde que cuando hicimos un cambio en la imagen, las dependencias yarn tuvieron que reinstalarse. ¿Hay alguna forma de solucionar este problema? No tiene mucho sentido distribuir las mismas dependencias cada vez que construimos, ¿verdad? Para solucionar esto, necesitamos reestructurar nuestro Dockerfile para ayudar a soportar el almacenamiento en caché de las dependencias. Para aplicaciones Node-based, esas dependencias se definen en el archivo `package.json`. Entonces, ¿qué pasa si copiamos solo ese archivo primero, instalamos las dependencias y, luego, copiamos todo lo demás? Luego, solo recreamos las dependencias yarn si hubo un cambio en el `package.json`. ¿Tener sentido? 1. Actualice el Dockerfile para copiar en el `package.json` primero, instale las dependencias y luego copie todo lo demás en. ```dockerfile hl_lines="3 4 5" FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"] ``` 1. Cree un archivo llamado `.dockerignore` en la misma carpeta que Dockerfile con el siguiente contenido. ```ignore node_modules ``` Los archivos `.dockerignore` son una manera fácil de copiar selectivamente solo archivos relevantes de imagen. Puede leer más sobre esto [aquí](https://docs.docker.com/engine/reference/builder/#dockerignore-file). En este caso, la carpeta `node_modules` debe omitirse en el segundo paso `COPY` porque de lo contrario, posiblemente sobrescribirá los archivos que fueron creados por el comando en el paso `RUN`. Para obtener más detalles sobre por qué se recomienda esto para las aplicaciones Node.js y otras prácticas recomendadas, echa un vistazo a su guía en [Dockerizar una aplicación web Node.js](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/). 1. Cree una nueva imagen usando `docker build`. ```bash docker build -t getting-started . ``` Debería ver un resultado como este ... ```plaintext Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Running in d53a06c9e4c2 yarn install v1.17.3 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... [4/4] Building fresh packages... Done in 10.89s. Removing intermediate container d53a06c9e4c2 ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> a239a11f68d8 Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 49999f68df8f Removing intermediate container 49999f68df8f ---> e709c03bc597 Successfully built e709c03bc597 Successfully tagged getting-started:latest ``` Verá que se reconstruyeron todas las capas. Perfectamente bien desde que cambiamos bastante el Dockerfile. 1. Ahora, haga un cambio en el archivo `src/static/index.html` (como cambiar el `` para decir "The Awesome Todo App"). 1. Cree la imagen de Docker ahora usando `docker build -t Getting started .` nuevamente. Esta vez, su salida debería verse un poco diferente. ```plaintext hl_lines="5 8 11" Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> Using cache ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Using cache ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> cccde25a3d9a Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 2be75662c150 Removing intermediate container 2be75662c150 ---> 458e5c6f080c Successfully built 458e5c6f080c Successfully tagged getting-started:latest ``` Antes que nada, ¡Deberías notar que la construcción fue MUCHO más rápida! Y verá que todos los pasos 1-4 tienen "Using cache". Entonces, ¡hurra! Estamos usando la caché de compilación. Pushing y pulling esta imagen y las actualizaciones también será mucho más rápido. ¡Hurra! ## Construcciones Multi-Stage Si bien no vamos a profundizar demasiado en este tutorial, las compilaciones multi-stage son una herramienta increíblemente poderosa para ayudar a usar multiple stages para crear una imagen. Tienen varias ventajas: - Separe las dependencias en build-time de las dependencias en runtime - Reduzca el tamaño general de la imagen enviando _solo_ lo que su aplicación necesita para ejecutarse ### Ejemplo Maven/Tomcat Al crear aplicaciones basadas en Java, se necesita un JDK para compilar el código fuente en código de bytes Java. Sin embargo, ese JDK no es necesario en producción. Además, es posible que esté utilizando herramientas como Maven o Gradle para ayudar a crear la aplicación. Esos tampoco son necesarios en nuestra imagen final. Las compilaciones de varias etapas ayudan. ```dockerfile 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, usamos una etapa (llamada `build`) para realizar la compilación real de Java usando Maven. En la segunda etapa (comenzando en `FROM tomcat`), copiamos archivos de la etapa de `build`. La imagen final es solo la última etapa que se está creando (que se puede anular usando el indicador `--target`). ### Ejemplo React Al crear aplicaciones React, necesitamos un entorno Node para compilar el código JS (normalmente JSX), SASS stylesheets, y más en HTML, JS, y CSS estático. Si no estamos haciendo renderizado del lado del servidor, ni siquiera necesitamos un entorno Node para nuestra compilación de producción. ¿Por qué no enviar los recursos estáticos en un contenedor nginx estático? ```dockerfile FROM node:12 AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install COPY public ./public COPY src ./src RUN yarn run build FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html ``` Aquí, estamos usando una imagen `node: 12` para realizar la compilación (maximizando el almacenamiento en caché de la capa) y luego copiando la salida en un contenedor nginx. Genial, ¿eh? ## Resumen Al comprender un poco cómo se estructuran las imágenes, podemos crear imágenes más rápido y enviar menos cambios. El escaneo de imágenes nos da la confianza de que los contenedores que estamos ejecutando y distribuyendo son seguros. Las compilaciones Multi-stage también nos ayudan a reducir el tamaño general de la imagen y a aumentar la seguridad del contenedor final al separar las dependencias en build-time de las dependencias en runtime.