Skip to content

13.2 Container & runtime (Dockerfiles)

บทนี้เปรียบเทียบ Dockerfile ของทั้ง 5 services และชี้ปัญหาที่มีผลต่อการ build และรันใน production

สรุปเปรียบเทียบ Dockerfile

Serviceไฟล์Base imageMulti-stageติดตั้ง system packageEXPOSE / CMD
vtrc-apivtrc-api/api/Dockerfile:1node:12ไม่ใช้qpdfCMD ["npm","start"]
cu-central-apicu-central-api/main-api/Dockerfile:1node:12.18.3ไม่ใช้opensslEXPOSE 9000, CMD ["npm","start"]
centralize-apicentralize-api/Dockerfile:1,15node:16ใช้ (develop + production)ไม่มีEXPOSE 3000, CMD ["yarn","start:prod"]
vtrc-webvtrc-web/Dockerfile:1,11node:14.20 + nginx:1.24.0ใช้ (stage-1 build, stage-2 nginx)ไม่มีไม่กำหนด CMD (ใช้ default ของ nginx)
vtrc-rc-backofficevtrc-rc-backoffice/Dockerfile:1,13node:16 + nginx:1.24.0ใช้ (stage-1 build, stage-2 nginx)ไม่มีไม่กำหนด CMD (ใช้ default ของ nginx)

vtrc-api Dockerfile

vtrc-api/api/Dockerfile:1

dockerfile
FROM node:12

WORKDIR /usr/src/app
COPY package*.json ./

RUN npm install --only=production
RUN apt-get update
RUN apt-get install qpdf -y

COPY . .

CMD ["npm", "start"]

จุดสังเกต:

  • Node 12 — End of Life เมษายน 2022 (อายุเกิน 4 ปี) ไม่มี patch ความปลอดภัย
  • ติดตั้ง qpdf ผ่าน apt-get — ใช้สำหรับเข้ารหัส PDF payslip (รายละเอียดใน Volume 11 บทที่ 4) ทำให้ image ผูกติดกับ system package นี้
  • ไม่มี multi-stage build — image สุดท้ายมีทั้ง node_modules ฝั่ง dev และ source code ทั้งหมด ขนาดใหญ่กว่าที่ควร
  • มี PhantomJS ที่ comment ไว้Dockerfile:6-11 แสดงว่าเคยใช้ PhantomJS สำหรับสร้าง PDF แต่ถูกปิดไปแล้ว เนื่องจาก PhantomJS ยุติการพัฒนาในปี 2018
  • ไม่มี .dockerignore ที่ครอบคลุมCOPY . . คัดลอกทุกอย่างใน working directory เข้า image รวมถึง .git, node_modules (ถ้ามี), logs/, storage/

cu-central-api Dockerfile

cu-central-api/main-api/Dockerfile:1

dockerfile
FROM node:12.18.3

WORKDIR /usr/src/app
COPY package*.json ./

RUN apt-get install -y openssl

RUN npm install

COPY . .

EXPOSE 9000

CMD ["npm", "start"]

จุดสังเกต:

  • Node 12.18.3 — ระบุ minor version เจาะจงกว่า vtrc-api แต่ก็ยังเป็นสาย EOL
  • npm install ไม่มี flag — ติดตั้งทั้ง dependencies และ devDependencies ทำให้ image มีขนาดใหญ่กว่าที่จำเป็น
  • EXPOSE 9000 — แต่พอรันใน docker-compose ใช้ port 8000 (cu-central-api/docker-compose.yml:53,72,89) ความขัดแย้งนี้บ่งชี้ว่า EXPOSE ใน Dockerfile ไม่ได้ถูกใช้จริง
  • ไม่มี user ที่ไม่ใช่ root — รันในฐานะ root ภายใน container
  • ไม่มี healthcheck — ไม่มีคำสั่ง HEALTHCHECK ใน Dockerfile

centralize-api Dockerfile (NestJS)

centralize-api/Dockerfile:1

dockerfile
FROM node:16 AS develop

WORKDIR /usr/src/app

ENV TZ="Asia/Bangkok"

COPY package.json ./

COPY . .

RUN yarn install --ignore-engines

RUN yarn build

FROM node:16 AS production

WORKDIR /usr/src/app

COPY --from=develop /usr/src/app/.env .
COPY --from=develop /usr/src/app/dist ./dist
COPY --from=develop /usr/src/app/package.json ./package.json

RUN yarn install --ignore-engines

EXPOSE 3000

CMD [ "yarn", "start:prod"]

จุดสังเกต:

  • เป็น Dockerfile เดียวที่ใช้ multi-stage build อย่างถูกต้อง — stage develop build TypeScript เป็น dist/, stage production คัดลอกเฉพาะ dist/, package.json, และ .env
  • yarn install --ignore-engines — เพิกเฉยต่อข้อกำหนดเครื่องยนต์ของ package ที่ dependency ระบุ ทำให้ peer dependency ที่ขัดแย้งกันถูกปิดไปแบบเงียบ ๆ (centralize-api/Dockerfile:11,23)
  • COPY --from=develop /usr/src/app/.env . — คัดลอก .env จาก build stage เข้า image production หมายความว่า .env ต้องอยู่ใน build context ตอน build จริง ๆ ไม่ใช่ถูก inject ตอน runtime
  • Node 16 — End of Life กันยายน 2023
  • ไม่มี lockfilels centralize-api/yarn.lock ไม่พบไฟล์ ทำให้ yarn install แต่ละครั้งอาจได้ผลลัพธ์ต่างกัน

vtrc-web Dockerfile (multi-stage build → nginx)

vtrc-web/Dockerfile:1

dockerfile
FROM node:14.20 AS stage-1

WORKDIR /usr/src/app

ADD . .

RUN npm install --legacy-peer-deps

RUN npm run build

FROM nginx:1.24.0 AS stage-2

COPY  ./nginx-config/default.conf /etc/nginx/conf.d/

COPY --from=stage-1 usr/src/app/build/ /usr/share/nginx/html/build/

COPY --from=stage-1 usr/src/app/src/assets/images/07_ไฟล์แนบ_แนวปฏิบัติสำหรับผู้ได้รับอุบัติ.pdf /usr/share/nginx/html/build/assets/07_ไฟล์แนบ_แนวปฏิบัติสำหรับผู้ได้รับอุบัติ.pdf

COPY --from=stage-1 usr/src/app/src/assets/images/16_แบบแจ้งรายการเพื่อการหักลดหย่อนภาษี.pdf /usr/share/nginx/html/build/assets/16_แบบแจ้งรายการเพื่อการหักลดหย่อนภาษี.pdf

จุดสังเกต:

  • สอง stage — stage-1 build ด้วย Node 14.20, stage-2 serve ด้วย nginx 1.24.0
  • ชื่อไฟล์ PDF ภาษาไทยDockerfile:17,19 มี COPY ของไฟล์ PDF ที่มีชื่อภาษาไทย ทำให้ build อาจมีปัญหาเมื่อ file system ของ build host ไม่รองรับ UTF-8 ได้เต็มที่
  • ไม่กำหนด CMD — ใช้ default CMD ของ nginx base image
  • npm install --legacy-peer-deps — เพิกเฉยต่อ peer dependency conflict (เช่นเดียวกับ centralize-api)
  • ไม่มี EXPOSE — nginx จะรับ traffic ผ่านการ publish port ใน docker-compose โดยตรง

vtrc-rc-backoffice Dockerfile (CRA → nginx)

vtrc-rc-backoffice/Dockerfile:1

dockerfile
FROM node:16 AS stage-1

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install --legacy-peer-deps

COPY . .

RUN CI=false GENERATE_SOURCEMAP=false NODE_OPTIONS=--max-old-space-size=4096 npm run build

FROM nginx:1.24.0 AS stage-2

COPY ./nginx/default.conf /etc/nginx/conf.d/

COPY --from=stage-1 /usr/src/app/build/ /usr/share/nginx/html/build/

จุดสังเกต:

  • Node 16 — End of Life กันยายน 2023
  • CI=false GENERATE_SOURCEMAP=false — ปิดการแจ้ง error ของ CRA และปิด source map เพื่อลดขนาด bundle
  • NODE_OPTIONS=--max-old-space-size=4096 — เพิ่ม memory limit ของ Node เป็น 4 GB เพื่อรองรับการ build bundle ที่ใหญ่มาก (backoffice มี dependency มากกว่า 130 MB) — รายละเอียดใน Volume 7 บทที่ 2
  • build context รวม source เดียว — ไม่มี npm install --only=production แยก ติดตั้งทั้ง dev dependency ที่ใช้ตอน build

เปรียบเทียบปัญหาที่พบทั้งหมด

ปัญหาvtrc-apicu-central-apicentralize-apivtrc-webvtrc-rc-backoffice
Node EOL🔴 12🔴 12🔴 16🟡 14🔴 16
Multi-stage buildไม่ใช้ไม่ใช้ใช้ใช้ใช้
Production-only installใช้ (--only=production)ไม่ใช้ไม่ใช้ (ทั้งสอง stage)ไม่ใช้ไม่ใช้
--ignore-engines / --legacy-peer-depsไม่ใช้ไม่ใช้ใช้ใช้ใช้
ติดตั้ง system packageqpdfopensslไม่มีไม่มีไม่มี
ระบุ EXPOSEไม่ระบุ90003000ไม่ระบุไม่ระบุ
ระบุ user ที่ไม่ใช่ rootไม่ระบุไม่ระบุไม่ระบุไม่ระบุไม่ระบุ
มี HEALTHCHECKไม่มีไม่มีไม่มีไม่มีไม่มี
มี lockfileมี package-lock.jsonมี package-lock.jsonไม่มีมีทั้ง yarn.lock และ package-lock.jsonมี package-lock.json

ขนาด image โดยประมาณ

จากโครงสร้าง Dockerfile สามารถประมาณการขนาด image ได้ดังนี้:

Serviceประมาณการขนาด imageสาเหตุหลัก
vtrc-apiใหญ่มาก (> 1.2 GB)Node 12 base + node_modules + source code + qpdf
cu-central-apiใหญ่มาก (> 1.2 GB)ติดตั้งทั้ง devDependencies ใน production
centralize-apiปานกลาง (~ 600 MB)Multi-stage แยก dist/ แต่ยังใช้ Node 16
vtrc-webเล็ก (< 200 MB)Multi-stage ทิ้ง Node ไป ใช้ nginx serve static
vtrc-rc-backofficeเล็ก (~ 200 MB)เช่นเดียวกัน — build static แล้ว serve ด้วย nginx

ข้อแนะนำเพื่อปรับปรุง

ข้อแนะนำต่อไปนี้เสนอทิศทางการแก้ไข ไม่ใช่สิ่งที่ต้องทำทันที — ทุกข้อต้องประสานงานกับทีมปฏิบัติการก่อน

  1. ยกเครื่อง Node runtime — ทุก service ควรใช้ Node 20 LTS ขั้นต่ำ ขจัดความเสี่ยงด้านความปลอดภัย
  2. เพิ่ม multi-stage build ใน vtrc-api และ cu-central-api — แยก build dependency ออกจาก production image
  3. commit lockfile ของ centralize-api — ทำให้การ build ทำซ้ำได้ผลลัพธ์เดิม
  4. เพิ่ม HEALTHCHECK ในทุก Dockerfile — ช่วยให้ docker-compose ตรวจสอบและ restart container ได้ถูกต้อง
  5. กำหนด user ที่ไม่ใช่ root — ลดความเสียหายถ้ามีผู้โจมตีสามารถสั่งคำสั่งใน container ได้