Docker compose containers deployment

ใช้ Docker Compose บน VPS จัดการ Multi-Container App

มิถุนายน 2026 · อ่าน 8 นาที

Docker Compose คืออะไร และทำไมต้องใช้บน VPS?

เมื่อ application ของเราเริ่มซับซ้อนขึ้น เช่น มี web server, database, และ cache อยู่ด้วยกัน การรัน docker run ทีละ container กลายเป็นเรื่องที่น่าปวดหัวมาก ต้องจำ flag ยาวๆ ต้องสร้าง network เอง ต้องระวังลำดับการ start — Docker Compose แก้ปัญหาเหล่านี้ทั้งหมดด้วยไฟล์ YAML ไฟล์เดียว

ในบทความนี้เราจะ deploy แอปพลิเคชันจริงที่ประกอบด้วย 3 service:

บทความนี้เหมาะสำหรับผู้ที่มีความรู้พื้นฐาน Docker แล้ว และมี VPS รัน Ubuntu 22.04 หรือ Debian 12 อยู่แล้ว

ขั้นตอนที่ 1 — ติดตั้ง Docker และ Docker Compose บน VPS

SSH เข้า VPS แล้วรัน script ติดตั้ง Docker แบบ official:

ssh root@your-vps-ip

# อัพเดท package list ก่อน
apt update && apt upgrade -y

# ติดตั้ง dependency ที่จำเป็น
apt install -y ca-certificates curl gnupg lsb-release

# เพิ่ม Docker GPG key
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

# เพิ่ม Docker repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" \
  | tee /etc/apt/sources.list.d/docker.list > /dev/null

# ติดตั้ง Docker Engine + Compose plugin
apt update
apt install -y docker-ce docker-ce-cli containerd.io \
               docker-buildx-plugin docker-compose-plugin

ตรวจสอบว่าติดตั้งสำเร็จ:

docker --version
# Docker version 26.x.x

docker compose version
# Docker Compose version v2.x.x
Docker Compose v2 มาในรูปแบบ plugin (docker compose) แทน standalone binary (docker-compose) แล้ว ใช้แบบใหม่ได้เลย

ขั้นตอนที่ 2 — สร้างโครงสร้างโปรเจกต์

สร้างโฟลเดอร์สำหรับโปรเจกต์และไฟล์ที่จำเป็น:

mkdir -p /opt/myapp/{nginx,app}
cd /opt/myapp

# โครงสร้างที่จะสร้าง:
# /opt/myapp/
# ├── docker-compose.yml
# ├── .env
# ├── nginx/
# │   └── default.conf
# └── app/
#     └── index.html  (ตัวอย่าง)

สร้าง Nginx config

cat > nginx/default.conf << 'EOF'
server {
    listen 80;
    server_name _;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }

    location /health {
        return 200 'OK';
        add_header Content-Type text/plain;
    }
}
EOF

สร้างไฟล์ environment variables

เก็บ secret ไว้ใน .env แยกต่างหาก ไม่ควร hardcode ลง docker-compose.yml โดยตรง:

cat > .env << 'EOF'
POSTGRES_DB=myapp
POSTGRES_USER=appuser
POSTGRES_PASSWORD=SuperSecret123!
REDIS_PASSWORD=RedisPass456!
APP_ENV=production
EOF

# จำกัดสิทธิ์ไฟล์ .env
chmod 600 .env
ห้าม commit ไฟล์ .env ลง Git เด็ดขาด! เพิ่ม .env ลงใน .gitignore ทันที

ขั้นตอนที่ 3 — เขียน docker-compose.yml

นี่คือหัวใจสำคัญของบทความนี้ — ไฟล์ docker-compose.yml ที่กำหนด service ทั้งหมดในที่เดียว:

cat > docker-compose.yml << 'EOF'
services:

  # ─── Web Server ──────────────────────────────────────
  nginx:
    image: nginx:1.25-alpine
    container_name: myapp_nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./app:/usr/share/nginx/html:ro
      - nginx_logs:/var/log/nginx
    depends_on:
      - postgres
      - redis
    networks:
      - frontend
      - backend

  # ─── Database ────────────────────────────────────────
  postgres:
    image: postgres:16-alpine
    container_name: myapp_postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ─── Cache ───────────────────────────────────────────
  redis:
    image: redis:7-alpine
    container_name: myapp_redis
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

# ─── Networks ──────────────────────────────────────────
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true   # ← network นี้ไม่มี internet access โดยตรง

# ─── Volumes ───────────────────────────────────────────
volumes:
  postgres_data:
    driver: local
  redis_data:
    driver: local
  nginx_logs:
    driver: local
EOF

สิ่งที่น่าสังเกตใน config นี้

ขั้นตอนที่ 4 — รัน Stack และตรวจสอบ

# รันทุก service ใน background (-d = detached mode)
docker compose up -d

# ดู log แบบ real-time (Ctrl+C เพื่อออก)
docker compose logs -f

# ดูเฉพาะ log ของ postgres
docker compose logs -f postgres

# ตรวจสอบสถานะทุก container
docker compose ps

ผลลัพธ์ที่ควรได้จาก docker compose ps:

NAME               IMAGE                COMMAND                  STATUS
myapp_nginx        nginx:1.25-alpine    "/docker-entrypoint.…"   Up (healthy)
myapp_postgres     postgres:16-alpine   "docker-entrypoint.s…"   Up (healthy)
myapp_redis        redis:7-alpine       "docker-entrypoint.s…"   Up (healthy)
ถ้า STATUS แสดง Up (health: starting) ให้รอสักครู่ ระบบกำลัง healthcheck อยู่ ถ้านาน เกิน 2 นาทีให้ดู log เพิ่มเติม

ขั้นตอนที่ 5 — คำสั่งที่ใช้บ่อยในการจัดการ

Lifecycle commands

# หยุดทุก service (เก็บ data ไว้)
docker compose stop

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

# หยุดและลบทุกอย่างรวม volume (ระวัง! ข้อมูลหาย)
docker compose down -v

# Restart service เดียว
docker compose restart nginx

# อ่าน config ใหม่หลังแก้ไข docker-compose.yml
docker compose up -d --force-recreate nginx

Debug และ exec เข้า container

# เข้า shell ของ postgres container
docker compose exec postgres sh

# รัน psql ตรงๆ
docker compose exec postgres psql -U appuser -d myapp

# ทดสอบ Redis
docker compose exec redis redis-cli -a RedisPass456! ping
# ได้ PONG = ปกติ

# ดู resource usage แบบ real-time
docker stats myapp_nginx myapp_postgres myapp_redis

Update image เป็นเวอร์ชันใหม่

# ดึง image ใหม่ทุก service
docker compose pull

# Recreate container ที่มี image ใหม่
docker compose up -d

# ลบ image เก่าที่ไม่ใช้แล้ว
docker image prune -f

ขั้นตอนที่ 6 — Backup ข้อมูล PostgreSQL

ข้อมูลใน volume ปลอดภัยจากการ recreate container แต่ควร backup ไว้ด้วย:

# Dump database ออกมาเป็นไฟล์ .sql
docker compose exec -T postgres \
  pg_dump -U appuser myapp \
  > /opt/backups/myapp_$(date +%Y%m%d_%H%M%S).sql

# Restore จาก backup
docker compose exec -T postgres \
  psql -U appuser myapp \
  < /opt/backups/myapp_20260603_120000.sql

ตั้ง cron backup รายวัน

# แก้ crontab
crontab -e

# เพิ่มบรรทัดนี้ — backup ทุกวันตี 2
0 2 * * * docker compose -f /opt/myapp/docker-compose.yml exec -T postgres \
  pg_dump -U appuser myapp \
  > /opt/backups/myapp_$(date +\%Y\%m\%d).sql 2>&1

ขั้นตอนที่ 7 — เพิ่ม SSL ด้วย Certbot (HTTPS)

สำหรับ production ต้องมี HTTPS ใช้ Certbot แบบ standalone ได้เลย:

# หยุด Nginx ชั่วคราวเพื่อให้ Certbot ใช้ port 80
docker compose stop nginx

# ติดตั้ง Certbot
apt install -y certbot

# ขอ certificate
certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com

# certificate จะอยู่ที่:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pem

จากนั้นแก้ nginx/default.conf ให้รองรับ HTTPS และ mount certificate เข้า container:

# เพิ่มใน volumes ของ nginx service ใน docker-compose.yml
volumes:
  - /etc/letsencrypt:/etc/letsencrypt:ro
  - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
อย่าลืม renew certificate อัตโนมัติ: certbot renew --pre-hook "docker compose -f /opt/myapp/docker-compose.yml stop nginx" --post-hook "docker compose -f /opt/myapp/docker-compose.yml start nginx"

Checklist ก่อน Go Live

  1. ตรวจสอบว่าทุก container มีสถานะ healthy ใน docker compose ps
  2. ไฟล์ .env มี permission 600 และไม่อยู่ใน Git
  3. Backend network ตั้ง internal: true เพื่อป้องกัน database expose
  4. ตั้ง restart: unless-stopped ทุก service สำคัญ
  5. มี backup strategy สำหรับ database volumes
  6. ตั้ง maxmemory ให้ Redis เพื่อป้องกัน RAM เต็ม
  7. ทดสอบ docker compose down && docker compose up -d ว่าข้อมูลยังอยู่ครบ

สรุป

Docker Compose เปลี่ยนการ manage multi-container application จากคำสั่งยาวๆ หลายบรรทัดให้เป็นไฟล์ YAML ไฟล์เดียวที่อ่านง่าย แก้ง่าย และ version control ได้ สิ่งที่สำคัญที่สุดจากบทความนี้:

เมื่อโปรเจกต์โตขึ้นและต้องการ scale หลาย VPS Docker Swarm หรือ Kubernetes คือขั้นตอนต่อไป แต่สำหรับ application ขนาดกลางที่รันบน VPS เครื่องเดียว Docker Compose ตอบโจทย์ได้ครบถ้วนแล้ว

ตรวจสอบว่า Docker Compose Stack ทำงานถูกต้อง

# ดูสถานะทุก container
docker compose ps

# ผลที่ถูกต้อง: STATUS ทุกตัวเป็น "Up (healthy)"
# NAME               STATUS
# myapp_nginx        Up (healthy)
# myapp_postgres     Up (healthy)
# myapp_redis        Up (healthy)

# ทดสอบ healthcheck ของ postgres
docker compose exec postgres pg_isready -U appuser -d myapp

# ทดสอบ Redis
docker compose exec redis redis-cli -a RedisPass456! ping
# ควรได้ PONG

# ตรวจสอบ resource usage ของแต่ละ container
docker stats --no-stream myapp_nginx myapp_postgres myapp_redis
Tip: ถ้า STATUS แสดง Up (health: starting) ให้รอ 10-30 วินาที healthcheck กำลังทำงาน ถ้าเปลี่ยนเป็น (unhealthy) ให้รัน docker compose logs postgres ดู error

แก้ปัญหาที่พบบ่อย

Container exit ทันทีหลัง start — restart loop

Application ใน container crash ตรวจสอบ log เพื่อหาสาเหตุ

# ดู log ของ container ที่มีปัญหา
docker compose logs postgres

# ดู exit code (Non-zero = error)
docker compose ps -a

# รัน container แบบ interactive เพื่อ debug
docker compose run --rm postgres sh
# แล้วรัน command ที่ต้องการดู error โดยตรง

Error: "port is already allocated" หรือ "bind: address already in use"

Port 80/443/5432 มีโปรแกรมอื่นใช้งานอยู่แล้วในระดับ host

# ดูว่าอะไรใช้ port 80 อยู่
sudo ss -tlnp | grep :80
# หรือ
sudo lsof -i :80

# ถ้าเป็น Nginx ระดับ host
sudo systemctl stop nginx

# หรือเปลี่ยน port mapping ใน docker-compose.yml
ports:
  - "8080:80"   # เปลี่ยนจาก "80:80"

ข้อมูล Database หายหลัง docker compose down

ใช้ docker compose down -v ซึ่งลบ volume ไปด้วย ถ้าต้องการเก็บข้อมูลให้ใช้แค่ docker compose down

# ตรวจสอบว่า volume ยังมีอยู่
docker volume ls | grep myapp

# ถ้า volume หายไปแล้ว ต้อง restore จาก backup
docker compose up -d postgres
docker compose exec -T postgres psql -U appuser myapp < backup.sql

# กฎ: docker compose down = ลบ container เก็บ volume ✅
#     docker compose down -v = ลบทั้ง container และ volume ⚠️

ไฟล์ .env ไม่ถูกโหลด — ตัวแปรเป็นค่าว่าง

Docker Compose อ่าน .env เฉพาะเมื่ออยู่ใน directory เดียวกับ docker-compose.yml

# ตรวจสอบว่า .env อยู่ถูกที่
ls -la /opt/myapp/.env

# ทดสอบว่า Compose อ่าน .env ได้
docker compose config | grep POSTGRES

# ถ้าต้องการระบุ .env file อื่น
docker compose --env-file /path/to/.env up -d

Healthcheck ล้มเหลวตลอด — container stuck ใน "unhealthy"

Command ใน healthcheck อาจผิดหรือ timeout สั้นเกินไปสำหรับ startup

# ดูผล healthcheck ล่าสุด
docker inspect myapp_postgres | python3 -m json.tool | grep -A10 Health

# เพิ่ม start_period เพื่อให้ container ได้เวลา initialize ก่อน healthcheck
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U appuser"]
  interval: 10s
  timeout: 5s
  retries: 5
  start_period: 30s   # รอ 30 วินาทีก่อนเริ่ม healthcheck

คำถามที่พบบ่อย

docker-compose.yml กับ Dockerfile ต่างกันอย่างไร?

Dockerfile คือ recipe สำหรับ build image ของ container เดียว กำหนดว่า base image คืออะไร ต้องติดตั้ง package อะไร copy ไฟล์อะไรเข้าไป docker-compose.yml คือ orchestration file ที่กำหนดว่าจะรัน container อะไรบ้าง ใช้ image ไหน map port และ volume อย่างไร และ container ไหน depends on กัน ทั้งสองใช้ร่วมกันได้: Compose ใช้ Dockerfile build image ของ app แล้ว pull image สำเร็จรูป (nginx/postgres/redis) มาใช้โดยตรง

depends_on กับ healthcheck ต่างกันอย่างไร?

depends_on แบบ default รอแค่ให้ container start แต่ไม่รอให้ service พร้อมใช้งาน เช่น Nginx start ก่อน Postgres เริ่มรับ connection ได้ ถ้าต้องการรอให้ service พร้อมจริงๆ ต้องใช้ depends_on ร่วมกับ condition: service_healthy ซึ่งต้องมี healthcheck ในนั้น service ด้วย

เมื่อไหรควรเปลี่ยนจาก Docker Compose ไป Kubernetes?

Docker Compose เหมาะสำหรับ single-server deployment ถ้าต้องการ scale ข้าม VPS หลายเครื่อง, auto-scaling, rolling update แบบ zero-downtime, หรือ self-healing ที่ซับซ้อน ให้พิจารณา Kubernetes หรือ Docker Swarm แต่สำหรับ startup หรือ project ที่ยังไม่ถึง 10,000 requests/วัน Compose เพียงพอแล้วและง่ายกว่ามาก