ข้ามไปยังเนื้อหา

พื้นฐาน Dockerfile

Dockerfile คือลำดับของ instruction Docker จะอ่านจากบนลงล่างและรันทีละบรรทัดเพื่อ build เป็น layer ใหม่ ต่อไปนี้คือ instruction หกตัวที่คุณจะได้ใช้ในแทบทุก Dockerfile

ทุก Dockerfile ต้องเริ่มต้นด้วย FROM ซึ่งกำหนดจุดเริ่มต้น — base image ที่ instruction ถัด ๆ ไปทั้งหมดจะ build ต่อยอดขึ้นไป

FROM node:22-alpine

node:22-alpine คือ image อย่างเป็นทางการของ Node.js ที่สร้างบน Alpine Linux (~50 MB) ให้เลือก base image ที่เฉพาะเจาะจงและเล็กที่สุดเท่าที่ตอบโจทย์ของคุณได้เสมอ

WORKDIR สร้างไดเรกทอรีภายใน image และตั้งให้เป็น working directory ปัจจุบันสำหรับ instruction ถัด ๆ ไปทั้งหมด การใช้ WORKDIR ช่วยให้ไม่ต้อง hard-code path กระจายไปทั่ว

WORKDIR /app

ถ้าไดเรกทอรียังไม่มีอยู่ Docker จะสร้างให้ instruction COPY, RUN, และ CMD ที่ตามมาทั้งหมดจะทำงานโดยอ้างอิงจาก /app

COPY คัดลอกไฟล์หรือไดเรกทอรีจาก build context (โฟลเดอร์โปรเจกต์ของคุณบนเครื่อง host) เข้าไปยัง filesystem ของ image

COPY package.json package-lock.json ./
COPY src/ ./src/

อาร์กิวเมนต์ตัวแรกคือต้นทาง (อ้างอิงจาก build context) ตัวที่สองคือปลายทางภายใน image (อ้างอิงจาก WORKDIR)

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 ที่ถูกฝังเข้าไปใน image และใช้ได้ทั้งตอน build time และ runtime

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE เป็นเอกสารกำกับ — บอกผู้อ่านและเครื่องมือต่าง ๆ ว่า application รับฟังที่ port ไหน แต่ไม่ได้เปิด port ให้จริง ๆ คุณยังคงต้องใช้ -p ตอน docker run

EXPOSE 3000

นี่คือ Dockerfile ที่สมบูรณ์สำหรับ HTTP server ของ Node.js แบบ minimal:

# syntax=docker/dockerfile:1
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src/ ./src/
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["node", "src/server.js"]

build image นี้:

Terminal window
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:

Terminal window
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
ตัวเลือกBenefitCost
รวมคำสั่งด้วย && ใน 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 ตามแบบแผนเดียวกันแทบทุกตัว (FROMWORKDIR → คัดลอก manifest → RUN install → คัดลอก source → CMD) จนกลายเป็น pattern มาตรฐานที่ community อ้างอิงเวลาต้องเขียน Dockerfile เอง

instruction ใดกำหนด working directory สำหรับ instruction ถัด ๆ ไปทั้งหมดใน Dockerfile?
จริง ๆ แล้ว EXPOSE ทำอะไร?
ทำไมจึงควรรวมหลายคำสั่ง shell ไว้ใน instruction RUN เดียวโดยใช้ &&?
instruction ใดคัดลอกไฟล์จาก build context ของ host เข้าไปใน image?