[Docker] Docker Node.js project tips

发布时间 2023-08-01 14:51:33作者: Zhentiw

Basic node image

FROM node:12-stretch

COPY index.js index.js

CMD ["node", "index.js"]

Build docker image: docker build -t my-node-app .

--init

docker run my-node-app, after docker is running, you press CTRL + C, it doesn't exit node process, to pass down the CTRL + C to node process as well, we can do:

docker run --init my-node-app

If you want to remove the container after exit:

docker run --init --rm my-node-app

If you want to expose node.js app port (4040) to outside world (3030)

docker run --init --rm --publish 3030:4040 my-node-app

USER node

Node image ship your a USER called node and a group called node which you can use to run without your root user.

# Wrong
FROM node:12-stretch

COPY index.js index.js

USER node

CMD ["node", "index.js"]

If we do like that, it's still wrong. Because COPY index.js index.js happens before USER node, so it still use root user.

# Correct
FROM node:12-stretch

USER node

COPY --chown=node:node index.js index.js # --chown=<user>:<group>

CMD ["node", "index.js"]

Rebuild: docker build -t my-node-app .
Rerun: docker run --init --rm --publish 3030:4040 my-node-app whoami
Output: node

COPY vs ADD

COPY --chown=node:node index.js index.js and ADD --chown=node:node index.js index.js, on your local mahcine, it pretty much doing the same thing.

COPY: do less work than ADD
ADD: if the file is not in your local machine, it will download from network. If it's a zip file, ADD will download it and unzip it.

WORKDIR

Currently, it copy the file to the root directory of the container.

FROM node:12-stretch

USER node

WORKDIR /home/node/code # home dir, node user, code folder

COPY --chown=node:node index.js index.js 

CMD ["node", "index.js"]

Node.js with deps

  1. First we want to install dependencies inside container
FROM node:12-stretch

USER node

# create a directory for the app code as node user
# to resolve permission issue
RUN mkdir home/node/code 

WORKDIR /home/node/code

COPY --chown=node:node . .

# install dependencies
RUN npm ci 

CMD ["node", "index.js"]

Buiid: docker build -t my-node-app .
Run: docker run --init --rm --publish 3000:3000 my-node-app ls -lsah
Output:

8.0K drwxr-xr-x 1 node node 4.0K Aug  1 06:27 .
8.0K drwxr-xr-x 1 node node 4.0K Aug  1 06:28 ..
4.0K -rw-r--r-- 1 node node  148 Aug  1 06:27 Dockerfile
   0 -rw-r--r-- 1 node node    0 Aug  1 06:21 README.md
4.0K -rw-r--r-- 1 node node  554 Aug  1 06:17 index.js
8.0K drwxr-xr-x 1 node node 4.0K Aug  1 06:28 node_modules
 56K -rw-r--r-- 1 node node  55K Aug  1 06:19 package-lock.json
4.0K -rw-r--r-- 1 node node  309 Aug  1 06:19 package.json

As you can see, all the files and folders belongs to node group and user.

One thing to notice that, in the node.js app code:

  const server = hapi.server({
    host: "0.0.0.0",
    port: process.env.PORT || 3000,
  });

It uses 0.0.0.0 as host, you cannot change it to localhost. This will not allow it to escape the container when you run it on localhost. It's a hard loop back that it cannot escape itself. Bind it to 0.0.0.0, allow you to escape outside the container.

EXPOSE

You can add EXPOSE 3000 as documentation.

FROM node:12-stretch

USER node

# create a directory for the app code as node user
# to resolve permission issue
RUN mkdir home/node/code 

WORKDIR /home/node/code

COPY --chown=node:node . .

# install dependencies
RUN npm ci

EXPOSE 3000

CMD ["node", "index.js"]

Layers

Each line in Docker file has its own layer. For example COPY --chown=node:node . . has it's own layer, Docker will check whether the layer output different from cache or not, if it is, then Docker will re-calcaulte.

The problem here for the current docker file is that, if we change our source code, then COPY --chown=node:node . . is changed, we need to re-install packages again and again.

So the improved version:

FROM node:12-stretch

USER node

# create a directory for the app code as node user
# to resolve permission issue
RUN mkdir home/node/code 

WORKDIR /home/node/code

COPY --chown=node:node package.json package-lock.json ./

RUN npm ci

COPY --chown=node:node . .

EXPOSE 3000

CMD ["node", "index.js"]

.dockerignore

Every project should have .dockerignore file

node_modules/
.git/