使用Docker运行一个Node项目

流程如下:






创建一个Node网页项目

创建一个Dockerfile

通过Dockerfile构建一个镜像

运行镜像

从网页链接项目


step 0 - 基础文件

  1. docker file:
# specify a base image
FROM alpine

# install dependencies
RUN npm install

# default command
CMD [ "npm", "start" ]

⚠️ 这里使用的基础镜像是 alpine。

  1. package.json:
{
"dependencies": {
    "express": "*",
    "redis": "2.8.0"
},
"scripts": {
    "start": "node index.js"
}
}
  1. index.js:
const express = require('express');

const app = express();

app.get('/', (req, res) => {
    res.send('Hi there')
});

app.listen(9999, () => {
console.log('Listening on port 9999');
});

step 1 - 基础镜像错误

运行Docker,其运行结果如下:

➜  visits-starter docker build .
Sending build context to Docker daemon  19.46kB
Step 1/3 : FROM alpine
latest: Pulling from library/alpine
213ec9aee27d: Already exists 
Digest: sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad
Status: Downloaded newer image for alpine:latest
 ---> 9c6f07244728
Step 2/3 : RUN npm install
 ---> Running in facd3767fa67
/bin/sh: npm: not found
The command '/bin/sh -c npm install' returned a non-zero code: 127

报错的原因是因为 alpine 中并没有 node 相关的包,因此直接运行 npm install 会抛出 npm: not found 的错。这个时候比较简单的解决方案:

  1. 下载、配置并安装 node
  2. 使用别人已经打包好的基础镜像,这里使用的是 node:14-alpine。我尝试使用 v16,不过似乎是有一些配置问题,直接运行会导致报错。
    目前 v14 的支持一直到 2023-04-30……不知道之后是不是会考虑直接升级到 v18 还是 v20.

修正后的部分:

# specify a base image
# FROM alpine - remove
# and replace with
FROM node:14-alpine

运行结果:

➜  visits-starter docker build .     
Sending build context to Docker daemon  19.46kB
Step 1/3 : FROM node:14-alpine
 ---> 798752c1e2a0
Step 2/3 : RUN npm install
 ---> Running in f980f1bc6709
npm WARN saveError ENOENT: no such file or directory, open '/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/package.json'
npm WARN !invalid#2 No description
npm WARN !invalid#2 No repository field.
npm WARN !invalid#2 No README data
npm WARN !invalid#2 No license field.

up to date in 0.436s
found 0 vulnerabilities

Removing intermediate container f980f1bc6709
 ---> 9575921d8a0b
Step 3/3 : CMD [ "npm", "start" ]
 ---> Running in 2eac9cfdffcd
Removing intermediate container 2eac9cfdffcd
 ---> a6c1af8bcdbd
Successfully built a6c1af8bcdbd

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

step 2 - 文件找不到错误

虽然在运行Docker的时候没有报错,不过我尝试运行了一下 npm start 还是报错了,运行结果如下:

➜  visits-starter docker run -it a6c1af8bcdbd sh
/ # ls
bin                etc                lib                mnt                package-lock.json  root               sbin               sys                usr
dev                home               media              opt                proc               run                srv                tmp                var
/ # npm start
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path /package.json
npm ERR! errno -2
npm ERR! enoent ENOENT: no such file or directory, open '/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent 

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2022-10-06T18_04_50_719Z-debug.log
/ #

应用并没有运行,并且爆出错误 package.json 找不到的错。

会出现这个错误的原因是因为每一个运行的镜像是当前系统的一个snapshot,而本地的文件——package.json, index.js——并没有复制到镜像中,因此在运行的时候,node会找不到package.json去运行。

这个时候就需要使用一个新的指令:COPY 去将本地文件复制到镜像中去:

# specify a base image
FROM node:14-alpine

# copy from locak system to docker temp container
COPY ./ ./

# install dependencies
RUN npm install

# default command
CMD [ "npm", "start" ]

运行结果:

➜  visits-starter docker build .                
Sending build context to Docker daemon  19.46kB
Step 1/4 : FROM node:14-alpine
 ---> 798752c1e2a0
Step 2/4 : COPY ./ ./
 ---> 22d75ba79400
Step 3/4 : RUN npm install
 ---> Running in 5065b9da7657
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN !invalid#2 No description
npm WARN !invalid#2 No repository field.
npm WARN !invalid#2 No license field.

added 61 packages from 46 contributors and audited 61 packages in 3.026s

7 packages are looking for funding
  run `npm fund` for details

found 1 high severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details
Removing intermediate container 5065b9da7657
 ---> 05230fcb61cc
Step 4/4 : CMD [ "npm", "start" ]
 ---> Running in 372aba7de32a
Removing intermediate container 372aba7de32a
 ---> 147ae3af863d
Successfully built 147ae3af863d

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

注意这里:added 61 packages from 46 contributors and audited 61 packages in 3.026s,这是可以确认package.json中的所有文件被下载了。

随后运行这个docker镜像:

➜  visits-starter docker run 147ae3af863d       

> @ start /
> node index.js

Listening on port 9999

目前程序已经可以运行,也在监听9999这个端口,但是当打开浏览器时,浏览器没法打开该网址。

node项目打docker镜像 node-red docker_json

这是因为镜像的路由并没有设置,本地无法访问镜像内部的端口。当然,docker默认禁止对内访问,对外访问是可以进行的——不然node的包也没办法下载成功。

step 3 - 配置路由

路由配置(port mapping) 只能在运行时进行操作,因此可以运行下面的指令:

我用了tag这样输入指令起来方便点

# outsite port does not have to be same as image port 
➜  visits-starter docker run -p 9999:9999 ga/v

> @ start /
> node index.js

Listening on port 9999

运行成功后:

node项目打docker镜像 node-red docker_npm_02

step 4 - 文件结构优化

继续访问镜像并且看一下目前的目录结构:

➜  visits-starter docker run -it ga/v sh
/ # ls
Dockerfile         etc                lib                node_modules       package.json       root               srv                usr
bin                home               media              opt                pnpm-lock.yaml     run                sys                var
dev                index.js           mnt                package-lock.json  proc               sbin               tmp
/ #

这个文件比较小还不要紧,但是文件比较大的时候就可能会出现重写系统默认文件的情况。Docker也提供了可以设置working directory的指令,这样可以让所有的文件复制到该working directory下。

Dockerfile更新如下:

# specify a base image
FROM node:14-alpine

# specify a working dir
WORKDIR /usr/app
# copy from locak system to docker temp container
COPY ./ ./

# install dependencies
RUN npm install

# default command
CMD [ "npm", "start" ]

之后运行指令查看目录结构:

➜  visits-starter docker run -it  ga/v sh
/usr/app # ls
Dockerfile         index.js           node_modules       package-lock.json  package.json       pnpm-lock.yaml
/usr/app # pwd
/usr/app
/usr/app #

step 5 - 优化安装

这一步是为了可以让修改源代码不影响node包的安装。Docker对比指令,一旦发现指令与先前的不同——如修改了源码,那么整个COPY ...RUN ..., CMD ... 的步骤全都会重新运行,这也就代表着所有的依赖包都会被重新下载。

为了提升性能,可以将复制 package.json 与其他代码的操作分开,这样只要 package.json 不被修改,那么代码就不会重新下载 node 依赖包。

# specify a base image
FROM node:14-alpine

# specify a working dir
WORKDIR /usr/app
# copy from locak system to docker temp container
COPY ./package.json ./

# install dependencies
RUN npm install

COPY ./ ./

# default command
CMD [ "npm", "start" ]