summaryrefslogtreecommitdiff
path: root/docs/tutorial/image-building-best-practices
diff options
context:
space:
mode:
authorMavlushechka <mavlushechka@gmail.com>2022-09-24 00:07:19 +0500
committerMavlushechka <mavlushechka@gmail.com>2022-09-24 00:07:19 +0500
commit43ffc1d6e59f2d118c730e968e032fcecbeaf202 (patch)
tree7e1e87163c8af30cd29d2b6e745d1c6b1077dc25 /docs/tutorial/image-building-best-practices
parente7c380c64056a004a1d61f04df7afb2a1c1c3675 (diff)
Copy docker/getting-started repository
Diffstat (limited to 'docs/tutorial/image-building-best-practices')
-rw-r--r--docs/tutorial/image-building-best-practices/hvs.pngbin0 -> 127965 bytes
-rw-r--r--docs/tutorial/image-building-best-practices/index.md270
2 files changed, 270 insertions, 0 deletions
diff --git a/docs/tutorial/image-building-best-practices/hvs.png b/docs/tutorial/image-building-best-practices/hvs.png
new file mode 100644
index 0000000..bce851b
--- /dev/null
+++ b/docs/tutorial/image-building-best-practices/hvs.png
Binary files differ
diff --git a/docs/tutorial/image-building-best-practices/index.md b/docs/tutorial/image-building-best-practices/index.md
new file mode 100644
index 0000000..49b51ca
--- /dev/null
+++ b/docs/tutorial/image-building-best-practices/index.md
@@ -0,0 +1,270 @@
+## Security Scanning
+
+When you have built an image, it is good practice to scan it for security vulnerabilities using the `docker scan` command.
+Docker has partnered with [Snyk](http://snyk.io) to provide the vulnerability scanning service.
+
+For example, to scan the `getting-started` image you created earlier in the tutorial, you can just type
+
+```bash
+docker scan getting-started
+```
+
+The scan uses a constantly updated database of vulnerabilities, so the output you see will vary as new
+vulnerabilities are discovered, but it might look something like this:
+
+```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
+```
+
+The output lists the type of vulnerability, a URL to learn more, and importantly which version of the relevant library
+fixes the vulnerability.
+
+There are several other options, which you can read about in the [docker scan documentation](https://docs.docker.com/engine/scan/).
+
+As well as scanning your newly built image on the command line, you can also [configure Docker Hub](https://docs.docker.com/docker-hub/vulnerability-scanning/)
+to scan all newly pushed images automatically, and you can then see the results in both Docker Hub and Docker Desktop.
+
+![Hub vulnerability scanning](hvs.png){: style=width:75% }
+{: .text-center }
+
+## Image Layering
+
+Did you know that you can look at what makes up an image? Using the `docker image history`
+command, you can see the command that was used to create each layer within an image.
+
+1. Use the `docker image history` command to see the layers in the `getting-started` image you
+ created earlier in the tutorial.
+
+ ```bash
+ docker image history getting-started
+ ```
+
+ You should get output that looks something like this (dates/IDs may be different).
+
+ ```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
+ <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 #(nop) ENV YARN_VERSION=1.21.1 0B
+ <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
+ ```
+
+ Each of the lines represents a layer in the image. The display here shows the base at the bottom with
+ the newest layer at the top. Using this, you can also quickly see the size of each layer, helping
+ diagnose large images.
+
+1. You'll notice that several of the lines are truncated. If you add the `--no-trunc` flag, you'll get the
+ full output (yes... funny how you use a truncated flag to get untruncated output, huh?)
+
+ ```bash
+ docker image history --no-trunc getting-started
+ ```
+
+
+## Layer Caching
+
+Now that you've seen the layering in action, there's an important lesson to learn to help decrease build
+times for your container images.
+
+> Once a layer changes, all downstream layers have to be recreated as well
+
+Let's look at the Dockerfile we were using one more time...
+
+```dockerfile
+FROM node:12-alpine
+WORKDIR /app
+COPY . .
+RUN yarn install --production
+CMD ["node", "src/index.js"]
+```
+
+Going back to the image history output, we see that each command in the Dockerfile becomes a new layer in the image.
+You might remember that when we made a change to the image, the yarn dependencies had to be reinstalled. Is there a
+way to fix this? It doesn't make much sense to ship around the same dependencies every time we build, right?
+
+To fix this, we need to restructure our Dockerfile to help support the caching of the dependencies. For Node-based
+applications, those dependencies are defined in the `package.json` file. So, what if we copied only that file in first,
+install the dependencies, and _then_ copy in everything else? Then, we only recreate the yarn dependencies if there was
+a change to the `package.json`. Make sense?
+
+1. Update the Dockerfile to copy in the `package.json` first, install dependencies, and then copy everything else in.
+
+ ```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. Create a file named `.dockerignore` in the same folder as the Dockerfile with the following contents.
+
+ ```ignore
+ node_modules
+ ```
+
+ `.dockerignore` files are an easy way to selectively copy only image relevant files.
+ You can read more about this
+ [here](https://docs.docker.com/engine/reference/builder/#dockerignore-file).
+ In this case, the `node_modules` folder should be omitted in the second `COPY` step because otherwise,
+ it would possibly overwrite files which were created by the command in the `RUN` step.
+ For further details on why this is recommended for Node.js applications and other best practices,
+ have a look at their guide on
+ [Dockerizing a Node.js web app](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/).
+
+1. Build a new image using `docker build`.
+
+ ```bash
+ docker build -t getting-started .
+ ```
+
+ You should see output like this...
+
+ ```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
+ ```
+
+ You'll see that all layers were rebuilt. Perfectly fine since we changed the Dockerfile quite a bit.
+
+1. Now, make a change to the `src/static/index.html` file (like change the `<title>` to say "The Awesome Todo App").
+
+1. Build the Docker image now using `docker build -t getting-started .` again. This time, your output should look a little different.
+
+ ```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
+ ```
+
+ First off, you should notice that the build was MUCH faster! And, you'll see that steps 1-4 all have
+ `Using cache`. So, hooray! We're using the build cache. Pushing and pulling this image and updates to it
+ will be much faster as well. Hooray!
+
+
+## Multi-Stage Builds
+
+While we're not going to dive into it too much in this tutorial, multi-stage builds are an incredibly powerful
+tool to help use multiple stages to create an image. There are several advantages for them:
+
+- Separate build-time dependencies from runtime dependencies
+- Reduce overall image size by shipping _only_ what your app needs to run
+
+### Maven/Tomcat Example
+
+When building Java-based applications, a JDK is needed to compile the source code to Java bytecode. However,
+that JDK isn't needed in production. Also, you might be using tools like Maven or Gradle to help build the app.
+Those also aren't needed in our final image. Multi-stage builds help.
+
+```dockerfile
+FROM maven AS build
+WORKDIR /app
+COPY . .
+RUN mvn package
+
+FROM tomcat
+COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
+```
+
+In this example, we use one stage (called `build`) to perform the actual Java build using Maven. In the second
+stage (starting at `FROM tomcat`), we copy in files from the `build` stage. The final image is only the last stage
+being created (which can be overridden using the `--target` flag).
+
+
+### React Example
+
+When building React applications, we need a Node environment to compile the JS code (typically JSX), SASS stylesheets,
+and more into static HTML, JS, and CSS. If we aren't doing server-side rendering, we don't even need a Node environment
+for our production build. Why not ship the static resources in a static nginx container?
+
+```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
+```
+
+Here, we are using a `node:12` image to perform the build (maximizing layer caching) and then copying the output
+into an nginx container. Cool, huh?
+
+
+## Recap
+
+By understanding a little bit about how images are structured, we can build images faster and ship fewer changes.
+Scanning images gives us confidence that the containers we are running and distributing are secure.
+Multi-stage builds also help us reduce overall image size and increase final container security by separating
+build-time dependencies from runtime dependencies.