13.2 Container & runtime (Dockerfiles)
บทนี้เปรียบเทียบ Dockerfile ของทั้ง 5 services และชี้ปัญหาที่มีผลต่อการ build และรันใน production
สรุปเปรียบเทียบ Dockerfile
| Service | ไฟล์ | Base image | Multi-stage | ติดตั้ง system package | EXPOSE / CMD |
|---|---|---|---|---|---|
| vtrc-api | vtrc-api/api/Dockerfile:1 | node:12 | ไม่ใช้ | qpdf | CMD ["npm","start"] |
| cu-central-api | cu-central-api/main-api/Dockerfile:1 | node:12.18.3 | ไม่ใช้ | openssl | EXPOSE 9000, CMD ["npm","start"] |
| centralize-api | centralize-api/Dockerfile:1,15 | node:16 | ใช้ (develop + production) | ไม่มี | EXPOSE 3000, CMD ["yarn","start:prod"] |
| vtrc-web | vtrc-web/Dockerfile:1,11 | node:14.20 + nginx:1.24.0 | ใช้ (stage-1 build, stage-2 nginx) | ไม่มี | ไม่กำหนด CMD (ใช้ default ของ nginx) |
| vtrc-rc-backoffice | vtrc-rc-backoffice/Dockerfile:1,13 | node: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
developbuild TypeScript เป็นdist/, stageproductionคัดลอกเฉพาะ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
- ไม่มี lockfile —
ls 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 เพื่อลดขนาด bundleNODE_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-api | cu-central-api | centralize-api | vtrc-web | vtrc-rc-backoffice |
|---|---|---|---|---|---|
| Node EOL | 🔴 12 | 🔴 12 | 🔴 16 | 🟡 14 | 🔴 16 |
| Multi-stage build | ไม่ใช้ | ไม่ใช้ | ใช้ | ใช้ | ใช้ |
| Production-only install | ใช้ (--only=production) | ไม่ใช้ | ไม่ใช้ (ทั้งสอง stage) | ไม่ใช้ | ไม่ใช้ |
--ignore-engines / --legacy-peer-deps | ไม่ใช้ | ไม่ใช้ | ใช้ | ใช้ | ใช้ |
| ติดตั้ง system package | qpdf | openssl | ไม่มี | ไม่มี | ไม่มี |
| ระบุ EXPOSE | ไม่ระบุ | 9000 | 3000 | ไม่ระบุ | ไม่ระบุ |
| ระบุ 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 |
ข้อแนะนำเพื่อปรับปรุง
ข้อแนะนำต่อไปนี้เสนอทิศทางการแก้ไข ไม่ใช่สิ่งที่ต้องทำทันที — ทุกข้อต้องประสานงานกับทีมปฏิบัติการก่อน
- ยกเครื่อง Node runtime — ทุก service ควรใช้ Node 20 LTS ขั้นต่ำ ขจัดความเสี่ยงด้านความปลอดภัย
- เพิ่ม multi-stage build ใน vtrc-api และ cu-central-api — แยก build dependency ออกจาก production image
- commit lockfile ของ centralize-api — ทำให้การ build ทำซ้ำได้ผลลัพธ์เดิม
- เพิ่ม
HEALTHCHECKในทุก Dockerfile — ช่วยให้ docker-compose ตรวจสอบและ restart container ได้ถูกต้อง - กำหนด user ที่ไม่ใช่ root — ลดความเสียหายถ้ามีผู้โจมตีสามารถสั่งคำสั่งใน container ได้