ทำไมต้องรู้จัก Docker
คนที่เขียนโค้ดมาสักพักน่าจะเคยเจอเหตุการณ์ทำนองนี้
โค้ดบนเครื่อง dev รันได้ปกติ แต่พอเอาไปลงเครื่องเพื่อนร่วมทีม รันแล้ว error เพราะ Node version ไม่ตรง พอจะ deploy ขึ้น server ก็เจออีก เพราะ server เป็น Ubuntu 20.04 แต่ที่ test คือ macOS
Docker เกิดมาเพื่อแก้ปัญหานี้ — มัน package ทั้งโค้ด ทั้ง dependency ทั้ง OS lib เข้าไปใน "container" เดียว เอาไปรันที่ไหนก็ได้ผลเหมือนกัน
Container กับ VM ต่างกันยังไง
หลายคนสับสนเรื่องนี้ ลองดูภาพเปรียบเทียบ
Virtual Machine (VM): จำลองทั้งคอม รวม OS เต็มๆ ใช้ disk เป็นหลาย GB, RAM เป็น GB, boot ใช้เวลาเป็นนาที
Container: ไม่จำลอง OS แต่ใช้ kernel ของ host แชร์ ตัว container เก็บแค่ app + dependency disk แค่ MB-100MB, RAM น้อยกว่ามาก, start ใช้เวลาวินาที
ภาพง่ายๆ:
┌─────────────────┐ ┌─────────────────┐
│ VM │ │ Container │
│ ┌───────────┐ │ │ ┌───────────┐ │
│ │ App │ │ │ │ App │ │
│ ├───────────┤ │ │ ├───────────┤ │
│ │ Libraries │ │ │ │ Libraries │ │
│ ├───────────┤ │ │ └───────────┘ │
│ │ Guest OS │ │ │ (ใช้ kernel ของ│
│ └───────────┘ │ │ host ร่วมกัน)│
│ Hypervisor │ │ Docker Engine │
│ Host OS │ │ Host OS │
└─────────────────┘ └─────────────────┘
ดังนั้นใน 1 server เราอาจรัน container ได้ 50 ตัว แต่รัน VM ได้แค่ 5 ตัว
คำว่า Image กับ Container
อีก 2 คำที่ต้องแยกให้ออก:
- Image — template ของ container เปรียบเหมือน "class" ใน OOP
- Container — instance ที่กำลังรันจาก image เปรียบเหมือน "object"
จาก 1 image คุณสามารถรันได้หลาย container
ติดตั้ง Docker
- macOS / Windows: ดาวน์โหลด Docker Desktop จาก docker.com
- Linux: ใช้ official script
curl -fsSL https://get.docker.com | sh
# ให้ user ปกติใช้ docker ได้โดยไม่ต้อง sudo
sudo usermod -aG docker $USER
# logout-login ใหม่
คำสั่งที่ใช้บ่อยสุด
# รัน container จาก image (ดึง image ลงให้อัตโนมัติ)
docker run hello-world
# ดู container ที่กำลังรัน
docker ps
# ดูทั้งที่หยุดด้วย
docker ps -a
# หยุด container
docker stop <container_id_or_name>
# ลบ container
docker rm <container_id_or_name>
# ดู image ทั้งหมด
docker images
# ลบ image
docker rmi <image_id>
# build image จาก Dockerfile ใน folder ปัจจุบัน
docker build -t myapp:1.0 .
# รัน container แบบ detached + map port
docker run -d -p 3000:3000 --name myapp myapp:1.0
# เข้าไปใน container ที่กำลังรัน (เหมือน SSH)
docker exec -it myapp sh
# ดู log
docker logs -f myapp
flag ที่ควรรู้:
-d= detached (รัน background)-p 3000:3000= map port host:container--name xxx= ตั้งชื่อ container-v ./data:/app/data= mount folder จาก host เข้า container-e NODE_ENV=production= set environment variable--rm= ลบ container ทิ้งหลังหยุด
เขียน Dockerfile แรก
สมมติมี Node.js app ในโฟลเดอร์ปัจจุบัน:
# ใช้ image base ที่มี Node 20 อยู่แล้ว
FROM node:20-alpine
# ตั้ง working directory ใน container
WORKDIR /app
# copy package files ก่อน เพื่อ cache dependency layer
COPY package*.json ./
RUN npm ci --omit=dev
# copy ไฟล์ที่เหลือ
COPY . .
# build (สำหรับ Next.js, TypeScript ฯลฯ)
RUN npm run build
# บอก docker ว่า app listen port อะไร (เป็น metadata เฉยๆ)
EXPOSE 3000
# คำสั่งเริ่มต้นเมื่อ container start
CMD ["npm", "start"]
build แล้วรัน:
docker build -t myapp .
docker run -p 3000:3000 myapp
เปิด http://localhost:3000 ได้เลย
เคล็ดลับ Dockerfile ให้ image เล็กและเร็ว
1. ใช้ alpine variant — node:20-alpine ขนาด ~50MB เทียบกับ node:20 ขนาด ~400MB
2. เรียง layer ให้ cache ได้ดี — copy package*.json แยกก่อน copy code ที่เหลือ เพราะ dependency เปลี่ยนน้อยกว่าโค้ด
3. Multi-stage build — แยก stage build กับ stage run ลด image final ได้เยอะ
# Stage 1: build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: runtime
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["npm", "start"]
วิธีนี้ stage runtime ไม่มี source code, devDependencies, build tool ใดๆ ขนาด final อาจลดได้ 60-70%
4. ใช้ .dockerignore — ไม่ให้ copy node_modules, .git, .env เข้า image
node_modules
.next
.git
.env*
*.md
.vscode
Docker Compose สำหรับหลาย service
ถ้า app ต้องใช้ database + cache ด้วย เขียน docker-compose.yml ครั้งเดียว start ทั้งหมดด้วยคำสั่งเดียว:
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:secret@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
# start ทั้ง 3 service
docker compose up -d
# ดู log รวมทุก service
docker compose logs -f
# หยุดทั้งหมด
docker compose down
# หยุด + ลบ volume (ข้อมูลใน db หาย)
docker compose down -v
ใน compose service สามารถเรียกหากันด้วยชื่อ service ได้เลย เช่น app ติดต่อ db ผ่าน hostname db
ข้อผิดพลาดที่เจอบ่อย
Container กิน disk ทั้งเครื่อง → image, container ที่ไม่ใช้สะสมไปเรื่อย ลบทิ้งบ้าง:
docker system prune -a --volumes
Build ช้ามาก → ตรวจสอบลำดับ COPY ใน Dockerfile, ใช้ multi-stage, ลองเปิด BuildKit (default แล้วใน Docker version ใหม่)
App รันแล้วเจอ "EACCES: permission denied" → user ใน container ไม่มีสิทธิเขียนใน volume ที่ mount มา แก้โดยเพิ่ม USER node ใน Dockerfile หรือ chown folder
ENV variable ไม่เข้า → จำไว้ว่า ENV ใน Dockerfile กับ -e ตอน run คนละเรื่อง production ควรใช้ -e หรือ env_file ใน compose มากกว่า
สรุป
Docker ลดเวลา onboarding คนเข้าทีมจาก 1 วัน เหลือ 10 นาที แค่ docker compose up ก็พร้อมเขียนโค้ด
ถ้าอยากเริ่มจริง — เปิด Dockerfile ของ project ตัวเองดูก่อน ลอง build, run, exec เข้าไปดู ของพวกนี้ต้อง hands-on เท่านั้นถึงจะเข้าใจจริง