Dockerfile Basics
เรียนรู้ว่า Dockerfile คืออะไร ทำหน้าที่อะไร และทำไมเราจึงต้องใช้มันในการสร้าง image สำหรับผู้ที่เพิ่งเริ่มต้นกับ Docker โดยไม่ต้องมีความรู้มาก่อน
ภาพจำสำคัญบทนี้
Dockerfile = สูตร → docker build = สร้าง image → docker run = เปิด container | แก้ code แล้วต้อง build ใหม่ก่อน | 1 image สร้างได้หลาย container
ส่วนที่ 1
Dockerfile คืออะไร
ลองนึกภาพว่าคุณต้องการอบเค้กสักชิ้น คุณคงไม่อยากจำทุกขั้นตอนไว้ในหัว แต่จะเขียนสูตรลงกระดาษแทน เพื่อให้ทำซ้ำได้ทุกครั้ง และส่งให้คนอื่นทำตามได้ด้วย Dockerfile (ด็อกเกอร์ไฟล์) คือ "สูตรอาหาร" ของ Docker — เป็นไฟล์ข้อความธรรมดาที่บอก Docker ทีละขั้นตอนว่า ให้สร้าง image (อิมเมจ) ขึ้นมาอย่างไร ชื่อไฟล์คือ Dockerfile เขียนติดกัน ไม่มีนามสกุล และต้องสะกดให้ถูกต้องแบบนี้เสมอ
- Dockerfile คือไฟล์ข้อความ (text file) ธรรมดา ที่อ่านได้และแก้ไขได้ง่าย
- แต่ละบรรทัดใน Dockerfile คือคำสั่ง (instruction) ที่บอก Docker ให้ทำอะไรสักอย่าง
- Dockerfile ไม่ใช่โปรแกรม ไม่ต้อง compile แค่บันทึกไฟล์แล้วสั่ง docker build ได้เลย
ส่วนที่ 2
ทำไมเราต้องใช้ Dockerfile
ก่อนมี Docker ถ้าอยากรันแอปบนเครื่องใหม่ เราต้องติดตั้งโปรแกรม ตั้งค่าสภาพแวดล้อม และแก้ปัญหา "ทำไมรันได้ในเครื่องฉัน แต่ไม่ได้ในเครื่องเธอ" ซ้ำแล้วซ้ำเล่า Dockerfile แก้ปัญหานี้ทั้งหมด เพราะมันบันทึกทุกอย่างที่แอปต้องการไว้ในที่เดียว
ประเด็นที่ 1
ทำซ้ำได้ทุกครั้ง (Reproducible) — build ครั้งไหนก็ได้ image เหมือนเดิมทุกครั้ง
ประเด็นที่ 2
แชร์ได้ง่าย (Shareable) — ส่ง Dockerfile ให้เพื่อนร่วมทีม เขา build ได้เลยโดยไม่ต้องอธิบาย
ประเด็นที่ 3
ทำงานอัตโนมัติได้ (Automatable) — ใช้ใน CI/CD pipeline สร้าง image อัตโนมัติทุกครั้งที่ push code
ประเด็นที่ 4
จัดการเวอร์ชันได้ (Versionable) — เก็บ Dockerfile ใน Git ได้เหมือนโค้ดทั่วไป ติดตามการเปลี่ยนแปลงได้
ส่วนที่ 3
Dockerfile เกี่ยวข้องกับ image และ container อย่างไร
สามสิ่งนี้เชื่อมกันเป็นลำดับขั้นตอน ให้จำไว้ว่า: Dockerfile คือ "สูตร" → image (อิมเมจ) คือ "แม่พิมพ์" → container (คอนเทนเนอร์) คือ "ของจริงที่รันได้" เหมือนกับการอบเค้ก: สูตรอาหาร → แม่พิมพ์เค้ก → เค้กที่กินได้จริง ๆ หลายชิ้น ความสัมพันธ์ที่สำคัญคือ: จาก Dockerfile หนึ่งไฟล์ เราสร้าง image ได้หนึ่งอัน แต่จาก image หนึ่งอัน เราสร้าง container ได้ไม่จำกัดจำนวน
| ชื่อ | คำอุปมา | สถานะ | สร้างโดย |
|---|---|---|---|
| Dockerfile | สูตรอาหาร | ไฟล์ข้อความ (ไม่รันได้) | เขียนด้วยมือ |
| Image (อิมเมจ) | แม่พิมพ์เค้ก | ไฟล์ที่บันทึกแล้ว (ไม่รันได้) | docker build |
| Container (คอนเทนเนอร์) | เค้กจริง ๆ | กระบวนการที่กำลังรันอยู่ | docker run |
ส่วนที่ 4
ภาพรวม flow: จาก source code ไปถึง container
การทำงานทั้งหมดมี 4 ขั้นตอนหลัก ที่ต่อกันเป็นลูกโซ่: ขั้นที่ 1 — เขียน Dockerfile: บอก Docker ว่าแอปของเราต้องการอะไร ใช้ base image อะไร และจะ setup อย่างไร ขั้นที่ 2 — สั่ง docker build: Docker อ่าน Dockerfile แล้วทำตามทีละขั้น ผลลัพธ์คือ image ขั้นที่ 3 — ได้ image: image คือสแนปช็อตของแอปพร้อม dependencies ทั้งหมด พร้อมแจกจ่ายหรือนำไปรัน ขั้นที่ 4 — สั่ง docker run: Docker สร้าง container จาก image แล้วเริ่มรันแอปของเราทันที
สองคำสั่งนี้คือหัวใจหลักของ Dockerfile workflow
ภาพที่ 1 — flow จาก source code ไปถึง container
ภาพที่ 1: เริ่มจาก source code + Dockerfile → สั่ง docker build เพื่อสร้าง image (อิมเมจ) → สั่ง docker run เพื่อเปิด container (คอนเทนเนอร์) ที่รันแอปจริง ๆ
ส่วนที่ 5
โครงสร้างพื้นฐานของ Dockerfile
Dockerfile ประกอบด้วยคำสั่ง (instructions) ทีละบรรทัด แต่ละคำสั่งเขียนเป็นตัวพิมพ์ใหญ่ตามด้วย argument คำสั่งที่ผู้เริ่มต้นต้องรู้จักมีอยู่ 6 คำสั่งหลัก:
- FROM — ระบุ base image (อิมเมจฐาน) ที่จะใช้เริ่มต้น เช่น node:20-alpine คือ Node.js บน Linux เบา ๆ
- WORKDIR — กำหนด working directory ภายใน container (เหมือนการ cd เข้าโฟลเดอร์)
- COPY — คัดลอกไฟล์จากเครื่องของเรา (host) เข้าไปใน image
- RUN — รันคำสั่ง shell ระหว่างขั้นตอน build เช่น ติดตั้ง packages หรือ compile code
- EXPOSE — บอก Docker ว่า container จะเปิดใช้งาน port อะไร (เพื่อเป็นเอกสาร)
- CMD — กำหนดคำสั่งเริ่มต้นที่จะรันเมื่อสั่ง docker run (เหมือน default startup command)
ส่วนที่ 6
ตัวอย่าง Dockerfile แบบง่าย (แอป Node.js)
ลองดูตัวอย่าง Dockerfile สำหรับแอป Node.js ง่าย ๆ ที่รัน HTTP server บน port 3000 สมมติว่า project ของเรามีไฟล์ 3 ไฟล์: package.json, package-lock.json, และ server.js
Dockerfile นี้ใช้คำสั่งพื้นฐาน 6 คำสั่ง เหมาะสำหรับแอปทั่วไป
ส่วนที่ 7
อธิบายทีละบรรทัด
มาดูรายละเอียดของแต่ละบรรทัดว่าทำอะไรและทำไมถึงเขียนแบบนี้
| คำสั่ง | หน้าที่ | ทำไมถึงเขียนแบบนี้ |
|---|---|---|
| FROM node:20-alpine | เลือก base image เป็น Node.js 20 บน Alpine Linux | Alpine Linux มีขนาดเล็กมาก (~5MB) ทำให้ image เบา และมี Node.js ติดมาให้แล้ว |
| WORKDIR /app | สร้างและเข้าสู่โฟลเดอร์ /app ใน container | ป้องกันไฟล์กระจัดกระจายที่ root ทำให้ไฟล์ทุกอย่างอยู่ที่เดิม |
| COPY package.json package-lock.json ./ | คัดลอกเฉพาะไฟล์ package config ก่อน | เพื่อให้ Docker cache layer ของ npm ci ได้ ถ้าไม่ได้แก้ dependencies จะไม่ต้องติดตั้งใหม่ |
| RUN npm ci | ติดตั้ง Node.js dependencies ทั้งหมด | npm ci ติดตั้งตาม lockfile ให้ผลเหมือนกันทุกครั้ง เหมาะกับ production |
| COPY . . | คัดลอก source code ทั้งหมดเข้า container | ทำหลัง npm ci เพื่อใช้ประโยชน์จาก layer cache ของ Docker |
| EXPOSE 3000 | ประกาศว่า container ใช้ port 3000 | เป็นเอกสารบอกคนอื่น และใช้กับ docker inspect หรือ compose ได้ |
| CMD ["node", "server.js"] | คำสั่งที่รันเมื่อ docker run | ใช้รูปแบบ array (exec form) เพื่อให้รับ signal ได้ถูกต้อง เช่น SIGTERM ตอน stop |
ส่วนที่ 8
คำศัพท์สำคัญที่ต้องรู้
คำศัพท์ 5 คำต่อไปนี้จะปรากฏซ้ำ ๆ ในทุกบทของ Docker ควรจำให้ขึ้นใจ
ประเด็นที่ 1
Image (อิมเมจ) — แม่แบบที่บรรจุ OS, runtime, dependencies, และ app ทุกอย่าง สร้างจาก Dockerfile ด้วย docker build อ่านอย่างเดียว ไม่แก้ไขได้โดยตรง
ประเด็นที่ 2
Container (คอนเทนเนอร์) — กระบวนการ (process) ที่รันจาก image ด้วย docker run คิดว่าเป็น instance ของ image ที่มีชีวิต จาก image เดียวสร้างได้หลาย container
ประเด็นที่ 3
Build (บิลด์) — กระบวนการที่ Docker อ่าน Dockerfile แล้วประกอบ image ขึ้นมา ใช้คำสั่ง docker build
ประเด็นที่ 4
Layer (เลเยอร์) — แต่ละคำสั่ง (RUN, COPY, FROM) ใน Dockerfile สร้าง layer หนึ่งชั้น Docker ซ้อน layer เหล่านี้เป็น image สุดท้าย ช่วยให้ cache และแชร์ชั้นที่ซ้ำกันได้
ประเด็นที่ 5
Base Image (เบสอิมเมจ) — image ตั้งต้นที่ระบุใน FROM เช่น node:20-alpine, python:3.11, ubuntu:22.04 คือจุดเริ่มต้นที่เราต่อยอดขึ้นไป
ภาพที่ 2 — ความสัมพันธ์ระหว่าง Dockerfile, image, และ container
ภาพที่ 2: จาก Dockerfile หนึ่งไฟล์ สร้างได้ image หนึ่งอัน แต่จาก image หนึ่งอัน สร้าง container ได้ไม่จำกัดจำนวน — รันบนเครื่องต่างกันก็ได้ผลเหมือนกัน
ส่วนที่ 9
ตัวอย่างสถานการณ์จริงที่ใช้ Dockerfile
Dockerfile ถูกใช้ในสถานการณ์จริงหลายรูปแบบ ดูตัวอย่างต่อไปนี้เพื่อเข้าใจว่ามันมีประโยชน์ในชีวิตจริงอย่างไร
ส่วนที่ 10
ข้อดีของการใช้ Dockerfile
สรุปข้อดีหลัก ๆ ที่ทำให้ทีม dev และ ops นิยมใช้ Dockerfile มากกว่าการติดตั้งแบบ manual
- ทำซ้ำได้ (Reproducibility) — build เมื่อไหร่ก็ได้ผลลัพธ์เหมือนกัน ไม่มี 'ทำงานในเครื่องฉันแต่ไม่ทำงานในเครื่องเธอ'
- เป็นเอกสารด้วยตัวเอง (Self-documenting) — Dockerfile คือเอกสารที่บอกทุกอย่างที่แอปต้องการ ชัดเจนกว่า README หลายหน้า
- เก็บ version ได้ (Version control) — เก็บใน Git เหมือนโค้ดทั่วไป ย้อนกลับหรือ diff การเปลี่ยนแปลงได้
- เร็วขึ้นด้วย cache (Layer caching) — Docker cache แต่ละ layer ไว้ ถ้าไม่มีอะไรเปลี่ยนจะไม่ build ซ้ำ ประหยัดเวลามาก
- ทำงานร่วมกับ CI/CD ได้ดี — ต่อเข้า pipeline อัตโนมัติ build, test, และ deploy image ได้โดยไม่ต้องมีคนทำเอง
ส่วนที่ 11
ข้อผิดพลาดที่ผู้เริ่มต้นมักเจอ
รายการด้านล่างนี้คือปัญหาที่มักพบเมื่อเริ่มเขียน Dockerfile ครั้งแรก พร้อมคำอธิบายและวิธีแก้
| ข้อผิดพลาด | สาเหตุ | วิธีแก้ |
|---|---|---|
| ลืม FROM เป็นบรรทัดแรก | ทุก Dockerfile ต้องเริ่มด้วย FROM เพื่อบอก base image | เพิ่ม FROM เป็นคำสั่งแรกเสมอ |
| COPY . . แล้วไฟล์ขาด | ไฟล์นั้นอาจถูก .dockerignore ยกเว้นไว้ หรือ path ผิด | ตรวจสอบ .dockerignore และใช้ path สัมพัทธ์จาก context |
| แก้ code แล้ว container ยังเหมือนเดิม | ลืม build image ใหม่หลังแก้ code | สั่ง docker build อีกครั้งก่อน docker run |
| RUN หลายคำสั่งแยกบรรทัด | แต่ละ RUN สร้าง layer ใหม่ ทำให้ image ใหญ่ขึ้นโดยไม่จำเป็น | รวมคำสั่งด้วย && เช่น RUN apt update && apt install -y curl |
| ใช้ CMD ผิดรูปแบบ | CMD node server.js (shell form) ทำให้รับ signal ไม่ได้ | ใช้ exec form เสมอ: CMD ["node", "server.js"] |
ส่วนที่ 12
สรุปท้ายบทแบบจำง่าย
บทนี้สอนพื้นฐานที่สำคัญที่สุดของ Docker workflow จำ 4 จุดนี้ไว้: 1. Dockerfile คือ "สูตร" บอก Docker ว่าจะสร้าง image อย่างไร 2. docker build อ่าน Dockerfile แล้วสร้าง image 3. docker run สร้าง container จาก image แล้วรันแอป 4. หนึ่ง image สร้างได้หลาย container ได้พร้อมกัน
- Dockerfile = ไฟล์ข้อความที่บอกวิธีสร้าง image ทีละขั้น
- FROM ต้องอยู่บรรทัดแรกเสมอ ระบุ base image ที่เริ่มต้น
- COPY คัดลอกไฟล์เข้า, RUN รันคำสั่ง, CMD บอกวิธีเปิดแอป
- docker build สร้าง image จาก Dockerfile
- docker run สร้าง container ที่รันได้จาก image
- แก้ code แล้วต้อง docker build ใหม่ก่อน ถึงจะเห็นการเปลี่ยนแปลง
แบบฝึกหัด
ทดสอบความเข้าใจ
ลองตอบคำถามต่อไปนี้ด้วยตัวเอง แล้วกดดูแนวเฉลยเพื่อเปรียบเทียบ