Ở 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
RedisNế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.yamlhoặc:
docker-compose.ymlVí dụ hệ thống:
NestJS Backend
↓
MySQL
+
Redisthay vì phải chạy ba lệnh:
docker run ...
docker run ...
docker run ...chúng ta chỉ cần:
docker compose up -dDocker 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: RedisNế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.yamlsẽ 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 :6379NestJS sẽ kết nối đến:
mysql:3306và:
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.yamlChú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:
Dockerfilevớ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/main6. Tạo file compose.yaml
Tiếp theo tạo:
compose.yamlvớ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
redis7. Service backend
Phần đầu tiên:
backend:định nghĩa container cho NestJS.
Phần:
build:
context: .
dockerfile: Dockerfileyê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 Container8. 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 → 3000Sau khi container chạy, chúng ta có thể truy cập:
http://localhost:30009. 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=mysqlkhông phải:
DB_HOST=localhostTại sao?
Bởi vì NestJS đang chạy trong container.
Nếu NestJS gọi:
localhostthì 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:
mysqlDo đó:
NestJS → mysql:3306sẽ kết nối đến MySQL container.
Tương tự:
NestJS → redis:6379sẽ kết nối Redis.
10. Service MySQL
Phần MySQL:
mysql:
image: mysql:8Docker sẽ sử dụng image:
mysql:8từ Docker Registry.
Sau đó:
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: demosẽ tạo:
Root Password: password
Database: demoKhi MySQL khởi động.
11. Volume để lưu dữ liệu MySQL
Trong MySQL chúng ta có:
volumes:
- mysql-data:/var/lib/mysqlVà 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ệuKhi sử dụng volume:
MySQL Container
↓
Docker Volume
↓
mysql-datadữ 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=6379Khô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
Redistrước backend.
Tuy nhiên cần lưu ý:
depends_onchỉ đả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:
healthcheckhoặc cơ chế retry connection trong ứng dụng.
14. Chạy toàn bộ hệ thống
Sau khi đã có:
Dockerfile
compose.yamlchạy:
docker compose up -dTrong đó:
docker compose upcó nghĩa là:
Build/Create/Start các servicevà:
-dcó nghĩa là chạy ở background.
Docker sẽ lần lượt tạo:
nestjs-backend
mysql-db
redis-cache15. Kiểm tra container
Chạy:
docker compose pshoặc:
docker psBạn sẽ thấy các container tương tự:
NAME STATUS
nestjs-backend Up
mysql-db Up
redis-cache UpLúc này hệ thống đã hoạt động.
16. Xem log
Để xem log toàn bộ hệ thống:
docker compose logsTheo dõi realtime:
docker compose logs -fNếu chỉ muốn xem backend:
docker compose logs -f backendMySQL:
docker compose logs -f mysqlRedis:
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 stopLệnh này dừng container nhưng không xóa chúng.
Muốn chạy lại:
docker compose start18. Xóa container
Nếu muốn dừng và xóa toàn bộ container:
docker compose downDocker 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 -vCầ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 --buildDocker 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 Container20. 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.yamlThay vào đó có thể sử dụng:
.envVí dụ:
DB_HOST=mysql
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=my-secret-password
DB_DATABASE=demo
REDIS_HOST=redis
REDIS_PORT=6379Sau đó trong Compose:
backend:
env_file:
- .envHoặc:
environment:
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}Và nhớ thêm:
.envvà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=localhostNhư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=6379Ví dụ:
{
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
}Docker sẽ tự tìm container Redis thông qua service name:
redis23. 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 7Developer B
Node 20
MySQL 5.7
Redis 6Developer C
Node 22
MySQL chưa cài
Redis chạy bằng HomebrewRấ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 -dvà 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 -dKhởi động và build lại:
docker compose up -d --buildKiểm tra container:
docker compose psXem log:
docker compose logsTheo dõi log:
docker compose logs -fDừng:
docker compose stopKhởi động lại:
docker compose startRestart:
docker compose restartDừng và xóa container:
docker compose downXóa cả volume:
docker compose down -v25. 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 RedisThay vì phải khởi động từng service riêng biệt, chúng ta chỉ cần:
docker compose up -dCó 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 / serviceNếu một dự án có:
Backend
Database
Redis
Queue
FrontendDocker 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.


