Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

Write the.dockerignore file

When building an image, Docker needs to prepare the context and collect all the required files into the process. The default context contains all files in the Dockerfile directory, but we don’t actually need the.git directory,.vscode directory,.idea directory, etc. .dockerignore functions and syntax similar to.gitignore, can ignore some unnecessary files, which can effectively speed up the image build time, while reducing the size of the Docker image.

Sample:

.git/
.vscode/
.idea/
Copy the code

A container only runs a single application

Technically, you can run multiple processes in a Docker container. You can run your database, front end, back end, SSH, and Supervisor all in the same Docker container. However, this can be very painful for you:

  • Very long build times (e.g., after modifying the front end, the entire back end also needs to be rebuilt)
  • Very large mirror size
  • Logs from multiple applications are difficult to handle (do not use stdout directly, otherwise logs from multiple applications will be mixed together)
  • Scale-out is a waste of resources (different applications need to run different containers)
  • Zombie process issues (you need to select the appropriate init process)

Therefore, it is recommended that you build a separate Docker image for each application.

Choose the appropriate base image

Suitable base images, such as Scratch, BusyBox, Alpine, distroless, and so on. It helps us reduce the size of the mirror image. At the same time, a smaller mirror means fewer useless programs, which can greatly reduce the number of targets for attack, thus improving security.

Merge multiple RUN directives into one

Docker images are layered, and the following points are important:

  • Each instruction in the Dockerfile creates a new image layer.
  • The image layer will be cached and reused
  • When the Dockerfile directive is changed, the copied file is changed, or the variables specified when the image is built are different, the corresponding mirror layer cache is invalidated
  • When a layer’s image cache is invalidated, all subsequent layer caches are invalidated
  • The image layer is immutable, so if we add a file to one layer and then delete it from the next, the image will still contain the file (but the file is not visible in the Docker container).

Now, let’s merge all the RUN directives into one. Also remove apt-get upgrade as it makes image builds very uncertain (we just need to rely on updates to the base image).

FROM ubuntu

ADD . /app

RUN apt-get update \
  && apt-get install -y nodejs \
  && cd /app \
  && npm install

CMD npm start
Copy the code

Remember, we can only combine instructions that change at the same rate. Combining the Node.js installation with the NPM module installation would require you to reinstall Node.js every time you change the source code, which is obviously not appropriate.

So, the correct way to write this is:

FROM ubuntu

RUN apt-get update && apt-get install -y nodejs

ADD . /app

RUN cd /app && npm install

CMD npm start
Copy the code

Do not use Latest on the labels of basic and production mirrors

When no label is specified for an image, the latest label is used by default. Therefore, the FROM Ubuntu directive is equivalent to FROM Ubuntu :latest. When the image is updated, the Latest tag points to a different image, and the build of the image may fail. If you really need to use the latest version of the base image, use the Latest tag, otherwise, it is best to specify a certain image tag.

Sample:

FROM ubuntu:16.04 # use 16.04 as the hashtag.

RUN apt-get update && apt-get install -y nodejs

ADD . /app

RUN cd /app && npm install

CMD npm start
Copy the code

Delete redundant files after each RUN instruction

Suppose we update the apt-get source, download, unpack and install some packages, all saved in the /var/lib/apt/lists/ directory. However, these files are not needed in the Docker image to run the application. We’d better remove them because it will make the Docker image bigger.

Sample:

In Dockerfile, we can delete files in /var/lib/apt/lists/ (they are generated by apt-get update).

FROM ubuntu:16.04

RUN apt-get update \
  && apt-get install -y nodejs \
  # added lines
  && rm -rf /var/lib/apt/lists/*

ADD . /app

RUN cd /app && npm install

CMD npm star
Copy the code

Set WORKDIR and CMD

The WORKDIR directive sets the default directory, which is where the RUN/CMD/ENTRYPOINT directive is RUN.

The CMD command can set container creation to be the default command to execute. In addition, you should write commands in an array where each element is each word of the command.

Sample:

FROM node:7-alpine

WORKDIR /app

ADD . /app

RUN npm install

CMD ["npm"."start"]
Copy the code

Start the command with exec when using ENTRYPOINT (optional)

In the script using EntryPoint, we run the application with the exec command. Without exec, we could not successfully close the container because the SIGTERM signal would be swallowed by the bash script process. The exec command starts the process in place of the script process, so all signals will work.

COPY is preferred to ADD

The COPY command is very simple and is used only to COPY files to an image. ADD is relatively complex and can be used to download remote files and unzip zip packages.

Sample:

FROM node:7-alpine

WORKDIR /app

COPY . /app

RUN npm install

ENTRYPOINT ["./entrypoint.sh"]

CMD ["start"]
Copy the code

Set default environment variables to map ports and data volumes

Running a Docker container will most likely require some environment variables. Setting the default environment variables in Dockerfile is a good way to do this. Also, we should set up mapped ports and data volumes in the Dockerfile.

Sample:

FROM node:7-alpine

ENV PROJECT_DIR=/app

WORKDIR $PROJECT_DIR

COPY package.json $PROJECT_DIR

RUN npm install

COPY . $PROJECT_DIR

ENV MEDIA_DIR=/media \
  NODE_ENV=production \
  APP_PORT=3000

VOLUME $MEDIA_DIR

EXPOSE $APP_PORT

ENTRYPOINT ["./entrypoint.sh"]

CMD ["start"]
Copy the code

The ENV directive specifies environment variables that can be used in containers. If you just need to specify variables to build the image, you can use the ARG directive.

Use LABEL to set image metadata

Using the LABEL directive, you can set metadata for the image, such as the image creator or the image description. The old Dockerfile syntax used the MAINTAINER directive to specify the image creator, but it has been deprecated. Sometimes, some external programs need to use the mirror of the metadata, such as nvidia – docker to use com. Nvidia. Volumes. Men.

Sample:

FROM node:7-alpine

LABEL maintainer "[email protected]"
Copy the code

An image can have multiple labels. To specify multiple labels, Docker recommends merging as many labels as possible into a LABEL directive. Each LABEL directive generates a new image layer. If you use more than one LABEL, you will end up building an inefficient image.

Add HEALTHCHECK

When running the container, you can specify the –restart always option. That way, when the container crashes, the Docker daemon restarts the container. This option is useful for containers that need to run for a long time. But what if the container is actually running, but not (stuck in an endless loop, misconfigured)?

Using the HEALTHCHECK command allows Docker to periodically check the health of the container. All we need to do is specify a command that returns 0 if all is well and 1 otherwise.

Sample:

FROM node:7-alpine

LABEL maintainer "[email protected]"

ENV PROJECT_DIR=/app

WORKDIR $PROJECT_DIR

COPY package.json $PROJECT_DIR

RUN npm install

COPY . $PROJECT_DIR

ENV MEDIA_DIR=/media \
  NODE_ENV=production \
  APP_PORT=3000

VOLUME $MEDIA_DIR

EXPOSE $APP_PORT

HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1

ENTRYPOINT ["./entrypoint.sh"]

CMD ["start"]
Copy the code

The curl –fail command returns a non-zero state when the request fails.

Adjust the order of COPY and RUN

We should place the least variable part in front of the Dockerfile to make full use of the image cache.

Sample:

The source code changes frequently, and the NPM module needs to be reinstalled every time an image is built, which is obviously not desirable. So we can copy package.json first, then install the NPM module, and then copy the rest of the source code. This way, even if the source code changes, there is no need to reinstall the NPM module.

FROM node:7-alpine

WORKDIR /app

COPY package.json /app

RUN npm install

COPY . /app

ENTRYPOINT ["./entrypoint.sh"]

CMD ["start"]
Copy the code

Reference documentation

  • How to write excellent Dockerfiles