Docker Compose là gì? Chạy NestJS, MySQL và Redis chỉ với một lệnh

 Ở bài trước, chúng ta đã tìm hiểu cách sử dụng Dockerfile để đóng gói một ứng dụng thành Docker Image.

Nhưng trong dự án thực tế, chúng ta hiếm khi chỉ có một ứng dụng duy nhất.

Ví dụ một hệ thống backend có thể gồm:

NestJS
MySQL
Redis

Nếu chạy từng service bằng docker run, chúng ta sẽ phải viết khá nhiều lệnh và tự cấu hình network, port, volume.

Docker Compose giúp giải quyết vấn đề này.


1. Docker Compose là gì?

Docker Compose là công cụ giúp chúng ta định nghĩa và chạy nhiều Docker Container bằng một file cấu hình duy nhất.

Thông thường file này có tên:

compose.yaml

hoặc:

docker-compose.yml

Ví dụ hệ thống:

NestJS Backend
      ↓
   MySQL

      +

    Redis

thay vì phải chạy ba lệnh:

docker run ...
docker run ...
docker run ...

chúng ta chỉ cần:

docker compose up -d

Docker Compose sẽ tự tạo và chạy toàn bộ các service được khai báo.


2. Vì sao nên dùng Docker Compose?

Giả sử dự án của chúng ta có:

Backend: NestJS
Database: MySQL
Cache: Redis

Nếu không sử dụng Docker Compose, chúng ta cần tự:

  • Chạy MySQL.

  • Chạy Redis.

  • Chạy backend.

  • Map port.

  • Tạo Docker Network.

  • Cấu hình kết nối giữa các container.

  • Quản lý từng container riêng biệt.

Khi dùng Docker Compose, tất cả được mô tả trong một file.

Ví dụ:

compose.yaml

sẽ mô tả:

Backend dùng image nào?
MySQL dùng phiên bản nào?
Redis dùng phiên bản nào?
Port nào được mở?
Biến môi trường là gì?
Service nào phụ thuộc service nào?

Sau đó chỉ cần một câu lệnh để khởi động.


3. Kiến trúc chúng ta sẽ tạo

Trong bài này, chúng ta sẽ tạo hệ thống:

Browser / Client
       ↓
     :3000
       ↓
┌──────────────────┐
│ NestJS Backend   │
│ Container        │
└───────┬──────────┘
        │
   ┌────┴────┐
   ↓         ↓
 MySQL      Redis
 :3306      :6379

NestJS sẽ kết nối đến:

mysql:3306

và:

redis:6379

Điểm quan trọng ở đây là NestJS không sử dụng localhost để gọi MySQL hoặc Redis.

Trong Docker Compose, các container có thể gọi nhau bằng tên service.


4. Cấu trúc project

Ví dụ project NestJS:

my-project/
│
├── src/
│
├── package.json
├── package-lock.json
├── Dockerfile
├── .dockerignore
└── compose.yaml

Chúng ta sẽ sử dụng:

Dockerfile

để build backend.

Và:

compose.yaml

để chạy backend, MySQL và Redis.


5. Dockerfile cho NestJS

Trước tiên tạo:

Dockerfile

với nội dung:

FROM node:22

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["node", "dist/main"]

Dockerfile này sẽ:

1. Sử dụng Node.js 22
2. Tạo thư mục /app
3. Copy package.json
4. Cài dependency
5. Copy source code
6. Build NestJS
7. Chạy ứng dụng từ dist/main

6. Tạo file compose.yaml

Tiếp theo tạo:

compose.yaml

với nội dung:

services:

  backend:
    build:
      context: .
      dockerfile: Dockerfile

    container_name: nestjs-backend

    ports:
      - "3000:3000"

    environment:
      DB_HOST: mysql
      DB_PORT: 3306
      DB_USERNAME: root
      DB_PASSWORD: password
      DB_DATABASE: demo

      REDIS_HOST: redis
      REDIS_PORT: 6379

    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8

    container_name: mysql-db

    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: demo

    ports:
      - "3306:3306"

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

  redis:
    image: redis:latest

    container_name: redis-cache

    ports:
      - "6379:6379"

volumes:
  mysql-data:

Nhìn qua file này khá dài, nhưng thực tế chỉ có ba service:

backend
mysql
redis

7. Service backend

Phần đầu tiên:

backend:

định nghĩa container cho NestJS.

Phần:

build:
  context: .
  dockerfile: Dockerfile

yêu cầu Docker Compose build image từ Dockerfile trong thư mục hiện tại.

Có thể hiểu:

Source Code
    ↓
Dockerfile
    ↓
Docker Compose
    ↓
Backend Image
    ↓
Backend Container

8. Port của NestJS

Chúng ta khai báo:

ports:
  - "3000:3000"

Điều này có nghĩa là:

Máy tính          Container

localhost:3000 → 3000

Sau khi container chạy, chúng ta có thể truy cập:

http://localhost:3000

9. Environment Variable

Trong backend chúng ta khai báo:

environment:
  DB_HOST: mysql
  DB_PORT: 3306
  DB_USERNAME: root
  DB_PASSWORD: password
  DB_DATABASE: demo

Điểm cần chú ý nhất là:

DB_HOST=mysql

không phải:

DB_HOST=localhost

Tại sao?

Bởi vì NestJS đang chạy trong container.

Nếu NestJS gọi:

localhost

thì nó đang gọi chính container NestJS.

Trong khi MySQL nằm trong container khác.

Docker Compose tạo network nội bộ và cho phép gọi service bằng tên:

mysql

Do đó:

NestJS → mysql:3306

sẽ kết nối đến MySQL container.

Tương tự:

NestJS → redis:6379

sẽ kết nối Redis.


10. Service MySQL

Phần MySQL:

mysql:
  image: mysql:8

Docker sẽ sử dụng image:

mysql:8

từ Docker Registry.

Sau đó:

environment:
  MYSQL_ROOT_PASSWORD: password
  MYSQL_DATABASE: demo

sẽ tạo:

Root Password: password

Database: demo

Khi MySQL khởi động.


11. Volume để lưu dữ liệu MySQL

Trong MySQL chúng ta có:

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

Và phía cuối:

volumes:
  mysql-data:

Volume rất quan trọng với database.

Nếu không dùng volume:

Container bị xóa
        ↓
Database có thể mất dữ liệu

Khi sử dụng volume:

MySQL Container
      ↓
Docker Volume
      ↓
mysql-data

dữ liệu vẫn được giữ ngay cả khi container MySQL bị xóa và tạo lại.


12. Redis service

Redis đơn giản hơn:

redis:
  image: redis:latest

  ports:
    - "6379:6379"

Docker Compose sẽ tải Redis Image và tạo một container.

NestJS có thể kết nối bằng:

REDIS_HOST=redis
REDIS_PORT=6379

Không cần biết IP của container Redis.

Docker sẽ tự xử lý DNS nội bộ.


13. depends_on dùng để làm gì?

Backend có:

depends_on:
  - mysql
  - redis

Điều này có nghĩa Docker Compose sẽ khởi động:

MySQL
Redis

trước backend.

Tuy nhiên cần lưu ý:

depends_on

chỉ đảm bảo container được khởi động theo thứ tự cơ bản.

Nó không đảm bảo MySQL đã hoàn toàn sẵn sàng nhận connection.

Trong production, chúng ta thường sử dụng thêm:

healthcheck

hoặc cơ chế retry connection trong ứng dụng.


14. Chạy toàn bộ hệ thống

Sau khi đã có:

Dockerfile
compose.yaml

chạy:

docker compose up -d

Trong đó:

docker compose up

có nghĩa là:

Build/Create/Start các service

và:

-d

có nghĩa là chạy ở background.

Docker sẽ lần lượt tạo:

nestjs-backend

mysql-db

redis-cache

15. Kiểm tra container

Chạy:

docker compose ps

hoặc:

docker ps

Bạn sẽ thấy các container tương tự:

NAME              STATUS

nestjs-backend    Up

mysql-db          Up

redis-cache       Up

Lúc này hệ thống đã hoạt động.


16. Xem log

Để xem log toàn bộ hệ thống:

docker compose logs

Theo dõi realtime:

docker compose logs -f

Nếu chỉ muốn xem backend:

docker compose logs -f backend

MySQL:

docker compose logs -f mysql

Redis:

docker compose logs -f redis

Đây là những câu lệnh rất hữu ích khi debug.


17. Dừng hệ thống

Để dừng:

docker compose stop

Lệnh này dừng container nhưng không xóa chúng.

Muốn chạy lại:

docker compose start

18. Xóa container

Nếu muốn dừng và xóa toàn bộ container:

docker compose down

Docker sẽ xóa các container và network được tạo bởi Compose.

Tuy nhiên volume MySQL vẫn được giữ.

Nếu muốn xóa cả volume:

docker compose down -v

Cần cẩn thận với lệnh này vì dữ liệu database trong volume có thể bị xóa.


19. Build lại backend sau khi sửa code

Nếu source code thay đổi và cần build lại image:

docker compose up -d --build

Docker Compose sẽ build lại backend rồi khởi động hệ thống.

Đây là câu lệnh được sử dụng khá thường xuyên:

Edit Code
    ↓
docker compose up -d --build
    ↓
Build Image mới
    ↓
Restart Container

20. Không nên viết password trực tiếp trong compose.yaml

Ví dụ ở trên chúng ta đang viết:

DB_PASSWORD: password

để dễ hiểu.

Trong dự án thực tế, không nên commit password hoặc API Key trực tiếp vào:

compose.yaml

Thay vào đó có thể sử dụng:

.env

Ví dụ:

DB_HOST=mysql
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=my-secret-password
DB_DATABASE=demo

REDIS_HOST=redis
REDIS_PORT=6379

Sau đó trong Compose:

backend:
  env_file:
    - .env

Hoặc:

environment:
  DB_HOST: ${DB_HOST}
  DB_PORT: ${DB_PORT}
  DB_USERNAME: ${DB_USERNAME}
  DB_PASSWORD: ${DB_PASSWORD}

Và nhớ thêm:

.env

vào:

.gitignore

để tránh đưa secret lên Git.


21. Ví dụ cấu hình NestJS với MySQL

Nếu sử dụng TypeORM, phần cấu hình có thể như sau:

TypeOrmModule.forRoot({
  type: 'mysql',

  host: process.env.DB_HOST,

  port: Number(process.env.DB_PORT),

  username: process.env.DB_USERNAME,

  password: process.env.DB_PASSWORD,

  database: process.env.DB_DATABASE,

  autoLoadEntities: true,
});

Khi chạy local trực tiếp:

DB_HOST=localhost

Nhưng khi chạy bên trong Docker Compose:

DB_HOST=mysql

Đây là một trong những lỗi phổ biến nhất khi mới sử dụng Docker.


22. Ví dụ kết nối Redis

NestJS cũng có thể đọc:

REDIS_HOST=redis
REDIS_PORT=6379

Ví dụ:

{
  host: process.env.REDIS_HOST,
  port: Number(process.env.REDIS_PORT),
}

Docker sẽ tự tìm container Redis thông qua service name:

redis

23. Docker Compose giúp được gì trong team?

Giả sử team có ba developer.

Nếu không sử dụng Docker Compose:

Developer A
Node 22
MySQL 8
Redis 7
Developer B
Node 20
MySQL 5.7
Redis 6
Developer C
Node 22
MySQL chưa cài
Redis chạy bằng Homebrew

Rất dễ xảy ra lỗi khác nhau giữa các máy.

Khi dùng Docker Compose, mọi người chỉ cần:

git clone ...

sau đó:

docker compose up -d

và cùng sử dụng:

NestJS version giống nhau

MySQL 8

Redis version giống nhau

Điều này giúp môi trường development nhất quán hơn.


24. Những lệnh Docker Compose nên nhớ

Khởi động:

docker compose up -d

Khởi động và build lại:

docker compose up -d --build

Kiểm tra container:

docker compose ps

Xem log:

docker compose logs

Theo dõi log:

docker compose logs -f

Dừng:

docker compose stop

Khởi động lại:

docker compose start

Restart:

docker compose restart

Dừng và xóa container:

docker compose down

Xóa cả volume:

docker compose down -v

25. Tổng kết

Docker Compose giúp chúng ta quản lý nhiều container dễ dàng hơn.

Trong ví dụ này:

              Docker Compose

                    ↓

        ┌───────────┴───────────┐
        ↓                       ↓

   NestJS Backend

        ↓

   ┌────┴────┐
   ↓         ↓

 MySQL      Redis

Thay vì phải khởi động từng service riêng biệt, chúng ta chỉ cần:

docker compose up -d

Có thể nhớ Docker Compose theo cách đơn giản:

Dockerfile
    ↓
Build một ứng dụng

Docker Compose
    ↓
Quản lý nhiều ứng dụng / service

Nếu một dự án có:

Backend
Database
Redis
Queue
Frontend

Docker Compose sẽ giúp việc setup môi trường development, testing và deploy trở nên đơn giản và nhất quán hơn.

Sau khi hiểu Docker Compose, bước tiếp theo rất phù hợp là tìm hiểu Docker Network và Docker Volume để hiểu rõ hơn cách các container giao tiếp với nhau và cách Docker lưu trữ dữ liệu.

Đăng nhận xét

Mới hơn Cũ hơn