API
DB
Redis
compose

Docker Compose cho local dev nhiều project

Cách dùng Docker Compose để chạy nhiều project song song không trùng port, tách riêng MySQL và Redis cho từng app, và giữ môi trường dev sạch, dễ reset.

14 phút đọc15/06/2026

Bài toán thực tế

Một máy dev thường phải chạy nhiều project cùng lúc:

  • project A cần MySQL + Redis
  • project B cũng cần MySQL nhưng data khác
  • project C lại có thêm MongoDB

Nếu cài trực tiếp lên host, rất nhanh sẽ dính:

  • conflict port
  • lệch version
  • data của project này lẫn với project khác

Cách gọn nhất là mỗi project có một file docker-compose.yml riêng, trong đó app, MySQL và Redis đi cùng nhau.

File compose hoàn chỉnh cho local dev

Nếu mục tiêu của bạn là: "cho tôi một file dùng được ngay để mỗi project có MySQL, Redis riêng và không đụng nhau", thì có thể bắt đầu luôn bằng setup này:

# docker-compose.yml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      NODE_ENV: development
      DB_HOST: mysql
      DB_PORT: 3306
      DB_USER: root
      DB_PASS: localroot
      DB_NAME: mydb
      REDIS_URL: redis://redis:6379
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started

  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: localroot
      MYSQL_DATABASE: mydb
    ports:
      - "3307:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-plocalroot"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 20s

  redis:
    image: redis:7-alpine
    ports:
      - "6380:6379"
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:

Dockerfile.dev:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "start:dev"]

Chạy:

docker compose up -d
docker compose logs -f api

Với file này:

  • app chạy ở localhost:3000
  • MySQL của project này chạy ở localhost:3307
  • Redis của project này chạy ở localhost:6380
  • data MySQL và Redis nằm trong volume riêng

Nếu project khác cũng dùng Compose, bạn chỉ cần đổi port host là đủ:

  • project A: 3307, 6380
  • project B: 3308, 6381
  • project C: 3309, 6382

Đó là phần quan trọng nhất để tránh conflict port mà vẫn giữ mỗi project có database/cache riêng.

Kiến trúc Docker Compose — API, MySQL, Redis nằm chung một network, giao tiếp qua tên service

Giải thích từng phần trong file compose trên

Vì sao api dùng DB_HOST=mysql thay vì localhost?

Vì bên trong network của Docker Compose, các service gọi nhau qua tên service.

Trong file trên:

  • api nói chuyện với MySQL qua mysql:3306
  • api nói chuyện với Redis qua redis:6379

localhost chỉ đúng khi bạn kết nối từ máy host vào container.

Vì sao MySQL là 3307:3306 và Redis là 6380:6379?

Format là:

HOST_PORT:CONTAINER_PORT

Tức là:

  • máy host dùng 3307
  • bên trong container MySQL vẫn là 3306

Làm vậy giúp mỗi project có thể giữ config bên trong giống nhau, chỉ khác cổng ngoài host.

Ví dụ project khác:

mysql:
  ports:
    - "3308:3306"

redis:
  ports:
    - "6381:6379"

Port mapping — 2 project chạy song song trên cùng máy, không conflict

Vì sao api mount cả project bằng - .:/app?

Với local dev, cách này tiện hơn mount riêng ./src:/app/src vì:

  • code đổi là container thấy ngay
  • nhiều framework cần cả config file ngoài src
  • đỡ sót thư mục như prisma, migrations, uploads, nest-cli.json

Phần:

- /app/node_modules

giúp node_modules trong container không bị thư mục host đè lên.

Vì sao MySQL và Redis cần volume riêng?

Vì nếu không có volume, mỗi lần xóa container là mất data.

Phần này giữ data bền hơn:

volumes:
  - mysql_data:/var/lib/mysql
  - redis_data:/data

Điều này rất hợp cho local dev nhiều project, vì:

  • mỗi project có data riêng
  • reset project A không ảnh hưởng project B

depends_onhealthcheck để làm gì?

Không ít project bị lỗi lúc start vì app lên trước MySQL.

Trong file này:

  • mysqlhealthcheck
  • api chờ mysql healthy rồi mới start

Như vậy đỡ gặp lỗi kiểu:

  • connect ECONNREFUSED
  • database not ready
  • migration chạy quá sớm

Các command nên chạy sau khi có file compose

Sau khi có file hoàn chỉnh, phần command mới thực sự dễ hiểu hơn:

# khởi động stack
docker compose up -d

# xem log app
docker compose logs -f api

# xem container nào đang chạy
docker compose ps

# vào shell của app
docker compose exec api sh

# dừng stack
docker compose down

# dừng và xóa luôn volume
docker compose down -v

docker compose down -v rất hữu ích khi bạn muốn reset sạch database của đúng project đó, thay vì dọn thủ công trên máy host.

Khi nào nên expose MySQL, Redis ra host?

Trong local dev, thường chỉ expose để:

  • dùng TablePlus, DBeaver
  • debug Redis bằng GUI hoặc CLI trên host

Nếu không cần, bạn có thể bỏ hẳn ports của MySQL và Redis. App vẫn kết nối được nội bộ qua tên service.

Ví dụ an toàn hơn:

mysql:
  image: mysql:8
  volumes:
    - mysql_data:/var/lib/mysql

redis:
  image: redis:7-alpine

Lúc đó:

  • app vẫn dùng được mysql:3306, redis:6379
  • nhưng host không truy cập trực tiếp vào được nữa

Khi nào nên tách riêng MySQL, Redis cho từng project?

Nên tách khi:

  • mỗi project có schema khác nhau
  • bạn muốn down -v project này mà không ảnh hưởng project khác
  • version MySQL hoặc Redis giữa các project khác nhau

Không nên cố dùng chung một MySQL local cho tất cả project nếu mục tiêu là dev nhanh và dễ reset. Lúc đầu có vẻ gọn, nhưng về sau thường rất dễ lẫn data và lệch config.

Volume và Network — named volume giữ data, bind mount cho hot reload, network tự resolve tên service

File Compose Cho Production

Production cần thêm một số điểm khác:

  • Không bind mount source code (build image hoàn chỉnh)
  • Không expose port database ra ngoài host
  • Thêm restart policy
  • Resource limits
# docker-compose.prod.yml
services:
  api:
    image: myapp:latest      # dùng image đã build sẵn, không build lại
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DB_HOST: mysql
      DB_PASS: ${DB_PASSWORD}
      REDIS_URL: redis://redis:6379
    restart: unless-stopped
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: mydb
    volumes:
      - mysql_data:/var/lib/mysql
    # Không expose port ra ngoài host trong production
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - api
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:

Deploy:

# Build image mới
docker build -t myapp:latest .

# Pull image mới (nếu dùng registry)
docker compose -f docker-compose.prod.yml pull

# Restart với image mới, không downtime cho các service khác
docker compose -f docker-compose.prod.yml up -d --no-deps api

Các Lệnh Hay Dùng Nhất

# Khởi động tất cả service (detach mode)
docker compose up -d

# Dừng tất cả
docker compose down

# Dừng và xóa cả volume (xóa data)
docker compose down -v

# Xem log một service
docker compose logs -f api

# Restart một service
docker compose restart api

# Vào shell trong container
docker compose exec api sh

# Xem trạng thái các service
docker compose ps

# Build lại image (khi thay đổi Dockerfile)
docker compose build api

# Scale một service (chạy nhiều instance)
docker compose up -d --scale api=3

Healthcheck — Đợi Service Sẵn Sàng Mới Start

API thường bị crash khi khởi động nếu database chưa ready. Dùng healthcheck + depends_on condition để xử lý:

mysql:
  image: mysql:8
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
    interval: 5s
    timeout: 5s
    retries: 10
    start_period: 30s

api:
  depends_on:
    mysql:
      condition: service_healthy   # chờ mysql healthy rồi mới start api

Tổng Kết

Docker Compose không chỉ là tool tiện lợi để chạy database nhanh — nó là cách chuẩn để quản lý multi-service application từ dev đến production:

Cài trực tiếp Docker Compose
Setup machine mới Cài lại từ đầu docker compose up
Nhiều project cùng lúc Conflict port, version Mỗi project độc lập
Đồng bộ team "Máy tao chạy được" Cùng một config
Dọn dẹp Còn sót file rải rắc docker compose down -v
Production Config riêng Cùng file, khác biến môi trường

Một khi đã quen với Compose, sẽ rất khó quay về cài service trực tiếp trên máy.