He who will not rest will not work. Lenin

This article will not explain the use of Docker command, installation, etc., because the previous article teaches you how to learn Docker entry into practice has been explained in detail, if not clear, you can click the link to go back and look again. This article focuses on how to implement Docker containerization and some practical optimization in Node.js project, as well as some common problems. Of course, if you have any other problems in use, you are welcome to leave a comment in the comment section.

About the author: Jun May, Nodejs Developer, a post-90s youth who loves technology and likes sharing. The public account “Nodejs Technology Stack”, Github open source project www.nodejs.red

What can you learn from this passage?

  • How to container a Node.js service with Docker
  • Dynamically set environment variables to build different versions of a Dockerfile file
  • How does the Node.js private NPM package authenticate when building an image
  • Egg.js framework Docker container should pay attention to the problem
  • Docker image volume and build time optimization

Docker transforms a Node.js application

We’ll start this article by creating a simple Node.js application, then creating a Docker image for the application, and building and running it

Create the Node.js project

First we need to create an app.js to start an HTTP service, then we will use Docker to run the program

const http = require('http');
const PORT = 30010;

const server = http.createServer((req, res) = > {
    res.end('Hello Docker');
})

server.listen(PORT, () => {
    console.log('Running on http://localhost:', PORT, 'NODE_ENV', process.env.NODE_ENV);
});
Copy the code

Json file, which describes your application and its dependencies. If you have written Node.js, you should be familiar with it. NPM run dev and NPM run pro are added to scripts. Because I want to show you how to pass in parameters at build time to dynamically set environment variables.

{ 
    "name": "hello-docker"."version": "1.0.2"."description": ""."author": "May"."main": "app.js"."scripts": {
      "dev": "NODE_ENV=dev node app.js"."pro": "NODE_ENV=pro node app.js"}}Copy the code

Dockerfile file

This is the information contained in a Dockerfile file. These commands are also explained in Docker Getting Started and Practices

FROM node:10.0-alpine

RUN apk --update add tzdata \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk del tzdata

RUN mkdir -p /usr/src/nodejs/

WORKDIR /usr/src/nodejs/

# add npm package
COPY package.json /usr/src/nodejs/package.json
RUN cd /usr/src/nodejs/
RUN npm i

# copy code
COPY . /usr/src/nodejs/

EXPOSE 30010

CMD npm run dev
Copy the code

Create a.dockerignore file under the Dockerfile sibling to avoid putting your local debug files, node_modules, and so on into the Docker container

.git
node_modules
npm-debug.log
Copy the code

At this point, you can build a Docker image by running the following command

$ docker image build -t mayjun/hello-docker
Copy the code

Docker run -d -p 30010:30010 mayjun/hello-docker can run a docker container, but there is a problem. There is only one environment that can be packaged by CMD NPM run dev above, but you can also create a file to implement it or some other method.

Dynamically set environment variables

In order to solve the above problem, MY idea is to pass in parameters to dynamically set the environment variables during the image build, and make changes to the Dockerfile file. See the following implementation:

EXPOSE 30010

ARG node_env # new
ENV NODE_ENV=$node_env  # new
CMD npm run ${NODE_ENV} # modified
Copy the code

Here’s an explanation of the code above

  • The ARG directive defines a variable that the user can pass to the builder at build time by using the Docker build command with the –build-arg = flagARG node_env
  • Use ENV to reference this variable in a DockerfileENV NODE_ENV=$node_env
  • That’s the step to useCMD npm run ${NODE_ENV}

All that is left is to pass in parameters dynamically when building the image

$docker image build --build-arg node_env=dev -t mayjun/hello-docker:1.0.2Build the test environment$docker image build --build-arg node_env= pro-t mayjun/hello-docker:1.0.2# Build the production environment
Copy the code

Run the container

$ docker run -d-p 30010:30010 mayjun/hello-docker:1.0.2 $docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2BC6e62cd0 e8 mayjun/hello - docker: 1.0.2"/ bin/sh -c 'NPM run..."3 minutes ago Up 3 minutes 0.0.0.0:30010->30010/ TCP Elastic_boumanCopy the code

Viewing container Logs

Docker logs -f 2bc6e62cd0e8 > [email protected] dev /usr/src/nodejs > NODE_ENV=dev node app.js Running on http://localhost: 30010 NODE_ENV devCopy the code

Docker pull Mayjun /hello-docker:1.0.2 docker pull Mayjun /hello-docker:1.0.2

Docker and Node.js private NPM packages

If you are using private NPM packages in your project, there will be an error during the Dcoker image building process. If it is outside the container, you can use NPM login to login to the account that has the permission of the NPM private package to solve this problem. But you can’t do that with Docker.

Create the authentication token

To install the private package we need to “create an authentication token” so that we can access our private NPM package within the continuous integration environment and Docker container. How to create this can be found at docs.npmjs.com/creating-an…

Implementation method

We need to add the following two commands to create Dockerfile:

# 528DAS62-e03e-4dc2-ba67-********** This Token is the authentication Token created for you
RUN echo "//registry.npmjs.org/:_authToken=528das62-e03e-4dc2-ba67-**********" > /root/.npmrc
RUN cat /root/.npmrc
Copy the code

Egg framework Docker container

In the Egg, if it is egg-scripts start –daemon, remove –daemon and go to egg-scripts start, otherwise the Docker container will not start.

Json. The Dockerfile file is the same as the first Docker-generated Node.js application above

package.json

{
  "scripts": {
    "start": "egg-scripts start" / / remove - daemon}}Copy the code

Docker container can not run, did you encounter it? Github.com/eggjs/egg/i…

Docker image volume and build time optimization

If a mirror is not optimized, the volume is usually very large. The following are some optimizations made in practice.

The RUN/COPY stratification

Each instruction in a Dockerfile creates an image layer, and each image layer can be reused and cached without changes to the Dockerfile instruction or copied project file.

The following code can be found in the Mayjun/Hello-docker :latest repository. In the following example, the NPM module is reinstalled after the source code is changed regardless of whether package.json is changed

#...

WORKDIR /usr/src/nodejs/hello-docker
COPY . /usr/src/nodejs/hello-docker

RUN npm install

#...
Copy the code

The improved code is shown below. We have made package.json ahead of schedule so that the NPM package will not be reinstalled without changes to package.json, which will also reduce deployment time.

#...

WORKDIR /usr/src/nodejs/

# add npm package
COPY package.json /usr/src/app/package.json
RUN cd /usr/src/app/
RUN npm i

# copy code
COPY . /usr/src/app/

#...
Copy the code

Node.js Alpine mirror optimization

Mayjun /hello-docker:1.0.0 This image is also searchable in the Docker repository and is about 688MB before optimization

$Docker images REPOSITORY TAG IMAGE ID CREATED SIZE Mayjun/Hello – Docker 1.0.0 7217fb3e9daa 5 seconds ago 688MB

Optimization with Alpine

Alpine is a small Linux distribution, and it is easiest to choose the Alpine version of Node.js if you want to dramatically reduce the image size. In addition, the time zone of Alpine is not domestic by default, and requires Dockerfile to configure the time zone.

FROM node:10.0-alpine

RUN apk --update add tzdata \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk del tzdata

RUN echo "Asia/Shanghai" > /etc/timezone

RUN mkdir -p /usr/src/nodejs/

WORKDIR /usr/src/nodejs/

# add npm package
COPY package.json /usr/src/app/package.json
RUN cd /usr/src/app/
RUN npm i

# copy code
COPY . /usr/src/app/

EXPOSE 30010
CMD npm start
Copy the code

Repackaged mayjun/ Hello-Docker :1.1.0 If you look at the effect again, you can see that the image file has been reduced from 688MB to 85.3MB, which is still a large volume optimization

$Docker Images REPOSITORY TAG IMAGE ID CREATED SIZE Mayjun/Hello docker 1.1.0 169e05b8197d 3 minutes ago 85.3MBCopy the code

Do not pack the devDependencies package in production

Json file devDependencies object, filtered by specifying the production parameter after NPM I

The improvements are as follows:

FROM node:10.0-alpine

# omit...

# add npm package
COPY package.json /usr/src/app/package.json
RUN cd /usr/src/app/
RUN npm i --production # Change is here

# omit...
Copy the code

Repackaged a version of Mayjun/Hello-docker :1.2.0. If you look again, you can see that the image file has been reduced from 85.3MB to 72.3MB

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mayjun/hello-docker   1.2.0               f018aa578711        3 seconds ago       72.3MB
Copy the code

Q&A

Question1

The following command generates the following error when deleting a mirror:

$ docker rmi 6b1c2775591e
Error response from daemon: conflict: unable to delete 6b1c2775591e (must be forced) - image is referenced in multiple repositories
Copy the code

If you are careful, you may find that the image ID 6b1C2775591e points to both hello-Docker and Mayjun/hello-Docker repositories, which is also the cause of the deletion failure

$Docker images REPOSITORY TAG IMAGE ID CREATED SIZE mysql 5.7 383867b75fd2 6 days ago 373MB Hello - Docker latest 6b1c2775591e 7 days ago 675MB mayjun/hello-docker latest 6b1c2775591e 7 days ago 675MBCopy the code

Delete mayjun/ Hello-docker from mayjun/ Hello-Docker by using a repository and tag

$ docker rmi mayjun/hello-docker
$ docker images                 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql               5.7                 383867b75fd2        6 days ago          373MB
hello-docker        latest              6b1c2775591e        7 days ago          675MB
Copy the code

Question2

The following error message is displayed when you run the command to delete an image:

$ docker rmi 9be467fd1285
Error response from daemon: conflict: unable to delete 9be467fd1285 (cannot be forced) - image is being used by running container 1febfb05b850
Copy the code

If a container is running, stop the container, delete the container, and then delete the image

$ docker container kill 1febfb05b850 # stop container
$ docker rm 1febfb05b850 # delete container
$ docker rmi 9be467fd1285 # delete mirror
Copy the code

Question3

Set the WORKDIR to be the same as below

. WORKDIR /usr/src/nodejs/# add npm package
COPY package.json /usr/src/node/package.json The directory is inconsistent
RUN cd /usr/src/node/ The directory is inconsistent
RUN npm i
...
Copy the code

For example, if the working directory is different from the actual COPY directory, the following error will be reported:

Then change it to consistent as follows

. WORKDIR /usr/src/nodejs/# add npm package
COPY package.json /usr/src/nodejs/package.json # change to consistent
RUN cd /usr/src/nodejs/ # change to consistent
RUN npm i
...
Copy the code