วิธีดีพลอยแอปพลิเคชันด้วย Docker Compose บน VPS
คู่มือโปรดักชันที่ใช้ได้จริงสำหรับ Docker Compose v2 บน Ubuntu: ติดตั้งเอนจิน เขียนไฟล์ Compose พร้อมเครือข่ายและวอลุม จัดการความลับ วางรีเวิร์สพร็อกซีด้านหน้า อัปเดตโดยไม่ตื่นตระหนก และสำรองวอลุมที่มีชื่อ

Docker Compose คือวิธีปกติที่จะรันสแตกเล็กบน VPS เครื่องเดียว: เว็บแอป ฐานข้อมูล แคช Redis และรีเวิร์สพร็อกซี บรรยายในไฟล์ YAML เดียว มันไม่ใช่ Kubernetes คุณจะไม่ได้ failover หลายโหนด คุณจะได้การตั้งค่าที่ทำซ้ำได้ ตรวจทานได้ สร้างใหม่บน VPS Hiddence ใหม่ได้ในไม่กี่นาที — ซึ่งคือสิ่งที่โปรเจกต์ข้างและผลิตภัณฑ์เล็กส่วนใหญ่ต้องการ
บทความนี้สมมติว่าคุณต้องการสิ่งที่ใกล้โปรดักชันกว่า docker run คืนวันศุกร์ เราจะติดตั้ง Docker Engine กับปลั๊กอิน Compose สร้างผู้ใช้ดีพลอยที่ไม่ใช่ root เขียนไฟล์ Compose พร้อม healthcheck และนโยบายรีสตาร์ต เก็บความลับไว้นอก Git เผยแพร่เฉพาะพอร์ตรีเวิร์สพร็อกซีสู่อินเทอร์เน็ต และกำหนดกิจวัตรอัปเดตกับสำรอง สแตกตัวอย่างคือเว็บแอปทั่วไปบวก PostgreSQL บวก Caddy แต่แพทเทิร์นเดียวกันใช้กับ Node, PHP, Python หรือกอง worker ได้
ทำไม Compose บน VPS เครื่องเดียวยังสมเหตุสมผล
คนกระโดดไป Kubernetes เพราะเป็นแฟชั่น แล้วใช้สุดสัปดาห์กับ YAML ของเว็บไซต์เดียว Compose ยังอ่านได้ คุณทำเวอร์ชันไฟล์ บันทึกตัวแปรสภาพแวดล้อม และ diff การเปลี่ยนแปลงก่อนใช้ การแยกตัวดีพอ: คอนเทนเนอร์แอปที่ถูกเจาะไม่ควรถือซ็อกเก็ต UNIX ของฐานข้อมูลถ้าคุณใช้เครือข่ายกับผู้ใช้สิทธิ์น้อยที่สุด ขีดจำกัดทรัพยากรหยุดการรั่วหน่วยความจำครั้งเดียวไม่ให้แช่แข็งทั้ง VPS
- ไฟล์เดียวบรรยายทั้งสแตก
- วอลุมที่มีชื่อรอดเมื่อสร้างคอนเทนเนอร์ใหม่
- เครือข่าย Docker ภายในดึง Postgres ออกจากอินเทอร์เน็ตสาธารณะ
- restart: unless-stopped ครอบคลุมการรีบูตส่วนใหญ่
- คัดลอกไป VPS ที่สองง่ายเมื่อเครื่องแรกแคบ
ความต้องการ
ใช้ VPS ที่มี RAM พอสำหรับแอปบวก Postgres แผน 2 GB คือขั้นต่ำที่เป็นจริงสำหรับแอป + ฐานข้อมูล + พร็อกซี สมมติ Ubuntu 24.04 อย่าติดตั้ง Docker จาก Snap สุ่มโดยไม่ได้อ่านว่ามันทำอะไรกับ cgroups ขั้นตอนด้านล่างใช้ที่เก็บ apt ทางการของ Docker
- Ubuntu 22.04/24.04, root หรือ sudo
- แนะนำ RAM 2 GB (1 GB เฉพาะสแตกจิ๋วไม่มี Postgres)
- โดเมนชี้ไป VPS ถ้าต้องการ HTTPS อัตโนมัติ
- Git หรือวิธีอื่นในการคัดลอกโปรเจกต์ขึ้นเซิร์ฟเวอร์
ขั้นตอนที่ 1: ติดตั้ง Docker Engine และ Compose v2
Compose v2 เป็นปลั๊กอินที่เรียกด้วย docker compose (มีช่องว่าง) ไม่ใช่ไบนารี Python docker-compose เก่า ติดตั้งทั้งคู่จากที่เก็บของ Docker เพื่อได้แพตช์ความปลอดภัยปัจจุบัน
ssh root@YOUR_VPS_IP
apt update && apt -y install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker version
docker compose versionขั้นตอนที่ 2: ผู้ใช้ดีพลอยและโครงไดเรกทอรี
รัน Compose เป็น root ได้ แต่ผู้ใช้เฉพาะที่เป็นสมาชิกกลุ่ม docker สะอาดกว่า วางโปรเจกต์ใน /opt หรือ /srv ไม่ใช่ /root เพื่อให้สำรองและสิทธิ์ชัดเจน อย่าให้ไดเรกทอรีที่มี .env เขียนได้ทั้งโลก
adduser --disabled-password --gecos '' deploy
usermod -aG docker deploy
mkdir -p /srv/app
chown deploy:deploy /srv/app
chmod 750 /srv/app
# Log in as deploy for the rest of the file editing:
# su - deploy
# cd /srv/appขั้นตอนที่ 3: เขียนไฟล์ Compose ที่คิดถึงโปรดักชัน
ไฟล์ด้านล่างเป็นเทมเพลต แทนที่อิมเมจแอปด้วยของคุณ Postgres ไม่ถูกเผยแพร่ไป 0.0.0.0:5432 — มีแค่ Caddy เผยแพร่บน 80 และ 443 Healthcheck หยุดรีเวิร์สพร็อกซีไม่ให้ส่งทราฟฟิกไปแอปที่ยังย้ายฐานข้อมูล ปักแท็กอิมเมจ latest คือวิธีที่พังโดยไม่คาดคิดเกิดขึ้น
# /srv/app/compose.yaml
services:
caddy:
image: caddy:2.8-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
app:
condition: service_healthy
app:
image: ghcr.io/example/webapp:1.4.2
restart: unless-stopped
env_file: .env
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"]
interval: 10s
timeout: 3s
retries: 10
networks:
- frontend
- backend
db:
image: postgres:16-alpine
restart: unless-stopped
env_file: .env
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- backend
networks:
frontend: {}
backend: {}
volumes:
pgdata:
caddy_data:
caddy_config:ขั้นตอนที่ 4: ความลับ, .env และ Caddyfile
ใส่รหัสผ่านใน .env บนเซิร์ฟเวอร์ chmod 600 และอย่าคอมมิตไฟล์นั้น สร้างรหัสผ่านด้วย openssl rand -base64 32 Caddyfile ต้องแค่รีเวิร์สพร็อกซีไปชื่อบริการแอปบนเครือข่าย Docker
# /srv/app/.env (permissions 600)
POSTGRES_USER=app
POSTGRES_PASSWORD=change-me-long-random
POSTGRES_DB=app
# plus any APP_SECRET / NEXTAUTH_SECRET your image needs
# /srv/app/Caddyfile
app.example.com {
encode gzip
reverse_proxy app:8000
}
chmod 600 /srv/app/.env
cd /srv/app
docker compose up -d
docker compose ps
docker compose logs -f --tail=100ขั้นตอนที่ 5: ไฟร์วอลล์ ล็อก และขีดจำกัดทรัพยากร
UFW ควรอนุญาตแค่ 22, 80 และ 443 Docker บางครั้งเลี่ยง UFW สำหรับพอร์ตที่เผยแพร่ ถ้าต้องการไฟร์วอลล์โฮสต์เข้ม ให้อ่านปฏิสัมพันธ์ Docker + UFW ปัจจุบันของเวอร์ชัน Ubuntu คุณ หรือเผยแพร่พอร์ตเฉพาะบน 127.0.0.1 แล้ววาง Caddy บนโฮสต์ด้านหน้า ตั้งขีดจำกัดหน่วยความจำใน Compose เพื่อไม่ให้ Postgres กิน VPS
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
# Optional in compose.yaml under app or db:
# deploy:
# resources:
# limits:
# memory: 512M
# Logs (do not let json-file grow forever):
# { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
# in /etc/docker/daemon.json then: systemctl restart dockerขั้นตอนที่ 6: อัปเดตและย้อนกลับ
การอัปเดตน่าเบื่อที่ใช้ได้คือ ดึงอิมเมจใหม่ up -d เฝ้า healthcheck เก็บแท็กก่อนหน้าใน git เพื่อปักกลับได้ อย่า docker compose down บนฐานข้อมูลโปรดักชันเว้นแต่ตั้งใจหยุดรับทราฟฟิก down -v ลบวอลุม — นั่นคือเช็ด ไม่ใช่รีสตาร์ต
cd /srv/app
git pull # if the Compose file lives in git
# Change the image tag in compose.yaml, then:
docker compose pull
docker compose up -d
docker compose ps
# Rollback: set the old tag, pull, up -d again
# NEVER: docker compose down -v # this deletes named volumesสำรองวอลุมที่มีชื่อ
สแนปช็อต VPS ดี Dump เชิงตรรกะของ Postgres ดีกว่าถ้าต้องกู้ไปอีกเครื่อง จัดตารางด้วย cron ทดลองกู้ครั้งหนึ่ง ไม่งั้นคุณไม่มีสำรอง — คุณมีไฟล์ที่หวังว่าเป็นสำรอง
# Postgres dump while the db container is running:
docker compose exec -T db pg_dump -U app app | gzip > /var/backups/app-$(date +%F).sql.gz
# Copy off-box
# scp /var/backups/app-*.sql.gz backup-host:~
# Restore sketch (maintenance window):
# gunzip -c app-2026-08-19.sql.gz | docker compose exec -T db psql -U app appแก้ปัญหา
อ่าน docker compose ps และ docker compose logs service การดึงอิมเมจล้มเหลวมักเป็นขีดจำกัดอัตรา ghcr/Docker Hub หรืออิมเมจส่วนตัวที่ไม่ได้ล็อกอิน 502 จาก Caddy หมายความแอปไม่สุขภาพดีหรือชื่อโฮสต์พร็อกซีผิด (ใช้ชื่อบริการ Compose ไม่ใช่ localhost จากในคอนเทนเนอร์ Caddy) ข้อผิดพลาดสิทธิ์บนวอลุมมักหมายความอิมเมจรันเป็น uid 1000 ในขณะที่ไดเรกทอรีโฮสต์เป็น root
- compose: command not found — คุณติดตั้งชื่อไบนารีเก่า ใช้ docker compose
- port is already allocated — สิ่งอื่นครอง 80/443 (Apache, Nginx, Caddy อีกตัว)
- database connection refused — แอปเริ่มก่อน Postgres พร้อม ใช้ healthcheck + depends_on condition
- ดิสก์เต็ม — docker system df แล้ว prune อิมเมจที่ไม่ใช้ด้วยความระวัง
- permission denied บน docker.sock — ผู้ใช้ไม่อยู่ในกลุ่ม docker หรือต้องล็อกอินเซสชันใหม่
ความปลอดภัย
อย่าเผยแพร่พอร์ตฐานข้อมูล อย่ารัน privileged: true อย่าเมานต์ /var/run/docker.sock เข้าคอนเทนเนอร์แอปเว้นแต่คุณตั้งใจเขียนตัวจัดการคอนเทนเนอร์ อัปเดตเอนจิน สแกนอิมเมจของคุณถ้าคุณสร้างเอง .env บนดิสก์ยังเป็นไฟล์ความลับ — จำกัดว่าใคร SSH เข้ากล่องได้
- ไม่มี 5432 / 3306 / 6379 สาธารณะ
- ปักไดเจสต์อิมเมจ หรืออย่างน้อยแท็กที่ไม่เปลี่ยน
- chmod 600 .env
- Unattended-upgrades สำหรับ OS โฮสต์ยังสำคัญ
- หนึ่งโปรเจกต์ Compose ต่อแอปทำให้รัศมีระเบิดเล็กลง
เคล็ดลับ
- ใส่ compose.yaml ใน git เก็บ .env.example โดยไม่มีความลับจริง
- ใช้ profiles สำหรับ worker ทางเลือก (docker compose --profile workers up -d)
- ดู docker stats เมื่อกำหนดขนาด VPS
- bind mount ซอร์สโค้ดไม่ใช่แพทเทิร์นโปรดักชัน — อบเป็นอิมเมจ
- ถ้าสแตกโตถึงหลาย VPS ค่อยดู orchestration — ไม่ก่อนหน้านั้น
การตั้ง Compose แบบใกล้โปรดักชันบน VPS เครื่องเดียวคือ แพ็กเกจ Docker ทางการ .env ที่ถูกล็อก ไฟล์ YAML ที่ไม่เผยแพร่ฐานข้อมูล healthcheck รีเวิร์สพร็อกซีบน 80/443 UFW แท็กอิมเมจที่ย้อนได้ และ dump Postgres ที่คุณกู้จริงมาแล้วครั้งหนึ่ง เริ่มจากเทมเพลตในบทความนี้ แทนที่อิมเมจแอป และปฏิบัติกับ docker compose down -v เหมือนคำสั่งทำลายด้วยความเคารพเดียวกับ rm -rf