พื้นฐาน Dockerfile
instruction หลักของ Dockerfile
หัวข้อที่มีชื่อว่า “instruction หลักของ Dockerfile”Dockerfile คือลำดับของ instruction Docker จะอ่านจากบนลงล่างและรันทีละบรรทัดเพื่อ build เป็น layer ใหม่ ต่อไปนี้คือ instruction หกตัวที่คุณจะได้ใช้ในแทบทุก Dockerfile
FROM — เลือก base ของคุณ
หัวข้อที่มีชื่อว่า “FROM — เลือก base ของคุณ”ทุก Dockerfile ต้องเริ่มต้นด้วย FROM ซึ่งกำหนดจุดเริ่มต้น — base image ที่ instruction ถัด ๆ ไปทั้งหมดจะ build ต่อยอดขึ้นไป
FROM node:22-alpinenode:22-alpine คือ image อย่างเป็นทางการของ Node.js ที่สร้างบน Alpine Linux (~50 MB) ให้เลือก base image ที่เฉพาะเจาะจงและเล็กที่สุดเท่าที่ตอบโจทย์ของคุณได้เสมอ
WORKDIR — กำหนด working directory
หัวข้อที่มีชื่อว่า “WORKDIR — กำหนด working directory”WORKDIR สร้างไดเรกทอรีภายใน image และตั้งให้เป็น working directory ปัจจุบันสำหรับ instruction ถัด ๆ ไปทั้งหมด การใช้ WORKDIR ช่วยให้ไม่ต้อง hard-code path กระจายไปทั่ว
WORKDIR /appถ้าไดเรกทอรียังไม่มีอยู่ Docker จะสร้างให้ instruction COPY, RUN, และ CMD ที่ตามมาทั้งหมดจะทำงานโดยอ้างอิงจาก /app
COPY — นำไฟล์เข้าสู่ image
หัวข้อที่มีชื่อว่า “COPY — นำไฟล์เข้าสู่ image”COPY คัดลอกไฟล์หรือไดเรกทอรีจาก build context (โฟลเดอร์โปรเจกต์ของคุณบนเครื่อง host) เข้าไปยัง filesystem ของ image
COPY package.json package-lock.json ./COPY src/ ./src/อาร์กิวเมนต์ตัวแรกคือต้นทาง (อ้างอิงจาก build context) ตัวที่สองคือปลายทางภายใน image (อ้างอิงจาก WORKDIR)
RUN — รันคำสั่งระหว่าง build
หัวข้อที่มีชื่อว่า “RUN — รันคำสั่งระหว่าง build”RUN รันคำสั่ง shell และ commit ผลลัพธ์เป็น layer ใหม่ ใช้ RUN เพื่อติดตั้ง dependencies, คอมไพล์โค้ด, หรือรันการ setup แบบครั้งเดียวจบ
RUN npm ci --omit=devควรรวมคำสั่งที่เกี่ยวข้องกันด้วย && เสมอเพื่อให้อยู่ใน layer เดียวกันและลดขนาด image:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*ENV — กำหนด environment variable
หัวข้อที่มีชื่อว่า “ENV — กำหนด environment variable”ENV กำหนด environment variable ที่ถูกฝังเข้าไปใน image และใช้ได้ทั้งตอน build time และ runtime
ENV NODE_ENV=productionENV PORT=3000EXPOSE — ระบุ port เชิงเอกสาร
หัวข้อที่มีชื่อว่า “EXPOSE — ระบุ port เชิงเอกสาร”EXPOSE เป็นเอกสารกำกับ — บอกผู้อ่านและเครื่องมือต่าง ๆ ว่า application รับฟังที่ port ไหน แต่ไม่ได้เปิด port ให้จริง ๆ คุณยังคงต้องใช้ -p ตอน docker run
EXPOSE 3000การ build image ของแอปจริง
หัวข้อที่มีชื่อว่า “การ build image ของแอปจริง”นี่คือ Dockerfile ที่สมบูรณ์สำหรับ HTTP server ของ Node.js แบบ minimal:
# syntax=docker/dockerfile:1FROM node:22-alpineWORKDIR /appCOPY package.json package-lock.json ./RUN npm ci --omit=devCOPY src/ ./src/ENV NODE_ENV=productionENV PORT=3000EXPOSE 3000CMD ["node", "src/server.js"]build image นี้:
docker build -t myapp:1.0 .ผลลัพธ์การ build ที่คาดหวัง (layer จะถูกใส่หมายเลข ส่วน layer ที่ถูก cache จะพิมพ์ CACHED):
[+] Building 12.3s (9/9) FINISHED => [internal] load build definition from Dockerfile 0.0s => [internal] load .dockerignore 0.0s => [1/6] FROM docker.io/library/node:22-alpine 4.1s => [2/6] WORKDIR /app 0.0s => [3/6] COPY package.json package-lock.json ./ 0.0s => [4/6] RUN npm ci --omit=dev 6.8s => [5/6] COPY src/ ./src/ 0.0s => [6/6] ENV NODE_ENV=production PORT=3000 0.0s => exporting to image 0.9s => => naming to docker.io/library/myapp:1.0รัน container:
docker run --rm -p 3000:3000 myapp:1.0ฝึกลงมือทำ
หัวข้อที่มีชื่อว่า “ฝึกลงมือทำ”snippet ด้านล่างประกอบด้วย Dockerfile ที่อยู่ในตัวเองครบ และ server.js แบบ inline เล็ก ๆ ที่เขียนด้วย echo ของ shell — ไม่ต้องใช้ไฟล์ภายนอกเลย วางลงใน Play with Docker แล้วรันได้เลย
# syntax=docker/dockerfile:1
FROM node:22-alpine
WORKDIR /app
# Inline a minimal server so no host files are needed
RUN echo 'const h=require("http");h.createServer((_,r)=>{r.writeHead(200);r.end("Hello from Docker!\n");}).listen(3000,()=>console.log("Listening on 3000"));' > server.js
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
# --- build & run ---
# docker build -t myapp .
# docker run --rm -p 3000:3000 myapp
# curl http://localhost:3000ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
รวมคำสั่งด้วย && ใน RUN เดียว | layer น้อยลง image เล็กลง | บรรทัดยาว อ่านยาก debug จุดที่ fail ลำบากขึ้น |
แยก RUN หลายบรรทัด | อ่านง่าย เห็นแต่ละ step ชัดเจน | layer เยอะขึ้น image ใหญ่ขึ้นโดยไม่จำเป็น |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เขียน
RUNแยกหลายบรรทัดแทนที่จะรวมด้วย&&ทำให้ layer และขนาด image บวมโดยไม่จำเป็น - ไม่ตั้ง
WORKDIRแล้ว hard-code full path ซ้ำ ๆ ในทุก instruction ทำให้แก้ path ทีต้องแก้หลายจุด - เข้าใจผิดว่า
EXPOSEเปิด port ให้จริง ทั้งที่เป็นแค่เอกสารกำกับ ยังต้องใช้-pตอนdocker runอยู่ดี
💡 ตัวอย่างจากของจริง
Docker Hub official image ของ
nodeจัดโครงสร้าง Dockerfile ตามแบบแผนเดียวกันแทบทุกตัว (FROM→WORKDIR→ คัดลอก manifest →RUNinstall → คัดลอก source →CMD) จนกลายเป็น pattern มาตรฐานที่ community อ้างอิงเวลาต้องเขียน Dockerfile เอง