Coding Gun

การ run Node JS ด้วย Docker

ตัวอย่างนี้จะพาไปดูขั้นตอนการนำ NodeJs Application เข้าไป run ใน docker ในตัวอย่างนี้เราจะนำตัวอย่าง To-Do Application ที่อยู่ใน image ชื่อว่า getting-start ของ docker โดยที่ขั้นตอนการทำงานกับ docker มีดังนี้

  1. Download To-Do application
  2. Build container image
  3. Start application container
  4. Update application ต้อง re-build image ใหม่
  5. Bind mounts volume

1. Download To-Do Application

อันดับแรกเราจะนำตัวอย่าง clone source code ด้วยคำสั่งนี้

git clone https://github.com/irobust/Sample-Todo-Application

หลังจากที่เราได้ source code มาแล้วนเราจะได้โครงสร้างของไฟล์แบบนี้

├── Sample-Todo-Application
│   ├── package.json
│   ├── src
│   |   ├── ...
│   |   ├── index.js
│   ├── spec
│   |   ├── ...

2. Build container image

ขั้นตอนต่อไปคือการสร้าง Dockerfile เพื่อทำงานกับ NodeJs Application แบบนี้

สุดท้ายเราจะได้ Dockerfile หน้าตาแบบนี้

FROM node:21-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
EXPOSE 3000
CMD ["node", "src/index.js"]

และ Dockerfile จะอยู่ใน path เดียวกับ package.json แบบนี้

├── Sample-Todo-Application
│   ├── Dockerfile
│   ├── package.json
│   ├── src
│   ├── spec

สุดท้ายให้สั่ง build image ด้วยคำสั่ง

docker build -t todo-app:1.0 .

3. Start application container

หลังจากที่เราได้ container image มาแล้วต่อไปให้นำ image นั้นมาสร้างเป็น container ด้วยคำสั่ง

docker run -d -p 3000:3000 todo-app:1.0

เราใส่ -d เข้าไปเพื่อให้ run container ใน detach mode หลังจากนั้นให้เข้าไปยังหน้าแรกของ application ที่ http://localhost:3000 เราจะได้ to-do application หน้าตาแบบนี้

Todo Application on docker

หลังจากที่เราลอง add item เข้าไปจะเป็นดังรูป

ตัวอย่าง Todo Application บน docker

4. Update application

ถ้าเราต้องการ update application ใหม่สิ่งที่เราต้องทำตือลบ image ตัวเก่าออกแล้ว pack ตัวใหม่ใส่เข้าไปแทน โดยจะมีขั้นตอนต่างๆ ดังนี้

  1. แก้ไข source code บรรทัดที่ 56 ในไฟล์ src/static/js/app.js ให้เป็นข้อความนี้
<p className="text-center">You have no todo items yet! Add one above!</p>
  1. ทำการ build image ตัวใหม่ (COPY code ชุดใหม่เข้าไปใส่ใน container image)
docker build -t todo-app:1.1 .

ถ้าต้องการให้ build image ใหม่ทั้งหมดโดยไม่ใช้ ข้อมูลจากใน cache เดิมเลยคือ

docker build --no-cache -t todo-app:1.1 .
  1. ถ้าคุณสั่ง docker run เลยเราจะเจอ error แบบนี้
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell (bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.

เนื่องจากถ้าเรายัง run container ตัวเก่าค้างอยู่ port 3000 จะใช้งานไม่ได้เราต้องลบ container ตัวเก่าออกก่อน

docker ps

เราต้องสั่ง docker ps เพื่อนำค่าของ container id มาใส่ใน docker rm

docker rm -f [container-id]
  1. run container ใหม่ได้แล้ว
docker run -dp 3000:3000 todo-app:1.1

5. Bind mounts volume

เนื่องจากตอนนี้เราใช้ sqlite เป็น database และจัดเก็บไฟล์ไว้ใน path /etc/todos/todo.db ซึ่ง path นี้ถูกเก็บไว้ใน container นั่นหมายความว่าถ้าเราลบ container ออก ไฟล์ todo.db ก็จะหายไปด้วย ในการแก้ไขปัญหานี้เราจะต้องสร้าง volume และ mount เข้าไปใน container ด้วยขั้นตอนนี้

  1. สร้าง docker volume ใหม่ขึ่้นมา เราตั้งชื่อว่า todo-db
docker volume create todo-db
  1. Stop to-do application container
docker stop [container-id]
# หรือ
docker rm -f [container-id]
  1. run container ขึ้นมาใหม่ด้วยคำสั่ง run โดยเพิ่ม -v todo:/etc/todos หมายความว่าเราจะ mount volume ชื่อว่า todo-db เข้าไปใน path /etc/todos
docker run -dp 3000:3000 -v todo-db:/etc/todos todo-app:1.1
  1. ทดสอบลบ container ออกด้วยคำสั่ง
docker rm -f [container-id]

หลังจากนั้น run คำสั่ง docker run อีกครั้ง

 docker run -dp 3000:3000 -v todo-db:/etc/todos todo-app:1.1
  1. เข้าไปใน http://localhost:3000 ควรจะมี item ที่เราได้ add ไว้ก่อนจะลบ image ยังคงอยู่ในหน้าจอเหมือนเดิม เพราะ docker rm จะลบเฉพาะ container ไม่ได้ลบ volume

  2. เข้าไปดูว่าจริงๆแล้ว file todo.db ถูกเก็บอยู่ที่ไหน

docker volume inspect todo-db

หลังจาก inspect เข้าไปใน volume เราจะพบที่อยู่ของ path โดยดูที่ Mountpoint

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]
Phanupong Permpimol
Follow me