๐ณ Docker Compose ๅฎๆ
ไธๅฐ VPS ่ท 10 ไธชๆๅก็็ผๆโโๅไปฃใๅฅๅบทๆฃๆฅใ่ชๅจ้ๅฏใๆฅๅฟ็ฎก็๏ผๅ
จๅจ่ฟไธไธช compose.yml ้
๐ ๆถๆๆป่ง
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ VPS ($5-20/ๆ) โ
โ โ
:443 โโโโโโโโโโโบ โ Traefik (ๅๅไปฃ็ + SSL) โ
โ โโโ app1.example.com โ App1 โ
โ โโโ app2.example.com โ App2 โ
โ โโโ api.example.com โ API โ
โ โโโ docs.example.com โ Docs โ
โ โ
โ PostgreSQL (ๅ
ฑไบซๆฐๆฎๅบ) โ
โ Redis (ๅ
ฑไบซ็ผๅญ) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ App1 (Next.js) โ โ
โ โ App2 (Python API) โ โ
โ โ App3 (Go Worker) โ โ
โ โ Adminer (DB็ฎก็) โ โ
โ โ Uptime Kuma (็ๆง) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ณ ๅฎๆด compose.yml
# docker-compose.yml โ ไธๅฐ VPS ่ท 10 ไธชๆๅก
version: "3.8"
# ๅ
ฑไบซ็ฝ็ป
networks:
web:
external: true # Traefik ไฝฟ็จ็็ฝ็ป
internal: # ๅ
้จๆๅก็ฝ็ป๏ผไธๆด้ฒๅฐๅค็ฝ
# ๅ
ฑไบซๆฐๆฎๅท
volumes:
postgres-data:
redis-data:
traefik-ssl:
services:
# ==========================================
# ๅๅไปฃ็ โ Traefik
# ==========================================
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/ssl/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-ssl:/ssl
networks:
- web
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`proxy.example.com`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$05..."
# ==========================================
# ๆฐๆฎๅบ โ PostgreSQL
# ==========================================
postgres:
image: postgres:16-alpine
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
# ==========================================
# ็ผๅญ โ Redis
# ==========================================
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- internal
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
# ==========================================
# ๅบ็จ โ Next.js SaaS
# ==========================================
app:
image: ghcr.io/yourorg/app:latest
container_name: app
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/app
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
NEXTAUTH_URL: https://app.example.com
networks:
- web
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# ==========================================
# API โ Python FastAPI
# ==========================================
api:
build: ./api
container_name: api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/api
networks:
- web
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.routers.api.middlewares=ratelimit"
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
# ==========================================
# ๅๅฐไปปๅก โ Worker
# ==========================================
worker:
build: ./api
container_name: worker
restart: unless-stopped
command: python -m celery -A app worker -l info
depends_on:
redis:
condition: service_healthy
environment:
CELERY_BROKER_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
networks:
- internal
# ==========================================
# ็ๆง โ Uptime Kuma
# ==========================================
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
volumes:
- ./uptime-kuma:/app/data
networks:
- web
labels:
- "traefik.enable=true"
- "traefik.http.routers.uptime.rule=Host(`status.example.com`)"
- "traefik.http.routers.uptime.entrypoints=websecure"
- "traefik.http.routers.uptime.tls.certresolver=letsencrypt"
# ==========================================
# DB ็ฎก็ โ Adminer
# ==========================================
adminer:
image: adminer:latest
container_name: adminer
restart: unless-stopped
networks:
- web
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.adminer.rule=Host(`db.example.com`)"
- "traefik.http.routers.adminer.entrypoints=websecure"
- "traefik.http.routers.adminer.tls.certresolver=letsencrypt"
- "traefik.http.routers.adminer.middlewares=ipwhitelist"
- "traefik.http.middlewares.ipwhitelist.ipwhitelist.sourcerange=YOUR_IP/32"
๐ง ๆฅๅฟ็ฎก็
# /etc/docker/daemon.json โ ๅ
จๅฑๆฅๅฟ้
็ฝฎ
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
# ๆฅ็ๆฅๅฟ
docker compose logs -f app # ๅฎๆถ่ท่ธช
docker compose logs --since 1h api # ๆ่ฟ1ๅฐๆถ
docker compose logs --tail 100 worker # ๆๅ100่ก
# ๆฅๅฟ่ฝฎ่ฝฌ๏ผๅจ compose.yml ไธญ้
็ฝฎ๏ผ
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
๐ ่ตๆบ้ๅถ
# ๅจ compose.yml ไธญ้ๅถๆฏไธชๆๅก็่ตๆบ
services:
app:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.5"
memory: 256M
worker:
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
postgres:
deploy:
resources:
limits:
cpus: "1.0"
memory: 1G
# ๆฅ็่ตๆบไฝฟ็จ
docker stats # ๅฎๆถ่ตๆบไฝฟ็จ
docker compose top # ่ฟ็จๅ่กจ
๐ ้จ็ฝฒ่ๆฌ
#!/bin/bash
# deploy.sh โ ้ถๅๆบ้จ็ฝฒ่ๆฌ
set -e
echo "๐ ๅผๅง้จ็ฝฒ..."
# 1. ๆๅๆๆฐ้ๅ
docker compose pull
# 2. ้ไธชๆๅกๆปๅจๆดๆฐ๏ผ้ถๅๆบ๏ผ
for service in app api worker; do
echo "๐ฆ ๆดๆฐ $service..."
docker compose up -d --no-deps --build $service
# ็ญๅพ
ๅฅๅบทๆฃๆฅ้่ฟ
echo "โณ ็ญๅพ
$service ๅฅๅบท..."
timeout=60
while [ $timeout -gt 0 ]; do
health=$(docker inspect --format='{{.State.Health.Status}}' $service 2>/dev/null || echo "unknown")
if [ "$health" = "healthy" ]; then
echo "โ
$service ๅฅๅบท"
break
fi
sleep 2
timeout=$((timeout - 2))
done
if [ $timeout -le 0 ]; then
echo "โ $service ๅฅๅบทๆฃๆฅ่ถ
ๆถ๏ผๅๆป..."
docker compose -f docker-compose.yml -f docker-compose.rollback.yml up -d --no-deps $service
exit 1
fi
done
# 3. ๆธ
็ๆง้ๅ
docker image prune -f
echo "๐ ้จ็ฝฒๅฎๆ๏ผ"
๐ก ็ฌ็ซๅผๅ่
็ Docker Compose ๅๅ
1. ๆฐธ่ฟ่ฎพ็ฝฎ restart: unless-stoppedโโๆๅกๅจ้ๅฏๅ่ชๅจๆขๅค 2. ๆฐธ่ฟ่ฎพ็ฝฎ healthcheckโโ็ฅ้ๆๅกๆฏๅฆ็็ๅจ่ท 3. ๆๆไฟกๆฏ็จ .env ๆไปถ๏ผไธ่ฆ็กฌ็ผ็ ๅจ compose.yml 4. ๅ
ฑไบซๆฐๆฎๅบ็จไธๅ DB ๅ่้ไธๅๅฎนๅจโโ็ๅ
ๅญ 5. ๅชๆด้ฒ Traefik ๅฐๅ
ฌ็ฝ๏ผๅ
ถไปๆๅก่ตฐๅ
้จ็ฝ็ปใ
๐ ็ธๅ
ณไธ้ข
๐ SSL & ๅๅ ยท ๐พ ๅคไปฝๆขๅค ยท ๐ก๏ธ ๆๅกๅจๅ ๅบ ยท ๐๏ธ ่ฟๅ้ฆ้กต