Bài 40.1: Docker Compose configuration

Bài 40.1: Docker Compose configuration
Chào mừng bạn đến với bài học về Docker Compose, một công cụ mạnh mẽ giúp định nghĩa và chạy các ứng dụng đa container của Docker. Nếu bạn đã làm quen với Docker và thấy việc quản lý nhiều container (ví dụ: một container cho database, một cho backend API, một cho frontend, một cho cache...) bằng các lệnh docker run
riêng lẻ khá phức tạp và tốn thời gian, thì Docker Compose chính là vị cứu tinh của bạn!
Docker Compose cho phép bạn mô tả toàn bộ kiến trúc ứng dụng của mình trong một tệp tin duy nhất, thường là docker-compose.yml
. Sau đó, chỉ với một lệnh đơn giản, bạn có thể khởi tạo, cấu hình và kết nối tất cả các dịch vụ (services) cần thiết. Điều này không chỉ giúp đơn giản hóa quy trình làm việc mà còn đảm bảo môi trường phát triển và triển khai của bạn luôn nhất quán.
docker-compose.yml
: Trái tim của ứng dụng đa container
Tệp tin docker-compose.yml
(hoặc docker-compose.yaml
) là nơi bạn định nghĩa tất cả các thành phần (services) của ứng dụng, cách chúng hoạt động, kết nối với nhau và các tài nguyên mà chúng cần (như cổng, volumes, network). Đây là một tệp tin cấu hình theo định dạng YAML, nổi tiếng với cú pháp đơn giản và dễ đọc.
Một cấu trúc cơ bản của tệp tin này sẽ trông như sau:
version: '3.8' # Xác định phiên bản của cú pháp Docker Compose file
services: # Phần định nghĩa các dịch vụ (các container) trong ứng dụng của bạn
service1: # Tên của dịch vụ đầu tiên
# Cấu hình cho service1
...
service2: # Tên của dịch vụ thứ hai
# Cấu hình cho service2
...
# Các định nghĩa khác như Volumes, Networks (tuỳ chọn)
volumes:
volume_name:
networks:
network_name:
driver: bridge
Hãy đi sâu vào các phần chính.
version
Phần này chỉ định phiên bản của cú pháp Compose file mà bạn đang sử dụng. Các phiên bản khác nhau có thể hỗ trợ các tính năng khác nhau. Phiên bản 3.x
thường được sử dụng và tương thích với các tính năng Docker mới nhất.
version: '3.8'
Giải thích: Khai báo này cho Docker Compose biết nên phân tích tệp tin dựa trên quy tắc và tính năng của phiên bản 3.8. Luôn nên sử dụng phiên bản mới nhất hoặc phiên bản được khuyến nghị cho môi trường của bạn.
services
Đây là phần quan trọng nhất. Dưới mục services
, bạn liệt kê từng thành phần của ứng dụng như một dịch vụ riêng biệt. Mỗi dịch vụ sẽ tương ứng với một container (hoặc nhiều container cùng loại nếu cấu hình mở rộng).
Cấu hình chi tiết cho một service
Bên trong mỗi định nghĩa dịch vụ, có rất nhiều tùy chọn cấu hình. Dưới đây là một số tùy chọn phổ biến và cực kỳ hữu ích cho phát triển web:
image
: Chỉ định image Docker để sử dụng cho dịch vụ này. Compose sẽ tự động tải image nếu nó chưa tồn tại trên máy của bạn.services: nginx: image: nginx:latest # Sử dụng image Nginx phiên bản latest
Giải thích: Khai báo này nói với Docker Compose rằng dịch vụ có tên
nginx
sẽ được tạo từ imagenginx:latest
từ Docker Hub.build
: Thay vì sử dụng một image có sẵn, bạn có thể yêu cầu Compose tự build image từ một Dockerfile. Điều này rất phổ biến cho các ứng dụng backend hoặc frontend cần môi trường tùy chỉnh.services: backend: build: ./backend # Build image từ Dockerfile trong thư mục ./backend
Giải thích: Dịch vụ
backend
sẽ được tạo từ một image được xây dựng dựa trên nội dung và Dockerfile trong thư mục./backend
trên máy host của bạn.ports
: Rất quan trọng cho các ứng dụng web! Ánh xạ (mapping) cổng giữa máy host của bạn và container. Điều này cho phép bạn truy cập vào ứng dụng đang chạy trong container từ trình duyệt trên máy tính của mình.services: frontend: image: node:lts ports: - "3000:3000" # Ánh xạ cổng 3000 của host tới cổng 3000 của container - "8080:80" # Ánh xạ cổng 8080 của host tới cổng 80 của container
Giải thích: Dòng
- "3000:3000"
nghĩa là bất kỳ traffic nào đến cổng3000
trên máy host sẽ được chuyển hướng đến cổng3000
bên trong containerfrontend
. Dòng- "8080:80"
làm tương tự với cổng 8080 trên host và cổng 80 trong container.volumes
: Cấu hình volumes cho container. Volumes là cách ưa thích để lưu trữ dữ liệu bền vững (persistent data) hoặc để mount code từ máy host vào container trong môi trường phát triển.services: database: image: postgres:latest volumes: - db_data:/var/lib/postgresql/data # Ánh xạ named volume 'db_data' tới đường dẫn dữ liệu của Postgres trong container backend: build: ./backend volumes: - ./backend:/app # Mount thư mục code backend từ host vào /app trong container - /app/node_modules # Bỏ qua (ignore) thư mục node_modules trên host khi mount
Giải thích:
- Dòng
- db_data:/var/lib/postgresql/data
sử dụng một named volume (db_data
, được định nghĩa ở phầnvolumes
ở ngoài cùng) để lưu trữ dữ liệu của PostgreSQL. Dữ liệu này sẽ không bị mất khi container database bị xoá và tạo lại. - Dòng
- ./backend:/app
là một bind mount. Nó ánh xạ thư mục./backend
trên máy host (nơi chứa code nguồn của bạn) tới thư mục/app
bên trong containerbackend
. Điều này cực kỳ hữu ích cho phát triển vì bất kỳ thay đổi nào bạn thực hiện với code trên máy host sẽ ngay lập tức hiển thị trong container (thường kết hợp với các công cụ hot-reloading). - Dòng
- /app/node_modules
là một kỹ thuật nâng cao trong bind mount. Khi bạn mount thư mục./backend
chứanode_modules
của host vào/app
, nó sẽ ghi đè lên thư mụcnode_modules
mà Dockerfile có thể đã tạo bên trong container. Dòng này tạo một volume ẩn tại/app/node_modules
bên trong container, đảm bảo rằng container sử dụngnode_modules
của chính nó (được cài đặt trong quá trình build hoặc khi chạy lệnhnpm install
bên trong container) thay vì thư mụcnode_modules
từ máy host (mà có thể có kiến trúc khác).
- Dòng
environment
: Thiết lập các biến môi trường (environment variables) trong container. Rất hữu ích để cấu hình ứng dụng (ví dụ: thông tin kết nối database, API keys...).services: backend: build: ./backend environment: - DATABASE_URL=postgresql://myuser:mypassword@database:5432/mydb # Thiết lập biến môi trường - NODE_ENV=development - API_KEY=your_secret_key # Lưu ý: Không nên lưu trữ key nhạy cảm trực tiếp trong file này ở production
Giải thích: Các biến này sẽ có sẵn trong môi trường chạy của container
backend
. Ứng dụng backend của bạn có thể đọc các biến này để biết cách kết nối tới database (sử dụng tên dịch vụdatabase
làm hostname nhờ mạng nội bộ của Docker Compose) hoặc cấu hình các setting khác.depends_on
: Chỉ định sự phụ thuộc giữa các dịch vụ. Docker Compose sẽ đảm bảo các dịch vụ trong danh sách này được khởi tạo trước dịch vụ hiện tại.services: backend: build: ./backend depends_on: - database # Đảm bảo dịch vụ 'database' khởi động trước 'backend' - cache # Đảm bảo dịch vụ 'cache' khởi động trước 'backend'
Giải thích: Compose sẽ chờ cho container của dịch vụ
database
vàcache
bắt đầu chạy (ở trạng tháistarted
, không nhất thiết làhealthy
trừ khi bạn cấu hìnhhealthcheck
) trước khi khởi động containerbackend
. Điều này giúp tránh lỗi kết nối khi dịch vụbackend
cố gắng truy cập database hoặc cache khi chúng chưa sẵn sàng.networks
: Gán dịch vụ vào một hoặc nhiều mạng đã được định nghĩa. Mặc định, Compose tạo một mạng bridge cho tất cả các dịch vụ, cho phép chúng giao tiếp với nhau bằng tên dịch vụ. Tuy nhiên, việc định nghĩa mạng tùy chỉnh giúp kiểm soát tốt hơn luồng traffic và cô lập các dịch vụ.services: frontend: build: ./frontend networks: - app-network # Tham gia mạng 'app-network' backend: build: ./backend networks: - app-network # Tham gia mạng 'app-network' database: image: postgres:latest networks: - app-network # Tham gia mạng 'app-network' networks: app-network: # Định nghĩa mạng tên 'app-network' driver: bridge
Giải thích: Tất cả ba dịch vụ (
frontend
,backend
,database
) đều được kết nối vào cùng một mạng tùy chỉnhapp-network
. Điều này cho phép chúng "nhìn thấy" nhau bằng tên dịch vụ (ví dụ,backend
có thể kết nối tớidatabase
bằng hostnamedatabase
).
volumes
(Top-level)
Phần này định nghĩa các named volumes mà bạn muốn sử dụng. Named volumes là cách được khuyến khích để lưu trữ dữ liệu bền vững trong Docker vì chúng được quản lý bởi Docker và độc lập với chu kỳ sống của container.
version: '3.8'
services:
# ... các dịch vụ sử dụng volume ...
database:
image: postgres:latest
volumes:
- db_data:/var/lib/postgresql/data # Sử dụng named volume 'db_data'
volumes: # Định nghĩa named volumes
db_data: # Tên của volume
driver: local # Có thể tuỳ chọn driver (mặc định là local)
Giải thích: Khối volumes
ở cấp cao nhất định nghĩa một named volume có tên là db_data
. Volume này sau đó được tham chiếu trong định nghĩa dịch vụ database
để lưu trữ dữ liệu.
networks
(Top-level)
Tương tự như volumes
, bạn có thể định nghĩa các mạng tùy chỉnh ở đây. Mạng tùy chỉnh giúp các container trong ứng dụng của bạn giao tiếp với nhau một cách an toàn và dễ dàng bằng tên dịch vụ, đồng thời cô lập chúng khỏi các container không liên quan khác trên cùng một máy host.
version: '3.8'
services:
# ... các dịch vụ sử dụng mạng ...
frontend:
networks:
- app-network
backend:
networks:
- app-network
networks: # Định nghĩa mạng tùy chỉnh
app-network: # Tên mạng
driver: bridge # Loại mạng (bridge là phổ biến nhất)
Giải thích: Khối networks
ở cấp cao nhất định nghĩa một mạng bridge tùy chỉnh có tên app-network
. Các dịch vụ frontend
và backend
được kết nối vào mạng này, cho phép chúng giao tiếp nội bộ.
Ví dụ tổng hợp: Ứng dụng Web Full-stack đơn giản
Hãy ghép nối các phần lại để tạo một tệp docker-compose.yml
cho một ứng dụng web đơn giản gồm 3 dịch vụ:
- Frontend: Ứng dụng React/Vue/Angular được build tĩnh hoặc chạy bởi Node.js dev server. (Giả sử dùng Nginx phục vụ tĩnh cho đơn giản).
- Backend: API server (ví dụ: Node.js/Express).
- Database: PostgreSQL.
Cấu trúc thư mục giả định:
.
├── docker-compose.yml
├── frontend/
│ ├── Dockerfile
│ └── index.html (hoặc code frontend)
└── backend/
├── Dockerfile
└── index.js (hoặc code backend)
./frontend/Dockerfile
(Ví dụ đơn giản phục vụ HTML tĩnh bằng Nginx):
FROM nginx:alpine
COPY . /usr/share/nginx/html
./backend/Dockerfile
(Ví dụ đơn giản Node.js):
FROM node:lts-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "index.js"]
Và đây là tệp docker-compose.yml
:
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "80:80" # Ánh xạ cổng 80 của host tới cổng 80 của container (Nginx mặc định)
networks:
- app-network # Tham gia mạng nội bộ
backend:
build: ./backend
ports:
- "5000:5000" # Ánh xạ cổng 5000 của host tới cổng 5000 của container (nơi backend lắng nghe)
environment:
- DATABASE_URL=postgresql://myuser:mypassword@database:5432/mydb # Kết nối tới database
depends_on:
- database # Đảm bảo database khởi động trước
volumes:
- ./backend:/app # Mount code backend cho phát triển (nếu muốn hot-reloading)
- /app/node_modules # Volume rỗng cho node_modules (xem giải thích ở trên)
networks:
- app-network # Tham gia mạng nội bộ
database:
image: postgres:latest # Sử dụng image Postgres
ports:
- "5432:5432" # Ánh xạ cổng 5432 của host tới cổng 5432 của container (tùy chọn, chỉ dùng khi cần truy cập DB từ host)
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
volumes:
- postgres_data:/var/lib/postgresql/data # Lưu trữ dữ liệu DB bền vững
networks:
- app-network # Tham gia mạng nội bộ
volumes: # Định nghĩa named volume cho dữ liệu database
postgres_data:
networks: # Định nghĩa mạng nội bộ
app-network:
driver: bridge
Giải thích tổng thể ví dụ:
- Tệp này định nghĩa 3 dịch vụ (
frontend
,backend
,database
). frontend
vàbackend
được build từ Dockerfile trong các thư mục tương ứng.database
sử dụng image có sẵn.- Cổng 80 của host được nối tới cổng 80 của container
frontend
, cho phép truy cập ứng dụng quahttp://localhost
. - Cổng 5000 của host được nối tới cổng 5000 của container
backend
(cho phép truy cập API trực tiếp nếu cần, hoặc chỉ đểfrontend
truy cập nội bộ qua mạng Docker). - Dịch vụ
backend
phụ thuộc vàodatabase
, đảm bảo database sẵn sàng trước. - Dịch vụ
backend
được truyền thông tin kết nối database qua biến môi trườngDATABASE_URL
. Lưu ý hostnamedatabase
được sử dụng, đây là tên dịch vụ trong mạng nội bộ của Docker Compose. - Dữ liệu của PostgreSQL được lưu vào named volume
postgres_data
để không bị mất khi container database dừng/khởi động lại. - Tất cả các dịch vụ đều nằm trong mạng nội bộ
app-network
, cho phép chúng giao tiếp với nhau một cách an toàn bằng tên dịch vụ (ví dụbackend
dùng hostnamedatabase
để nối tới database). - Trong dịch vụ
backend
, có sử dụng bind mount./backend:/app
và volume rỗng/app/node_modules
. Cấu hình này rất phổ biến cho môi trường phát triển Node.js: code từ host được mount vào/app
, còn thư mụcnode_modules
sẽ sử dụng các gói được cài đặt bên trong container, tương thích với môi trường container.
Các lệnh Docker Compose cơ bản
Sau khi có tệp docker-compose.yml
, bạn sử dụng lệnh docker-compose
để tương tác với ứng dụng của mình.
docker-compose up
: Khởi tạo và chạy toàn bộ ứng dụng (tất cả các dịch vụ được định nghĩa trong tệp). Nếu các image chưa tồn tại, Compose sẽ build (nếu cóbuild
) hoặc pull (nếu cóimage
).docker-compose up # Chạy ở chế độ foreground (hiển thị logs) docker-compose up -d # Chạy ở chế độ detached (nền) docker-compose up --build # Buộc build lại image trước khi chạy
Giải thích: Lệnh này là lệnh phổ biến nhất. Nó đọc tệp
docker-compose.yml
, tạo mạng, volumes (nếu chưa có) và khởi tạo các container theo đúng thứ tự phụ thuộc.docker-compose down
: Dừng và xóa các container, mạng và volumes (trừ các named volumes) được tạo bởiup
.docker-compose down # Dừng và xóa container, mạng docker-compose down --volumes # Dừng, xóa container, mạng VÀ named volumes
Giải thích: Lệnh này giúp "dọn dẹp" môi trường ứng dụng của bạn. Sử dụng
--volumes
khi bạn muốn xóa cả dữ liệu bền vững (cẩn thận khi dùng trong môi trường có dữ liệu quan trọng!).docker-compose ps
: Liệt kê trạng thái của các dịch vụ (container) trong ứng dụng.docker-compose ps
Giải thích: Cho biết container nào đang chạy, cổng nào đang được ánh xạ, và trạng thái hiện tại.
docker-compose logs [service_name]
: Xem log đầu ra của một hoặc tất cả các dịch vụ.docker-compose logs frontend # Xem log của dịch vụ frontend docker-compose logs # Xem log của tất cả các dịch vụ
Giải thích: Hữu ích để gỡ lỗi và kiểm tra hoạt động của ứng dụng.
docker-compose exec [service_name] [command]
: Thực thi một lệnh bên trong một container đang chạy của một dịch vụ cụ thể.docker-compose exec backend bash # Mở shell trong container backend docker-compose exec database psql -U myuser mydb # Chạy lệnh psql để kết nối DB trong container database
Giải thích: Cho phép bạn tương tác trực tiếp với môi trường bên trong container để kiểm tra, gỡ lỗi hoặc chạy các tác vụ thủ công.
docker-compose build [service_name]
: Build lại image cho một hoặc tất cả các dịch vụ có sử dụngbuild
.docker-compose build backend # Chỉ build lại image cho dịch vụ backend docker-compose build # Build lại image cho tất cả dịch vụ có 'build'
Giải thích: Sử dụng khi bạn đã thay đổi Dockerfile hoặc nội dung cần build lại image.
Tại sao Docker Compose đặc biệt hữu ích cho Lập trình Web?
- Thiết lập môi trường phát triển nhất quán: Toàn bộ team có thể chạy môi trường phát triển giống hệt nhau chỉ với
docker-compose up
. Không còn "Nó chạy được trên máy tôi!". - Quản lý dịch vụ dễ dàng: Khởi động, dừng, khởi động lại toàn bộ ứng dụng đa dịch vụ chỉ với một vài lệnh.
- Tách biệt môi trường: Dễ dàng định nghĩa các cấu hình khác nhau cho môi trường phát triển, staging, production bằng cách sử dụng nhiều tệp Compose và tính năng
extends
hoặc profile. - Tích hợp CI/CD: Docker Compose là nền tảng tuyệt vời để chạy các bài kiểm thử tích hợp trong môi trường giống với production.
- Hot-reloading hiệu quả: Với
volumes
dạng bind mount, thay đổi code trên host sẽ được cập nhật trong container, giúp tăng tốc độ phát triển với các framework hỗ trợ hot-reloading. - Cấu hình tập trung: Tất cả cấu hình cho ứng dụng của bạn nằm gọn trong một tệp, dễ dàng quản lý và versioning.
Sử dụng Docker Compose không chỉ giúp bạn quản lý các ứng dụng phức tạp dễ dàng hơn mà còn là một kỹ năng quan trọng trong thế giới DevOps hiện đại. Nắm vững cách định cấu hình tệp docker-compose.yml
sẽ mở ra rất nhiều khả năng trong việc xây dựng, thử nghiệm và triển khai các ứng dụng web. Hãy thử áp dụng nó vào dự án tiếp theo của bạn!
Comments