Docker Compose dev environment: 5 dev share 1 VPS không đụng nhau

Chia sẻ bài viết

Mục lục
TL;DR
  • 1 VPS Cloud VPS 80 (4GB RAM, 799k/tháng) cho 5 dev cùng dev branch feature riêng, không đụng port/database.
  • Mỗi dev có namespace riêng qua COMPOSE_PROJECT_NAME, container và network tự prefix theo username.
  • Traefik reverse proxy tự route subdomain dev1.preview.team.local thành container của dev1.
  • Postgres dùng database riêng cho mỗi dev (myapp_dev1, myapp_dev2), share 1 instance Postgres tiết kiệm RAM.
  • Bonus: script teardown 1 lệnh xoá hết container, volume, DB của dev khi đóng project.

Team 5 dev, mỗi người laptop config khác nhau (M1 Mac, Windows WSL2, Ubuntu Lenovo), mỗi người setup dev environment 2-3 tiếng. Mất thời gian, lỗi vặt như "machine tôi chạy được, máy bạn thì không". Giải pháp: 1 VPS shared, mỗi dev SSH vào dùng VSCode Remote, chạy Docker Compose isolated namespace. Setup 1 lần, onboard dev mới trong 15 phút.

Mình triển khai mô hình này cho 3 team agency Việt Nam từ 2024, lưu được 30-50 giờ/dev/năm chỉ riêng setup. Bài này chia sẻ full template: docker-compose.yml multi-tenant, Traefik routing, Postgres shared, workflow đẩy code branch lên VPS dev cá nhân.

1. Tại sao shared VPS dev environment?

Vấn đềLocal devShared VPS dev
Setup time/dev mới2-4 giờ15 phút
Lỗi "works on my machine"Thường xuyênHiếm
OS difference (Mac/Win/Linux)Khó debugĐồng nhất Linux
RAM laptop ăn 6-8GB cho DockerPin chết nhanhLaptop chạy nhẹ
Test feature trên thiết bị thậtPhải tunnel ngrokSubdomain HTTPS sẵn
Chi phí0đ (nếu có laptop mạnh)799k/tháng cho team 5

Mô hình này không thay thế hoàn toàn local dev (bạn vẫn dev local cho feature nhanh), mà là layer integration: dev branch xong push lên VPS dev cá nhân, share link cho QA/PM xem trước khi merge.

2. Cấu hình VPS gốc

Cloud VPS 80 (4GB RAM, 4 vCPU, 80GB SSD CEPH, 799k/tháng) đủ cho team 5 dev. Tỷ lệ: mỗi dev ăn 600MB RAM (1 app Node + 1 worker + share Postgres/Redis). Tổng 3GB, dư 1GB cho OS và Traefik.

# Update OS và cài Docker
dnf update -y
dnf install -y docker docker-compose-plugin git curl tmux htop

systemctl enable --now docker

# Tạo group docker để dev không cần sudo
groupadd docker
for u in dev1 dev2 dev3 dev4 dev5; do
  useradd -m -s /bin/bash $u
  usermod -aG docker $u
  mkdir -p /home/$u/.ssh
  # Copy SSH public key của dev vào /home/$u/.ssh/authorized_keys
done

3. Docker Compose template với namespace isolation

Trick chính: dùng COMPOSE_PROJECT_NAME để mỗi dev có namespace riêng. Container, volume, network tự prefix.

# /home/dev1/myapp/.env
COMPOSE_PROJECT_NAME=dev1
DEV_USER=dev1
APP_PORT=3001       # mỗi dev port riêng nếu cần
DB_NAME=myapp_dev1
DB_HOST=postgres-shared
REDIS_HOST=redis-shared

File docker-compose.yml dùng env var:

services:
  app:
    build: .
    container_name: ${DEV_USER}_app
    environment:
      DATABASE_URL: postgres://shared:secure@${DB_HOST}:5432/${DB_NAME}
      REDIS_URL: redis://${REDIS_HOST}:6379/0
      NODE_ENV: development
    volumes:
      - .:/app
      - /app/node_modules
    networks:
      - shared-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${DEV_USER}-app.rule=Host(`${DEV_USER}.preview.team.local`)"
      - "traefik.http.services.${DEV_USER}-app.loadbalancer.server.port=3000"

  worker:
    build: .
    container_name: ${DEV_USER}_worker
    command: npm run worker
    environment:
      DATABASE_URL: postgres://shared:secure@${DB_HOST}:5432/${DB_NAME}
    networks:
      - shared-net

networks:
  shared-net:
    external: true
    name: shared-net

Khi dev1 chạy docker compose up, container tên dev1_app và dev1_worker. Khi dev2 chạy cùng config trong /home/dev2/myapp, container thành dev2_app và dev2_worker. Không đụng nhau.

4. Setup Postgres và Redis shared

Chạy 1 Postgres và 1 Redis chung cho cả team, tiết kiệm RAM. Mỗi dev có database riêng trong cùng instance Postgres:

# /opt/shared/docker-compose.yml (chạy bằng root hoặc user shared)
services:
  postgres-shared:
    image: postgres:16-alpine
    container_name: postgres-shared
    environment:
      POSTGRES_USER: shared
      POSTGRES_PASSWORD: your_secure_password
      POSTGRES_DB: postgres
    volumes:
      - pg-data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      - shared-net

  redis-shared:
    image: redis:7-alpine
    container_name: redis-shared
    networks:
      - shared-net

  traefik:
    image: traefik:v3.2
    container_name: traefik
    command:
      - --providers.docker
      - --providers.docker.network=shared-net
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - [email protected]
      - --certificatesresolvers.le.acme.storage=/acme/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-acme:/acme
    networks:
      - shared-net

volumes:
  pg-data:
  traefik-acme:

networks:
  shared-net:
    name: shared-net

File init.sql tạo database cho mỗi dev:

CREATE DATABASE myapp_dev1 OWNER shared;
CREATE DATABASE myapp_dev2 OWNER shared;
CREATE DATABASE myapp_dev3 OWNER shared;
CREATE DATABASE myapp_dev4 OWNER shared;
CREATE DATABASE myapp_dev5 OWNER shared;
GRANT ALL PRIVILEGES ON DATABASE myapp_dev1 TO shared;
-- ... etc

5. DNS wildcard cho subdomain dev

Trỏ A record *.preview.team.local hoặc *.dev.your-domain.com về IP VPS. Sau đó dev1.preview.your-domain.com tự route tới container dev1_app, dev2 tới dev2_app, etc. Traefik làm hết.

Để có HTTPS auto Let's Encrypt cho wildcard, dùng DNS-01 challenge với Cloudflare API:

# Traefik command thêm:
- --certificatesresolvers.le.acme.dnschallenge=true
- --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare

# Environment:
environment:
  CF_API_EMAIL: $CLOUDFLARE_EMAIL
  CF_DNS_API_TOKEN: $CLOUDFLARE_TOKEN

6. Workflow dev hằng ngày

  1. Dev SSH vào VPS: ssh [email protected]
  2. cd ~/myapp, git pull, checkout branch feature mới
  3. docker compose up -d
  4. Mở browser https://dev1.dev.your-domain.com xem app
  5. Edit code qua VSCode Remote SSH, hot reload tự động
  6. Test xong, git push, tạo PR
  7. Sau merge, docker compose down -v để clean

VSCode Remote SSH extension cho phép edit file trên VPS như local, intellisense và debug đầy đủ. Trải nghiệm gần như local nhưng không tốn RAM laptop.

7. Script helper tiện cho dev

# /usr/local/bin/devup
#!/bin/bash
USER=$(whoami)
cd ~/myapp
git pull
docker compose up -d --build
echo "App live at: https://$USER.dev.your-domain.com"

# /usr/local/bin/devdown
#!/bin/bash
cd ~/myapp
docker compose down -v
echo "Cleaned containers and volumes for $(whoami)"

# /usr/local/bin/devreset
#!/bin/bash
USER=$(whoami)
docker compose down -v
docker exec postgres-shared psql -U shared -c "DROP DATABASE IF EXISTS myapp_$USER; CREATE DATABASE myapp_$USER OWNER shared;"
docker compose up -d
echo "Reset database for $USER"

chmod +x cho mỗi script, dev gõ devup, devdown, devreset là done.

8. Monitor resource usage realtime

# Xem RAM/CPU mỗi container
docker stats

# Xem ai đang chiếm RAM nhiều nhất
docker stats --format "table {{.Container}}t{{.MemUsage}}t{{.CPUPerc}}" 
  | sort -k2 -hr | head

Nếu thấy 1 dev nuốt 2GB RAM (vd Node memory leak trong dev branch), ping nhắc nhở hoặc tự docker stop. Setup alert qua Uptime Kuma nếu RAM VPS > 90%.

9. Backup data và auto cleanup volume

Dev environment không cần backup quan trọng như prod, nhưng nên backup Postgres hằng tuần để tránh dev mất data demo:

# /opt/shared/backup.sh - cron tuần
docker exec postgres-shared pg_dumpall -U shared | gzip > /backup/all-$(date +%F).sql.gz

# Xoá file > 30 ngày
find /backup -name "*.sql.gz" -mtime +30 -delete

# Prune Docker thừa hằng ngày
docker system prune -af --volumes --filter "until=72h"

10. Bonus: Devcontainer cho VSCode

VSCode Devcontainer chuẩn hoá thêm 1 lớp: file .devcontainer/devcontainer.json define environment:

{
  "name": "MyApp Dev",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/app",
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "Prisma.prisma"
      ],
      "settings": {
        "editor.formatOnSave": true
      }
    }
  },
  "postCreateCommand": "npm install",
  "remoteUser": "node"
}

VSCode Remote Containers extension đọc file này, tự build container, install extension, mount workspace. Dev mới chỉ cần clone repo, mở VSCode, click "Reopen in Container" là xong.

11. Multi-project, multi-team

Nếu agency có 5 client project, mỗi dev làm 2-3 project: namespace mở rộng thành ${USER}_${PROJECT}:

# /home/dev1/clientA/.env
COMPOSE_PROJECT_NAME=dev1-clienta
APP_DOMAIN=dev1-clienta.dev.your-domain.com
DB_NAME=clienta_dev1

# /home/dev1/clientB/.env
COMPOSE_PROJECT_NAME=dev1-clientb
APP_DOMAIN=dev1-clientb.dev.your-domain.com
DB_NAME=clientb_dev1

Mỗi project subdomain riêng, container không đụng. RAM VPS cần scale lên Cloud VPS 160 hoặc 240 nếu 5 dev x 3 project chạy concurrent.

12. Security và network isolation

  1. SSH key only, fail2ban: dev không được password, brute force bị block.
  2. Postgres không expose port out: chỉ bind shared-net, không 0.0.0.0:5432.
  3. Tách network per project: nếu cần security cao, tạo network riêng cho mỗi project, dev chỉ join network của project mình.
  4. Secret quan trọng dùng Docker secret hoặc Vault: không hardcode trong env.
  5. Disable XSS preview public: subdomain dev nên có basic auth Traefik nếu thật sự private.

13. So sánh với Gitpod / Codespaces / DevPod

ToolChi phí/dev/thángSelf-hostCustomization
Self VPS + Docker160k (799k/5 dev)Toàn quyền
GitHub Codespaces~30-100 USDKhôngHạn chế
Gitpod Cloud~25-50 USDSelf-host enterpriseTrung bình
DevPod (open source)0đ (chỉ VPS cost)Toàn quyền
Coder.com~30 USD enterpriseTốt

Self VPS rẻ nhất và customize được. Codespaces/Gitpod tiện hơn cho dev cá nhân không muốn manage ops. Cho team Việt Nam 5-20 dev, self VPS là sweet spot.

14. Onboarding dev mới trong 15 phút

  1. Admin tạo user Linux trên VPS, add SSH key
  2. Send file .env mẫu cho dev mới
  3. Dev clone repo, copy .env, chỉnh DEV_USER và COMPOSE_PROJECT_NAME thành username của mình
  4. Chạy devup, đợi 2-3 phút build image lần đầu
  5. Mở browser https://username.dev.your-domain.com
  6. Done. Bắt đầu code.

So với onboarding local truyền thống (install Docker Desktop, Node, Postgres client, dotenv, migrate DB, seed data... mất 2-4 tiếng + đôi khi lỗi M1 vs Intel chip), workflow này rút ngắn xuống 15 phút.

15. Setup CI runner trên cùng VPS dev

Tận dụng RAM idle ban đêm cho CI: cài GitHub Actions self-hosted runner hoặc Gitea Actions runner trên cùng VPS. Trong giờ làm việc runner idle để dev có RAM, ngoài giờ runner chạy build/test.

useradd -m runner
sudo -iu runner

curl -O -L https://github.com/actions/runner/releases/latest/download/actions-runner-linux-x64.tar.gz
tar xzf actions-runner-linux-x64.tar.gz

./config.sh --url https://github.com/your-org/your-repo --token $RUNNER_TOKEN 
  --name vps-shared-dev --labels self-hosted,linux,dev-vps
./run.sh   # hoặc setup systemd service

Limit runner CPU bằng systemd CPUQuota=200% (tương đương 2 vCPU), để CI không nuốt hết tài nguyên dev đang code.

16. Khi nào nên tách dev environment riêng cho mỗi dev

  • Team scale lên 15+ dev: 1 VPS 4-8GB không đủ, chia 2-3 VPS hoặc cấp mỗi dev 1 VPS Cloud VPS 20 (199k/tháng).
  • Project yêu cầu OS khác nhau: 1 service Windows-only, cần VPS Windows riêng.
  • Compliance audit yêu cầu isolation tuyệt đối: shared environment không pass audit, mỗi dev VPS riêng + log riêng.
  • Dev có laptop mạnh sẵn: Mac Mini M4 / MacBook Pro M4 32GB chạy Docker local nhanh hơn VPS shared, có thể kết hợp local + shared dev.

Shared VPS sweet spot 3-10 dev. Trên đó nên đầu tư platform mạnh hơn như Coder.com self-host, DevPod, hoặc Gitpod self-host enterprise.

17. Bài học sau 2 năm chạy shared VPS dev

  1. Onboarding time giảm 90%, từ 4 giờ xuống 15 phút.
  2. Lỗi "works on my machine" gần như biến mất.
  3. Manager onboard dev mới gọi 1 dev senior hỗ trợ 15 phút, không cần 1 buổi DevOps support.
  4. Pin laptop dev tăng từ 4 giờ lên 8-9 giờ (không chạy Docker local).
  5. Cost rẻ hơn Codespaces 5-10 lần cho team Việt Nam.

Workflow này không hoàn hảo nhưng đủ tốt cho 80% agency và startup Việt Nam. Trade-off internet dependency và RAM share chấp nhận được khi nhìn vào lợi ích DX.

Cloud VPS cho vibe coder

VPS shared dev environment cho team 5-20 dev

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Cloud VPS 80 (4GB RAM, 799k/tháng) đủ cho 5 dev concurrent. Scale lên Cloud VPS 160 cho team 10+ dev.

Xem 8 cấu hình Cloud VPS →

FAQ

VPS shared có chậm hơn dev local không?

Build container ban đầu chậm hơn 1.5-2x (vì 4 vCPU shared cho 5 dev). Sau khi build xong, hot reload và browse app gần như tương đương local. SSD CEPH của TND nhanh hơn HDD nhiều VPS rẻ tiền, build npm install không quá lag.

Làm sao tránh dev này break dev kia?

Namespace isolation qua COMPOSE_PROJECT_NAME đảm bảo container/network/volume riêng. Database riêng cho mỗi dev. Cho RAM/CPU limit trong docker-compose (deploy.resources.limits) để 1 dev không nuốt hết tài nguyên VPS.

Nếu internet dev rớt thì sao?

Dev không code được nữa vì VSCode Remote cần internet. Đây là trade-off lớn nhất. Mitigation: dùng tmux trong SSH, công việc đang chạy không bị mất khi rớt. Code local cho task offline, push lên VPS dev khi có mạng.

Có dùng Docker Swarm hay Kubernetes thay được không?

Có nhưng overkill cho dev environment. Docker Compose đủ tốt và đơn giản. Swarm/K8s phù hợp khi cần multi-server failover, auto scaling - những thứ dev env không cần.

Sửa thẳng trên VPS có gây conflict git không?

Mỗi dev có folder ~/myapp riêng, là clone độc lập của repo. Mỗi dev pull/push branch của mình, không đụng nhau. Workflow git bình thường như local, chỉ là làm trên server.

2009
15+ năm vận hành liên tục
10+
tập đoàn lớn tin dùng
100+
doanh nghiệp SMB Việt
30 ngày
đổi key lỗi miễn phí
Phần mềm bản quyền chính hãng chúng tôi cung cấp
Bản quyền chính hãng Hóa đơn VAT đầy đủ Đổi key lỗi 30 ngày Vận hành từ 2009 MST 0200994870 Hotline 0225.999.6666