๐Ÿณ 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 & ๅŸŸๅ ยท ๐Ÿ’พ ๅค‡ไปฝๆขๅค ยท ๐Ÿ›ก๏ธ ๆœๅŠกๅ™จๅŠ ๅ›บ ยท ๐Ÿ—๏ธ ่ฟ”ๅ›ž้ฆ–้กต