Recently, we have optimized the Docker packaging process of the company’s front-end engineering. The packaging time has increased from more than ten and twenty minutes at the beginning to one or two minutes now, which has achieved good results. Therefore, A simple summary is made.

What’s the use?

  • Speed up Docker image packaging
  • Reduce the volume of Docker images
  • Speed up image uploading

Optimization idea

  • The project’s NPM dependencies rarely change, so they only need to be executed when the package.json file changesnpm intallIn other cases, the installed node_module will be reused.
  • We only need to build the generated static files on the front end and use Nginx to proxy, so the final image should have as few modules as possible outside of those two.

Optimization analysis

Assuming our front-end engineering build command is NPM run build, the final Dockerfile looks like this:

FROM node:12.15.0 AS builder
WORKDIR /home/node/app
ADD package.json package-lock.json /home/node/app/
RUN npm install
ADD . /home/node/app
RUN npm run build

FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /home/node/app/dist /usr/share/nginx/html
EXPOSE 8080
Copy the code

The main optimization is as follows:

  1. Adding the project to Docker is divided into two steps: first copy package.json and package-lock.json, then execute NPM install to install the package, and then copy the entire project code into Docker. The advantage of this is that, When package.json and package-lock.json contents are not changed, step 4 NPM install will not be executed. This is because each step in a Dockerfile is a layer for a Docker image. Starting from the first step, each subsequent layer will be regenerated only if the operation of one of the steps changes, and the previous layer will be reused, as can be seen from the process output of the build:

    Sending build context to Docker Daemon 37MB Step 1/10: FROM node:12.15.0 AS Builder --> b6f455933a97 Step 2/10: WORKDIR /home/node/app ---> Using cache ---> ab720696eb7b Step 3/10 : ADD package.json package-lock.json /home/node/app/ ---> Using cache ---> 272488848fb7 Step 4/10 : RUN npm install ---> Using cache ---> 60c57b0b15e9 Step 5/10 : ADD . /home/node/app ---> 29ac75b71907 Step 6/10 : RUN npm run build ---> Runningin 4aa5cf14edbf
    Copy the code
  2. The entire packaging process uses two images. The first NodeJS image is just used to build the front end project and generate static files. Then, these static files are put into a Alpine nginx image to generate the final image, which is very lightweight because it does not require the NodeJS environment. It only has 3 layers, and when we upload the image to the mirror repository, the second time we upload only the last layer, which is the static file on the front end. The code to do this is on the first and penultimate lines. The first line uses AS Builder to declare the alias of the first image, Then the penultimate line COPY –from=builder means to COPY the static file from the image alias Builder to the directory of the nginx agent.

other

There are also some problems when writing nginx configuration. For a SPA that uses front-end routing, all subpaths except the resource file need to be redirected to index.html, but the resource file can only be returned 404 if it is not found. Because redirecting to index.html would cause the CDN to use the cache policy of index.html, which would then cause some trouble, we also include our nginx configuration:

server {
  listen 8080 default_server;
  listen[: :] :8080 default_server;

  root /usr/share/nginx/html;

  index index.html;

  server_name localhost;

  location / {
    try_files $uri $uri/ @rewrites;
  }

  location @rewrites {
    rewrite^ (. +) $ /index.html last;
  }

  location ~ * \. (? :ico|css|js|gif|jpe? g|png)$ {
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate"; }}Copy the code