Best practices for building Docker images

1. Mirror layering

Using the docker image history command, you can see the commands used to create each layer in the image.

 1. Use the docker image history command to view the layers in the created entry image.

docker image history getting-started

You should get output like this:

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 line represents a layer in the image. The display here shows the bottom at the bottom with the latest layer at the top. Using this option, you can also quickly view the size of each layer to help diagnose large images.

2. You will notice that several lines are truncated. If you add the --no trunc flag, you will get the complete output.

docker image history --no-trunc getting-started

2. Layer cache

Now that you have seen layering in action, there is an important lesson to learn to help reduce container image build times. Once a layer changes, all downstream layers must also be recreated.
Please review the following Dockerfile you created for your starter application.

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

Go back to the image history output and you will see that each command in the Dockerfile becomes a new layer in the image. You may remember that when you make changes to your image, you must reinstall yarn dependencies. It doesn't make much sense to ship around the same dependencies every time you build.
To fix it, you need to restructure the Dockerfile to help support caching of dependencies. For node-based applications, these dependencies are defined in the package.json file. You can first copy just that file, install the dependencies, and then copy everything else. Yarn dependencies are then only recreated if package.json changes.

1. Update the Dockerfile to first copy in package.json, install dependencies, and then copy everything else in.

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]

2. Create a file named .dockerignore in the same folder as the Dockerfile, containing the following content.
node_modules
.dockerignore files are a simple way to selectively copy only the files related to the image. You can read more about this here. In this case, the node_modules folder should be omitted in the second COPY step because otherwise, it may overwrite the files created by the command in the RUN step.
3. Use docker Build to build a new image.

docker build -t getting-started .

You should see the following output.

[+] 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:18-alpine
=> [internal] load build context
=> => transferring context: 53.37MB
=> [1/5] FROM docker.io/library/node:18-alpine
=> CACHED [2/5] WORKDIR /app
=> [3/5] COPY package.json yarn.lock ./
=> [4/5] RUN yarn install --production
=> [5/5] COPY . .
=> exporting to image
=> => exporting layers
=> => writing image     sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25
=> => naming to docker.io/library/getting-started

4. Now, make changes to the src/static/index.html file. For example, change <title> to "the Awesome Todo App."
5. Now use Docker Build-t getting started to build the Docker image. again. This time, your output should look a little different.

[+] 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:18-alpine
=> [internal] load build context
=> => transferring context: 450.43kB
=> [1/5] FROM docker.io/library/node:18-alpine
=> CACHED [2/5] WORKDIR /app
=> CACHED [3/5] COPY package.json yarn.lock ./
=> CACHED [4/5] RUN yarn install --production
=> [5/5] COPY . .
=> exporting to image
=> => exporting layers
=> => writing image     sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda
=> => naming to docker.io/library/getting-started

First, you should notice that builds are much faster. And, you'll see that several steps are using previously cached layers. Pushing, pulling, and updating this image will also be much faster.

3. Multi-stage construction

Multi-stage build is a very powerful tool that helps create images using multiple stages. They have several advantages:

  • Separate build-time dependencies from run-time dependencies
  • Reduce overall image size by delivering only what your application needs to run

1. Maven/Tomcat example

When building a Java-based application, a JDK is required to compile the source code into Java bytecode. However, the JDK is not required in production. Additionally, you may be using tools like Maven or Gradle to help build your application. These are not needed in the final image either. Multi-stage build help.​ 

# 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 

In this example, you use a stage (called build) to perform the actual Java build using Maven. In the second phase (starting from FROM tomcat), files are copied from the build phase. The final image is just the last stage being created and can be overridden using the --target flag.

2. React example

When building a React application, you need a Node environment to compile JS code (usually JSX), SASS stylesheets, etc. into static HTML, JS, and CSS. Without server-side rendering, a production build doesn't even require a node environment. You can ship static resources in a static nginx container.

# syntax=docker/dockerfile:1
FROM node:18 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

In the previous Dockerfile example, it performs the build using the node:18 image (maximizing the layer cache) and then copies the output into the nginx container.

Guess you like

Origin blog.csdn.net/leesinbad/article/details/134784696