Developer Tools

Docker สำหรับนักพัฒนา: Containers, Compose, และ Workflow จริงในการทำงาน

คู่มือปฏิบัติเกี่ยวกับแนวคิด Docker, แนวทางที่ดีที่สุดสำหรับ Dockerfile, และการใช้ Docker Compose เพื่อรัน development environment แบบหลาย service

9 นาทีในการอ่าน

Server room with blue lighting

"มันทำงานได้บนเครื่องฉันนะ" คือประโยคที่จุดชนวนความล้มเหลวในการ deploy มานับพัน Docker แก้ปัญหานี้ด้วยการบรรจุแอปพลิเคชันและ dependency ทั้งหมดของคุณลงใน container — สภาพแวดล้อมที่แยกตัวและสร้างซ้ำได้ ซึ่งทำงานเหมือนกันทุกที่ ไม่ว่าจะเป็น laptop หรือ production

Containers vs. virtual machines

Virtual Machine Container
การแยกตัว Full OS ระดับ Process
เวลาเริ่มต้น หลายนาที หลายวินาที
ขนาด GBs MBs
Overhead สูง (hypervisor) แทบไม่มี
การใช้งาน แยก OS เต็มรูปแบบ บรรจุ App

Container แชร์ host kernel แต่แยก filesystem, process และ networking ออกจากกัน นั่นจึงเป็นเหตุผลที่มันเบามาก

แนวคิดหลักของ Docker

Image — พิมพ์เขียวแบบอ่านอย่างเดียวสำหรับ container ลองนึกภาพเหมือนการนิยาม class

Container — instance ที่กำลังรันอยู่ของ image ลองนึกภาพเหมือน object ที่สร้างจาก class

Registry — ระบบจัดเก็บและกระจาย image Docker Hub คือค่าเริ่มต้นแบบสาธารณะ ส่วน GitHub Container Registry และ AWS ECR เป็นทางเลือกที่นิยมใช้กัน

Volume — พื้นที่จัดเก็บข้อมูลถาวรที่ยังคงอยู่แม้ container จะ restart ข้อมูลที่เขียนภายใน container โดยไม่มี volume จะหายไปเมื่อ container หยุดทำงาน

การเขียน Dockerfile ที่ดี

# ใช้ version ที่ระบุชัดเจน — ห้ามใช้ "latest" ใน production
FROM node:20-alpine

# กำหนด working directory
WORKDIR /app

# Copy ไฟล์ dependency ก่อน (ใช้ประโยชน์จาก layer caching)
COPY package.json package-lock.json ./
RUN npm ci --only=production

# Copy โค้ดแอปพลิเคชัน
COPY . .

# Build แอป
RUN npm run build

# รันในฐานะ non-root user เพื่อความปลอดภัย
USER node

# ระบุ port (ไม่ได้ publish)
EXPOSE 3000

# ใช้ exec form เพื่อจัดการ signal อย่างถูกต้อง
CMD ["node", "server.js"]

Layer caching: กุญแจสู่การ build ที่รวดเร็ว

แต่ละคำสั่งใน Dockerfile จะสร้าง layer ขึ้นมา Docker จะ cache layer ไว้ — หากไม่มีการเปลี่ยนแปลงใน layer นั้นหรือ layer ก่อนหน้า Docker จะนำ cache กลับมาใช้

Copy ไฟล์ dependency ก่อนโค้ดแอปพลิเคชัน dependency เปลี่ยนแปลงน้อยกว่าโค้ดของคุณมาก หากคุณ copy ทุกอย่างพร้อมกัน การเปลี่ยนแปลงเพียงบรรทัดเดียวใน index.js จะทำให้ cache การติดตั้ง dependency ใช้ไม่ได้และบังคับให้รัน npm install ใหม่ทั้งหมด

Multi-stage builds

ใช้ multi-stage builds เพื่อให้ production image มีขนาดเล็กลง:

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production (ไม่มี dev dependencies, ไม่มี source)
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]

image สุดท้ายจะมีเฉพาะผลลัพธ์ที่ build แล้ว — ไม่มี source code, ไฟล์ test หรือ dev dependency

Docker Compose สำหรับการพัฒนาในเครื่อง

Docker Compose จัดการแอปพลิเคชันที่มีหลาย container ไฟล์ docker-compose.yml เพียงไฟล์เดียวกำหนด local stack ทั้งหมดของคุณ:

version: "3.9"

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

สร้าง scaffolding สำหรับ stack ของคุณด้วย Docker Compose Generator ของเรา — เลือก service ที่ต้องการและรับ docker-compose.yml ที่พร้อมใช้งานใน production ได้ในไม่กี่วินาที

คำสั่ง Compose ที่จำเป็น

# เริ่ม service ทั้งหมดในพื้นหลัง
docker compose up -d

# ดู log (แบบ follow mode)
docker compose logs -f app

# รันคำสั่งภายใน container ที่กำลังทำงาน
docker compose exec app sh

# หยุดและลบ container (เก็บ volume ไว้)
docker compose down

# หยุด ลบ container และ volume ด้วย
docker compose down -v

# Rebuild image หลังจากเปลี่ยน Dockerfile
docker compose up -d --build

Environment variables และ secrets

อย่า hardcode credentials ใน Dockerfile หรือไฟล์ Compose ใช้ไฟล์ .env แทน:

# .env (เพิ่มใน .gitignore!)
DATABASE_URL=postgresql://user:pass@db:5432/myapp
REDIS_URL=redis://redis:6379
JWT_SECRET=your-super-secret-key

Compose จะโหลด .env จาก project directory โดยอัตโนมัติ อ้างอิงตัวแปรด้วย ${VARIABLE_NAME} ใน YAML ของคุณ

ใช้ Env Generator ของเรา เพื่อสร้างไฟล์ .env พร้อม default ที่เหมาะสมและ template .env.example สำหรับทีมของคุณ

Networking ใน Docker

Compose จะสร้าง network เริ่มต้นสำหรับ stack ของคุณ service ต่างๆ สามารถเชื่อมต่อถึงกันโดยใช้ ชื่อ service เป็น hostname — นั่นคือเหตุผลที่แอปใช้ db เป็น database host แทนที่จะเป็น localhost

app → connects to "db:5432"
app → connects to "redis:6379"

เฉพาะ port ที่ map ไว้อย่างชัดเจนด้วย ports: เท่านั้นที่เข้าถึงได้จาก host machine

Health checks และลำดับการเริ่มต้น

depends_on จะรอเฉพาะให้ container เริ่มต้น เท่านั้น ไม่ได้รอให้ พร้อมใช้งาน container ของ database เริ่มต้นในไม่กี่วินาที แต่ Postgres อาจต้องการเวลาอีกสักครู่กว่าจะรับการเชื่อมต่อได้ ใช้ healthcheck + condition: service_healthy เพื่อรอจนกว่า service จะพร้อมใช้งานจริงๆ

ข้อควรพิจารณาสำหรับ Production

  1. ใช้ image tag ที่ระบุชัดเจนnode:20.11.1-alpine ไม่ใช่ node:latest
  2. สแกน image หาช่องโหว่docker scout cves myapp:latest
  3. กำหนด resource limits — ป้องกันไม่ให้ container ตัวเดียวดึงทรัพยากรจนตัวอื่นไม่พอใช้
  4. ใช้ read-only filesystem เท่าที่เป็นไปได้ — read_only: true ใน Compose
  5. อย่ารันในฐานะ root — เพิ่ม USER node หรือเทียบเท่า
  6. ตั้งค่า Nginx เป็น reverse proxy ไว้หน้าแอปของคุณ — ใช้ Nginx Config Generator ของเรา เพื่อรับ config ที่ผ่านการทดสอบมาแล้ว

Quick reference

# Images
docker images                    # แสดง image ในเครื่อง
docker pull nginx:alpine         # ดาวน์โหลด image
docker rmi myimage               # ลบ image

# Containers
docker ps                        # container ที่กำลังรัน
docker ps -a                     # container ทั้งหมด
docker stop <id>                 # หยุดอย่างสุภาพ
docker rm <id>                   # ลบ container ที่หยุดแล้ว
docker logs <id> -f              # ดู log แบบ stream

# Cleanup
docker system prune              # ลบทุกสิ่งที่ไม่ได้ใช้
docker volume prune              # ลบ volume ที่ไม่ได้ใช้

Docker ขจัดปัญหา bug ที่เกี่ยวกับ environment ทั้งหมด และทำให้การ onboard นักพัฒนาใหม่เป็นเรื่องง่ายมาก เมื่อคุณเชี่ยวชาญพื้นฐานที่นี่แล้ว คุณจะมีรากฐานสำหรับ Kubernetes, CI/CD pipelines และการ deploy บน cloud