上一张介绍了如何 build 一个客製化的 image 与 build image 未遇到的问题与步骤,接着要来 build 一个真正的专案,虽然说是真正的专案但也只是一个简易版的 NodeJS Web Server,不过方法与流程都大同小异,所以可以透过这个步骤创建出各种不同的 image。
NodeJS Server Setup
首先先创建一个新的资料夹并命名为 simpleNodeServer
并进到资料夹中
mkdir simpleNodeServercd simpleNodeServer
接着创建一个 package.json file,并对他进行编辑
touch package.jsonvim package.json
{"devDependencies": { "@types/express": "^4.17.13", "@types/node": "^17.0.23", "nodemon": "^2.0.15", "ts-node": "^10.7.0", "typescript": "^4.6.3","express": "^4.17.3" },"scripts": { "start": "nodemon index.ts" },}
接着创建一个 index.ts
file 并输入预设的内容以创建一个基本的 nodeJS Server
touch index.tsvim index.ts
import express from 'express';const app = express();const port = 3000;app.get('/', (req, res) => { res.send('The server is working!');});app.listen(port, () => { console.log(`server is listening on ${port} !!!`);});
Create Dockerfile
设定完基础的 NodeJS Server 后,接着来设定 Dockerfile
touch Dockerfilevim Dockerfile
FROM alpineRUN npm installCMD ["npm", "start"]
FROM: 透过 base image
创建一个新的 ImageRUN: 创建 Image 时须要执行的命令CMD: 利用这个 Image 创建 Container 后预设所执行的指令以上面的例子来说,会利用 apline
这个 base image
创建一个 Image,接着在创建 Image 的过程中需要执行 npm install
这个命令,最后当利用这个 Image 创建一个 Container 时会预设执行 npm start
这个指令。
接着使用 docker build .
来将这个 NodeJS Server Image build 起来
docker build .
接着可以看到我们的终端机中出现了错误,主要是因为 npm: not found
Base Image Issues
会造成这样的原因是因为我们使用的 base image
是一个非常小的 Image,所以他并不包含能够执行 npm install
的内容,所以应该要使用带有可以执行 npm install
的 base image
FROM node:14-alpineRUN npm installCMD ["npm", "start"]
透过使用可以执行 npm install 的 base image 便可以将 NodeJS Server build 出来,接着利用 image id 创建一个 Container 吧。
docker run -it 15c18aa74f8507a785e96a45f17b54b34d365d8774c99e7d239da45a3aecbdfb
还是出错了!这次出错的问题在于当在创建 image 时所执行的 npm install 他需要有 package.json
才能够正常执行,而我们创建的 image 中并没有 package.json file,所以导致错误。
A Few Missing Files
所以我们需要将我们需要附加在的档案加在 Image 中,这样在创建 Image 时才能够完整的执行指令,这时就需要利用 COPY
指令複製档案到 Image 中。
利用 COPY
指令将我们机器上面的某个档案(相对路径)複製到 Container 中的某个位置上,所以我们需要将我们机器中的所有档案都複製到 Container 中
FROM node:14-alpineCOPY ./ ./RUN npm installCMD ["npm", "start"]
这样的话当我们使用 image 创建 Container 时就有 package.json file 可以提供给 npm install
指令使用。
docker build .## [+] Building 9.8s (9/9) FINISHED## => [internal] load build definition from Dockerfile 0.0s## => => transferring dockerfile: 112B 0.0s## => [internal] load .dockerignore 0.0s## => => transferring context: 2B 0.0s## => [internal] load metadata for docker.io/library/node:14-alpine 2.5s## => [auth] library/node:pull token for registry-1.docker.io 0.0s## => [internal] load build context 0.1s## => => transferring context: 684B 0.1s## => CACHED [1/3] FROM docker.io/library/node:14-alpine@sha256:87641a998f00bee1bad8ad15e00f05ac41d96a7093a6b50c5cf8540dda1b65a6 0.0s## => [2/3] COPY ./ ./ 0.0s## => [3/3] RUN npm install 6.7s## => exporting to image 0.3s## => => exporting layers 0.3s## => => writing image sha256:09de30e6003ab9adb4de287dbf8ffa72201941bf537681c10f5c5742e2c427c6 0.0s## Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix themdocker run -it 09de30e6003ab9adb4de287dbf8ffa72201941bf537681c10f5c5742e2c427c6## > @ start /## > nodemon index.ts## [nodemon] 2.0.15## [nodemon] to restart at any time, enter `rs`## [nodemon] watching path(s): *.*## [nodemon] watching extensions: ts,json## [nodemon] starting `ts-node index.ts`## server is listening on 3000 !!!
接着我们到网页中看看 localhost: 3000 是否有东西
Container Port Mapping
简单来说我们在 Container 中执行了 npm start
确实是有在 [localhost](http://localhost) 3000
成功创建一个 Server,但这个 localhost 3000
是在 Container 中的 Port
,所以我们本机的 localhost
无法成功连结到 Container 的 localhost
。
所以我们要做的就是将我们本机的 Port 3000 连结到 Container 内部的 Port 3000,这样都浏览器向 localhost: 3000 发出请求时,就可以连接到 Container 中的 Port 3000。
所以可以使用 docker run 中的其中一个 Option
利用 -p
option 将本机指定的 Port 连接到 Container 中指定的 Port
docker run -it -p 3000:3000 09de30e6003ab9adb4de287dbf8ffa72201941bf537681c10f5c5742e2c427c6
这样就可以顺利连结到 localhost: 3000 了,由于是将本机的 Port 连接到 Container 的 Port,所以也可以
docker run -it -p 8080:3000 09de30e6003ab9adb4de287dbf8ffa72201941bf537681c10f5c5742e2c427c6
这样就会变成本机的 Port 8080 连接到 Container 的 Port 3000。
Specifying a Working Directory
我们可以开启另一个终端机看一下透过 NodeJS Server Image 创建的 Container 裏面的结构
docker ps## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES## b38d0ae47f41 09de30e6003a "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 0.0.0.0:3000->3000/tcp practical_jepsendocker exec -it 00302f0de178 sh/ # ls## Dockerfile index.ts opt run usr## bin lib package-lock.json sbin var## dev media package.json srv## etc mnt proc sys## home node_modules root tmp
透过 ls
指令可以看到我们利用 COPY
指令複製的所有档案都放在 Container 的根目录,这样对于大型的 Docker Project 来说会很难管理,所以可以利用 WORKDIR
指令决定要将複製的档案放在哪一个资料夹。
FROM node:14-alpineWORKDIR /usr/appCOPY ./ ./RUN npm installCMD ["npm", "start"]
这将就可以将複製的档案通通存放在 /usr/app
当中了。
在我们 build image 的时候可以多添加一个 option -t
,主要的目的是可以将这个 Image 新增一个 tag,这样当要 run 这个 image 时就可以使用这个 tag 而不用使用 image id。
docker build -t fandix/image/1 .docker run -it -p 3000:3000 fandix/image/1## > @ start /usr/app## > nodemon index.ts## [nodemon] 2.0.15## [nodemon] to restart at any time, enter `rs`## [nodemon] watching path(s): *.*## [nodemon] watching extensions: ts,json## [nodemon] starting `ts-node index.ts`## server is listening on 3000 !!!
接着开启另一个终端机观看一下 Container 的内部状况
docker ps## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES## 4e6932d216d7 fandix/image/1 "docker-entrypoint.s…" 39 seconds ago Up 38 seconds 0.0.0.0:3000->3000/tcp romantic_khayyamdocker exec -it 4e6932d216d7 sh## /usr/app ls## Dockerfile index.ts node_modules package-lock.json package.json
当我们使用 sh
进入 Container 的 shell
之后,预设的路径是刚刚利用 WORKDIR
指令设定的 /usr/app
而不是一开始的根目录,透过 ls
指令可以看到所有里用 COPY
指令複製的档案都存放在这里,当我们移动到 Container shell 的根目录时可以看到变得非常乾净
## /usr/app cd /ls## bin etc lib mnt proc run srv tmp var## dev home media opt root sbin sys usr
Unnecessary Rebuild
当我们要更改 index.ts
中的内容时,就需要重新 rebuild 一次 image,而 build 每次 image 就需要
一直重複的执行 npm install
指令,但明明我们的 package.json 并没有更改,所以没有必要每次都重新执行 npm install
,这时可以将 build image 的过程分为两部分。
由于只有执行 RUN npm install
这个命令才会需要使用 package.json
,所以没有必要在 RUN npm install
之前把所有的档案複製一次,所以在执行 RUN npm install
之前只需要複製 package.json
就好,那么 Dockerfile
就可以更改为
COPY ./package.json ./RUN npm install
接着在做完 RUN npm install
这个指令后,再将其他的档案複製回 Container 中就好,这样就可以确保当更改其他档案的时候,并不会影嚮到 package.json
导致需要重新执行 npm install
。
COPY ./package.json ./RUN npm installCOPY ./ ./