ในโพสต์ Docker vs VMs เราเรียนรู้ว่า Docker สร้าง container ได้ง่าย แต่ app จริงมักใช้หลาย services — web server, API, database, cache, queue...
ถ้าต้อง docker run ทีละตัว ตั้ง network เอง ตั้ง volume เอง... จะบ้าตาย! 😵
Docker Compose แก้ปัญหานี้ — กำหนดทุก service ในไฟล์ YAML เดียว แล้วรันด้วยคำสั่งเดียว! 🐳🐕
Docker Compose คืออะไร?
Docker Compose คือเครื่องมือที่ช่วย กำหนด + รันหลาย containers พร้อมกัน ด้วยไฟล์ docker-compose.yml (หรือ compose.yml)
💡 จำง่ายๆ: Dockerfile = สูตรทำ 1 container | Docker Compose = สูตรทำทั้งร้านอาหาร (หลาย containers)
ตัวอย่างแรก — Web App + Database
มาเริ่มจากตัวอย่างง่ายที่สุด — web app + PostgreSQL database แค่ 2 services แต่ถ้าไม่มี Compose ต้อง: สร้าง network เอง, รัน postgres container ตั้ง password + volume, รัน app container ตั้ง env vars + เชื่อม network... รวม 5-6 คำสั่ง! Docker Compose ทำให้เหลือแค่ docker compose up:
# docker-compose.yml
services:
# Web Application
app:
build: . # build จาก Dockerfile ใน directory นี้
ports:
- "3000:3000" # host:container
environment:
- DATABASE_URL=postgres://myuser:mypass@db:5432/mydb
- NODE_ENV=development
volumes:
- .:/app # mount code เข้าไป (dev mode)
- /app/node_modules # ไม่ mount node_modules
depends_on:
db:
condition: service_healthy
restart: unless-stopped
# PostgreSQL Database
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypass
- POSTGRES_DB=mydb
volumes:
- pgdata:/var/lib/postgresql/data # persist data
ports:
- "5432:5432"
healthcheck:
test: pg_isready -U myuser
interval: 10s
retries: 5
volumes:
pgdata: # named volume สำหรับ DB data
# รันทุก service
docker compose up -d
# [+] Running 3/3
# ✔ Network myapp_default Created
# ✔ Container myapp-db-1 Started
# ✔ Container myapp-app-1 Started
# ดูสถานะ
docker compose ps
# NAME SERVICE STATUS PORTS
# myapp-app-1 app running 0.0.0.0:3000->3000/tcp
# myapp-db-1 db running 0.0.0.0:5432->5432/tcp
# ดู logs
docker compose logs -f app # follow logs ของ app
docker compose logs # logs ทุก service
# หยุดทุก service
docker compose down # หยุด + ลบ containers
docker compose down -v # + ลบ volumes ด้วย (⚠️ data หาย!)
Full Stack — React + API + DB + Redis + Nginx
ตัวอย่างจริงที่ใกล้ production — 5 services ทำงานร่วมกัน: React frontend, Node.js API, PostgreSQL database, Redis cache, และ Nginx reverse proxy สังเกตการใช้ depends_on กำหนดลำดับการ start, healthcheck ตรวจว่า service พร้อมจริงก่อน start ตัวที่พึ่ง, volumes สำหรับ persistent data, และ networks แยก frontend/backend traffic:
# docker-compose.yml — Full Stack
services:
# ─── Nginx Reverse Proxy ───
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- frontend
- backend
restart: always
# ─── React Frontend ───
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
environment:
- REACT_APP_API_URL=/api
expose:
- "3000" # expose ให้ nginx เห็น (ไม่เปิด host)
# ─── Node.js Backend ───
backend:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- DATABASE_URL=postgres://app:secret@db:5432/appdb
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET} # อ่านจาก .env file
expose:
- "8080"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
# ─── PostgreSQL ───
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=app
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=appdb
volumes:
- pgdata:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: pg_isready -U app
interval: 10s
retries: 5
restart: unless-stopped
# ─── Redis Cache ───
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
pgdata:
redisdata:
Nginx Config
Nginx ทำหน้าที่เป็น reverse proxy — รับ traffic ทั้งหมดแล้วส่งต่อไปยัง frontend หรือ API ตาม URL path ข้อดีคือ expose port เดียว (80) แทนที่ต้องเปิดหลาย ports และสามารถ cache, gzip, load balance ได้:
# nginx/default.conf
upstream frontend {
server frontend:3000;
}
upstream backend {
server backend:8080;
}
server {
listen 80;
# Frontend
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
}
# API → Backend
location /api/ {
proxy_pass http://backend/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket support
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
🔧 Docker Compose คำสั่งสำคัญ
คำสั่ง Docker Compose ที่ใช้บ่อยในชีวิตประจำวัน — ตั้งแต่ start/stop services, ดู logs, exec เข้าไปใน container, ไปจนถึง rebuild images หลังแก้ Dockerfile สังเกตว่า docker compose (ไม่มี hyphen) เป็น v2 ที่แนะนำ แทน docker-compose v1:
# ─── Lifecycle ───
docker compose up # รัน (foreground)
docker compose up -d # รัน (background) ⭐
docker compose down # หยุด + ลบ containers
docker compose down -v # + ลบ volumes
docker compose restart # restart ทุก service
docker compose stop # หยุด (ไม่ลบ)
docker compose start # เริ่มจาก stop
# ─── Build ───
docker compose build # build ทุก service
docker compose build --no-cache # build ใหม่ ไม่ใช้ cache
docker compose up --build # build แล้วรันเลย ⭐
# ─── Status & Logs ───
docker compose ps # ดูสถานะ
docker compose logs # ดู logs ทั้งหมด
docker compose logs -f backend # follow logs ของ backend
docker compose top # ดู processes ในแต่ละ container
# ─── Execute ───
docker compose exec backend sh # เข้าไปใน container ⭐
docker compose exec db psql -U app appdb # เข้า PostgreSQL
docker compose run --rm backend npm test # รันแล้วลบ container
# ─── Scale ───
docker compose up -d --scale backend=3 # รัน backend 3 ตัว!
📁 .env File — แยก Config ออกจาก YAML
.env file ช่วยแยก sensitive config (passwords, API keys) ออกจาก docker-compose.yml — ทำให้สามารถ commit compose file เข้า Git ได้โดยไม่ leak secrets เพราะ .env อยู่ใน .gitignore Docker Compose อ่าน .env อัตโนมัติ:
# .env (อยู่ข้างๆ docker-compose.yml)
POSTGRES_USER=app
POSTGRES_PASSWORD=supersecret
POSTGRES_DB=myapp
JWT_SECRET=my-jwt-secret-key
NODE_ENV=development
APP_PORT=3000
# docker-compose.yml — ใช้ตัวแปรจาก .env
services:
backend:
build: ./backend
ports:
- "${APP_PORT}:${APP_PORT}"
environment:
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
- JWT_SECRET=${JWT_SECRET}
- NODE_ENV=${NODE_ENV}
- PORT=${APP_PORT}
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
🐕 สำคัญ! อย่าลืมใส่.envใน.gitignore— ไม่ commit secrets เข้า Git!
🔀 Multi-Environment — Dev vs Production
Dev environment ต้องการ hot-reload, debug tools, volume mounts สำหรับ live coding ส่วน production ต้อง optimize image size, set resource limits, ปิด debug mode จะจัดการ config ต่างๆ ยังไงให้ไม่ต้อง maintain 2 ไฟล์ที่ซ้ำกัน? Docker Compose มี 2 วิธีหลัก:
วิธีที่ 1: Override Files
Override files คือการแยก base config (docker-compose.yml) กับ environment-specific config (docker-compose.override.yml หรือ docker-compose.prod.yml) Docker Compose จะ merge ทั้ง 2 ไฟล์เข้าด้วยกัน — base กำหนด services ทั้งหมด, override เพิ่ม/แก้เฉพาะค่าที่ต่างกัน:
# docker-compose.yml (base — shared config)
services:
backend:
build: ./backend
environment:
- DATABASE_URL=postgres://app:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:16-alpine
# docker-compose.override.yml (dev — auto-loaded!)
services:
backend:
volumes:
- ./backend:/app # hot reload
ports:
- "8080:8080"
- "9229:9229" # debug port
environment:
- NODE_ENV=development
command: npm run dev
# docker-compose.prod.yml (production)
services:
backend:
restart: always
environment:
- NODE_ENV=production
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
# Development (auto-loads override)
docker compose up
# Production (explicit file)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
วิธีที่ 2: Profiles
Profiles (Docker Compose v2) ช่วยให้ group services ตาม use case — เช่น debug tools, monitoring stack, seeding scripts ที่ไม่ต้องรันทุกครั้ง แค่ docker compose --profile debug up จะ start เฉพาะ services ที่ tag ด้วย profile "debug":
services:
backend:
build: ./backend
# ไม่มี profile = รันเสมอ
db:
image: postgres:16-alpine
# ไม่มี profile = รันเสมอ
# Dev tools — รันเฉพาะ dev
adminer:
image: adminer
ports:
- "8888:8080"
profiles:
- dev # รันเมื่อ --profile dev
mailhog:
image: mailhog/mailhog
ports:
- "8025:8025"
profiles:
- dev
# Monitoring — รันเฉพาะ monitoring
prometheus:
image: prom/prometheus
profiles:
- monitoring
# รันแค่ base services
docker compose up -d
# รัน + dev tools
docker compose --profile dev up -d
# รัน + monitoring
docker compose --profile monitoring up -d
# รันทั้งหมด
docker compose --profile dev --profile monitoring up -d
💾 Volumes — เก็บข้อมูลถาวร
โดย default เมื่อ container หยุด ข้อมูลข้างในหายหมด! Volumes แก้ปัญหานี้โดยเก็บข้อมูลไว้นอก container — database data, uploaded files, logs จะไม่หายแม้ docker compose down มี 2 แบบ: named volumes (Docker จัดการ path ให้ เหมาะกับ data) และ bind mounts (mount directory จาก host เข้า container เหมาะกับ development):
services:
db:
image: postgres:16-alpine
volumes:
# Named volume — Docker จัดการให้ (production)
- pgdata:/var/lib/postgresql/data
# Bind mount — map folder จาก host (dev)
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
backend:
build: ./backend
volumes:
# Bind mount — hot reload สำหรับ dev
- ./backend/src:/app/src
# Anonymous volume — ป้องกัน overwrite
- /app/node_modules
volumes:
pgdata: # ประกาศ named volume
driver: local
# ดู volumes
docker volume ls
# ลบ volumes ที่ไม่ใช้
docker volume prune
# Backup database
docker compose exec db pg_dump -U app mydb > backup.sql
# Restore database
cat backup.sql | docker compose exec -T db psql -U app mydb
🌐 Networks — Container คุยกันยังไง
Docker Compose สร้าง default network ให้ทุก service อัตโนมัติ — containers สื่อสารกันโดยใช้ service name เป็น hostname เช่น API container เรียก database ด้วย postgres://db:5432 ไม่ต้องใช้ IP address สำหรับ security ที่ดีขึ้น ควรสร้าง networks แยก — frontend network เฉพาะ web+nginx, backend network เฉพาะ API+DB ทำให้ frontend เข้าถึง DB โดยตรงไม่ได้:
services:
frontend:
networks:
- frontend-net # เข้าถึงได้จาก nginx
backend:
networks:
- frontend-net # nginx เข้าถึงได้
- backend-net # เข้าถึง db ได้
db:
networks:
- backend-net # เฉพาะ backend เข้าถึง ✅
# frontend เข้าไม่ได้ ✅ (security!)
networks:
frontend-net:
backend-net:
💡 Security Tip: แยก network เพื่อ จำกัดการเข้าถึง — frontend ไม่ควรเข้า DB ตรงได้!
❤️ Health Checks — ตรวจสุขภาพ
Health checks ตรวจว่า service ทำงานจริง ไม่ใช่แค่ "process ยังรันอยู่" — เช่น PostgreSQL container อาจ start แล้วแต่ยังรับ connection ไม่ได้ ถ้า API start ก่อน DB พร้อม จะ crash! depends_on + condition: service_healthy แก้ปัญหานี้โดย wait จน health check ผ่านก่อนค่อย start service ที่พึ่ง:
services:
backend:
build: ./backend
healthcheck:
test: curl -f http://localhost:8080/health || exit 1
interval: 30s # ตรวจทุก 30 วินาที
timeout: 10s # timeout 10 วินาที
retries: 3 # ลองใหม่ 3 ครั้ง
start_period: 40s # รอ 40 วินาทีก่อนเริ่มตรวจ
depends_on:
db:
condition: service_healthy # รอจน db healthy ก่อน!
db:
image: postgres:16-alpine
healthcheck:
test: pg_isready -U app
interval: 10s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: redis-cli ping
interval: 10s
retries: 3
🚀 Production Tips
Docker Compose ใช้ใน production ได้สำหรับ small-medium apps (ถ้ายังไม่ต้องการ Kubernetes) แต่ต้องตั้งค่าให้ production-ready — restart policies, resource limits, logging, security hardening เคล็ดลับสำคัญ:
# docker-compose.prod.yml
services:
backend:
image: myregistry.com/myapp:v2.1.0 # ใช้ image จาก registry (ไม่ build)
restart: always
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
logging:
driver: json-file
options:
max-size: "10m" # จำกัดขนาด log file
max-file: "3" # เก็บแค่ 3 ไฟล์
read_only: true # filesystem read-only (security)
tmpfs:
- /tmp # ให้เขียนได้แค่ /tmp
security_opt:
- no-new-privileges:true
Docker Compose vs Kubernetes
คำถามที่พบบ่อย — "ควรใช้ Docker Compose หรือ Kubernetes?" คำตอบง่ายๆ: Compose สำหรับ single-host (1 server, dev environment, small apps), Kubernetes สำหรับ multi-host (หลาย servers, auto-scaling, high availability, large teams) เริ่มด้วย Compose แล้วย้ายไป K8s เมื่อ scale จนจำเป็น:
| Feature | Docker Compose | Kubernetes |
|---|---|---|
| เหมาะกับ | Dev, small production | Large-scale production |
| เครื่อง | เครื่องเดียว | หลายเครื่อง (cluster) |
| Auto-scaling | ❌ Manual scale | ✅ HPA auto-scale |
| Self-healing | ⚠️ restart policy | ✅ Full self-healing |
| Rolling update | ⚠️ Basic | ✅ Zero-downtime |
| ความซับซ้อน | ง่าย (1 YAML file) | ซับซ้อน (many manifests) |
| Learning curve | 30 นาที | 30 วัน+ |
💡 กฎง่ายๆ: เครื่องเดียว + ทีมเล็ก = Docker Compose | หลายเครื่อง + scale = Kubernetes
สรุป
Docker Compose เป็นเครื่องมือที่ทุก developer ควรรู้ — ทำให้การ setup development environment เหลือแค่ docker compose up ไม่ต้อง install dependencies บนเครื่อง ทุกคนในทีมได้ environment เดียวกัน "works on my machine" หมดไป:
- Docker Compose = จัดการหลาย containers ด้วย
docker-compose.ymlไฟล์เดียว - คำสั่งหลัก =
up -d(รัน),down(หยุด),logs -f(ดู log),exec(เข้า container) - .env file = แยก secrets/config ออกจาก YAML (อย่า commit!)
- Multi-env = Override files หรือ Profiles แยก dev/prod
- Volumes = Named volumes สำหรับ data, Bind mounts สำหรับ dev
- Networks = แยก network เพื่อ security (frontend ≠ database)
- Health checks = ตรวจสุขภาพ +
depends_on condition: service_healthy - Production = resource limits, logging, read-only filesystem 🐳🐕