SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

Cheat sheet Docker Compose cho server thực tế

Tổng hợp lệnh Docker Compose V2 dùng hằng ngày trên server: khởi động, áp dụng thay đổi, xem log, vào shell, quản lý network, volume và dọn dẹp an toàn.

Các lệnh Compose bạn thực sự cần dùng

Docker Compose có hàng chục subcommand. Công việc hàng ngày trên máy chủ chỉ dùng khoảng một chục lệnh. Trang này nhóm các lệnh đó theo tác vụ bạn đang thực hiện, đưa ra một lý do đơn giản cho mỗi lệnh và dẫn đến phần giải thích chi tiết khi một lệnh tiềm ẩn rủi ro.

Mọi nội dung ở đây đều sử dụng Compose V2 hoặc V5: docker compose với dấu cách, không phải script docker-compose cũ. Cả hai đều là plugin Go; trên Linux, Compose plugin được cài đặt tách biệt với Docker Engine. Ubuntu 24.04 LTS vẫn cung cấp công cụ Python V1 cũ dưới dạng docker-compose phiên bản 1.29.2-6ubuntu1 trong kho lưu trữ universe. Kiểm tra bằng docker compose version. Nếu lệnh không khả dụng, hãy thiết lập kho lưu trữ của Docker và cài đặt gói docker-compose-plugin.

Mọi lệnh dưới đây đều chạy từ thư mục chứa file compose.yaml của bạn; Compose sử dụng tên thư mục đó làm tên dự án mặc định trừ khi bạn ghi đè nó. Nếu không có -f hoặc COMPOSE_FILE, Compose sẽ tìm kiếm file cấu hình trong thư mục làm việc hiện tại và các thư mục cha, vì vậy việc chạy lệnh ở cấp thư mục cao hơn chỉ tìm thấy stack của bạn nếu file vẫn nằm trên đường dẫn tìm kiếm đó. Nếu định dạng file còn mới với bạn, hãy bắt đầu với file Compose đầu tiên trên VPS rồi quay lại đây để xem các lệnh.

Vòng đời: bốn lệnh bạn cần dùng và lệnh xóa container

docker compose up -d
docker compose up -d --wait
docker compose stop
docker compose start
docker compose restart web
docker compose down

up -d tạo network, tạo các container, khởi động chúng và trả về kết quả. Nó trả về ngay sau khi khởi động container mà không đợi ứng dụng sẵn sàng, đó là lý do tại sao script deploy chạy ngay sau đó bằng một probe curl thường thất bại ở lần thử đầu tiên. up -d --wait đợi các service có healthcheck chuyển sang trạng thái healthy và các service không có healthcheck chuyển sang trạng thái running; một service không healthy hoặc một --wait-timeout quá hạn sẽ khiến lệnh trả về mã lỗi khác 0. Flag này chỉ hiệu quả khi healthcheck được thiết lập đúng, vì vậy hãy viết một healthcheck mà Compose có thể tin tưởng trước khi dựa vào nó trong tự động hóa.

stop dừng các container và giữ lại chúng, vì vậy start sẽ khởi động lại chính các container đó với lớp ghi (writable layer) cũ. down dừng và sau đó xóa các container cùng với network của dự án. Mọi dữ liệu được ghi bên trong container mà không nằm trong volume sẽ bị mất. Đây là hiểu lầm đắt giá nhất trong Compose, và sự khác biệt đầy đủ giữa down và stop giải thích rõ nơi nó gây ra rắc rối.

restart không phải là lệnh reload. Nó dừng và khởi động lại chính container đó với cấu hình hiện tại, vì vậy một biến môi trường thay đổi, một image tag mới hoặc một port mapping được chỉnh sửa sẽ không có tác dụng gì cả. Để áp dụng thay đổi file, bạn phải chạy lại up -d. Compose so sánh từng service với container đang chạy và tạo lại những container có cấu hình hoặc image đã thay đổi.

Áp dụng thay đổi: recreate, pull hoặc rebuild

docker compose up -d --force-recreate
docker compose pull && docker compose up -d
docker compose build --no-cache web
docker compose up -d --build web

up -d tái sử dụng các container đang chạy khi cấu hình và image của chúng không thay đổi; việc pull và build image tuân theo chính sách pull của service. --force-recreate ghi đè so sánh đó và thay thế mọi container ngay cả khi cấu hình giống hệt nhau, vì vậy đây là cách nhanh nhất để xóa trạng thái bất thường bên trong container.

Quy trình cập nhật image tường minh sử dụng hai lệnh vì chúng thực hiện hai việc khác nhau. pull tải xuống image hiện tại cho mỗi tag được nêu trong file. Sau đó up -d nhận thấy ID image của service không còn khớp với container đang chạy và thực hiện recreate. Với chính sách pull mặc định là missing, các tag trong cache sẽ được tái sử dụng ngoại trừ latest, vốn luôn luôn được pull. Rủi ro ngược lại xuất hiện trên một stack đa dịch vụ, nơi việc pull latest cho mọi service cùng lúc có thể làm hỏng một ứng dụng đang hoạt động tốt mười giây trước đó, đó là lý do tại sao một workspace AFFiNE tự host khuyến nghị ghim (pin) ứng dụng và image migration vào cùng một digest. Việc ghim cũng biến quá trình nâng cấp thành một thao tác chỉnh sửa tag có chủ đích, theo sau là lệnh pull và recreate tương tự, và trên một stack thực hiện migrate database khi khởi động, bạn cần có sẵn một bản dump trước khi chạy bất kỳ lệnh nào, đây là quy trình mà một hệ thống hỗ trợ Chatwoot tự host tuân thủ cho mỗi lần tăng phiên bản.

build áp dụng cho các service khai báo phần build:, bao gồm cả các service có khai báo thêm image:. up -d --build thực hiện build và khởi động trong một bước, đây là vòng lặp thông thường khi bạn đang thay đổi code. Chỉ sử dụng --no-cache khi một layer trong cache rõ ràng đã cũ, vì nó sẽ build lại mọi layer từ đầu. Một số dự án cung cấp cả hai lộ trình từ một repo đã clone, và khi đó hai vòng lặp này là các đường dẫn cập nhật thay thế: một trình theo dõi tập luyện openGym tự host sử dụng docker compose up -d --build để build bản checkout đã ghim của nó, vì việc pull các image latest có thể chạy một bản release mới hơn so với bản checkout đó.

Kiểm tra các tiến trình đang chạy

docker compose ps
docker compose ps -a
docker compose logs -f --tail=100
docker compose logs --since 15m --timestamps db
docker compose top
docker compose ls

ps chỉ liệt kê các container đang chạy. Một service bị crash trong lúc khởi động sẽ không hiển thị ở đó trừ khi bạn thêm -a, vì vậy việc một container không xuất hiện trong ps trong khi ps -a hiển thị nó ở trạng thái Exited (1) là dấu hiệu bình thường của lỗi khởi động. Hãy đọc mã exit, sau đó đọc log.

logs -f theo dõi tất cả các service cùng lúc và thêm tên service vào đầu mỗi dòng, đây là chế độ xem bạn cần khi các service tương tác với nhau và thứ tự sự kiện là quan trọng. Hãy chỉ định tên service để thu hẹp phạm vi. --tail=100 rất quan trọng đối với một container đã chạy được một tháng, vì mặc định nó sẽ in toàn bộ lịch sử và làm tràn terminal. --since 15m trả lời câu hỏi thường gặp của bạn, đó là chuyện gì đã xảy ra trong lần restart vừa thực hiện.

top liệt kê các tiến trình bên trong mỗi container, giúp phân biệt giữa "container đang chạy" và "tiến trình bên trong nó đang chạy". ls thoát ra khỏi thư mục hiện tại và liệt kê các dự án Compose đang chạy trên host cùng với trạng thái của chúng; thêm -a để bao gồm cả các dự án đã dừng.

Truy cập shell bên trong một service

docker compose exec web sh
docker compose exec -u root web sh
docker compose run --rm web env
docker compose run --rm --no-deps web sh

exec chạy một lệnh bên trong container đang hoạt động. run khởi chạy một container mới từ cùng định nghĩa service, đây là cách bạn cần dùng khi service không chạy đủ lâu để thực hiện lệnh exec. Luôn kết hợp run với --rm, vì nếu thiếu nó, mỗi lần gọi lệnh sẽ để lại một container đã dừng, và chúng sẽ tích tụ cho đến khi docker compose ps -a trở nên không thể đọc được.

Hãy thử sh trước bash. Một số image dựa trên Alpine bao gồm bash, kể cả image PostgreSQL Alpine chính thức; khi nó không có sẵn, lỗi sẽ hiển thị là exec: "bash": executable file not found in $PATH. Thêm --no-deps vào run sẽ bỏ qua các dependency của service, giúp việc kiểm tra cấu hình nhanh không khởi động toàn bộ database của bạn.

run --rm web env là cách nhanh nhất để xem môi trường thực tế mà một service nhận được, sau khi mọi file .env, khối environment: và biến shell đã được gộp lại. Khi một giá trị bị sai, nguyên nhân thường nằm ở thứ tự gộp, và cách Compose phân giải các file env và secret sẽ chỉ ra nguồn nào được ưu tiên.

Mạng, cổng và phân giải tên miền

docker compose port web 80
docker compose exec web getent hosts db
docker compose config --networks

Mặc định, Compose đặt mọi service vào một mạng dự án, và mỗi tên service đóng vai trò là một tên DNS trong mạng đó. Chạy getent hosts db bên trong web, khi image có chứa getent, sẽ kiểm tra việc phân giải tên miền; nó không kiểm tra xem db có chấp nhận kết nối hay không. Nếu tên miền phân giải được nhưng kết nối bị từ chối, hãy kiểm tra xem server có đang chạy và lắng nghe trên địa chỉ và cổng được yêu cầu hay không; việc chỉ bind vào 127.0.0.1 là một nguyên nhân phổ biến. Ranh giới tương tự giải thích lý do tại sao một container khởi chạy bên ngoài dự án, dù là bởi docker run hay dưới dạng stack riêng, đều cần một mạng chung để phân giải tên service như jellyfin, đây là điều đầu tiên cần kiểm tra khi một front end Halcyon cho thư viện Jellyfin của bạn không thể kết nối tới server mà nó trỏ tới. Phần còn lại của mô hình này nằm trong cách thức hoạt động của mạng Compose và DNS service.

port web 80 in ra địa chỉ host và cổng mà cổng của container được publish, giúp bạn không phải đoán khi mapping đến từ một biến. Việc publish một cổng cũng ghi một rule firewall mà Docker tự quản lý, và rule đó nằm trước rule của bạn, vì vậy một service mà bạn nghĩ là riêng tư có thể bị mở ra internet. Trường hợp đó được đề cập trong lý do các cổng Docker được publish lại bỏ qua ufw. Việc để các cổng đó không publish và thay vào đó đặt một proxy xác thực trên mạng dự án phía trước các service là cách làm an toàn hơn, đó là những gì chạy Authentik như một lớp đăng nhập một lần mang lại cho bạn.

Volumes và dữ liệu

docker compose config --volumes
docker compose cp db:/etc/postgresql/pg_hba.conf ./pg_hba.conf
docker compose down -v

config --volumes liệt kê các named volumes mà dự án khai báo, mỗi volume trên một dòng. Danh sách đó chỉ là một phần của danh mục sao lưu: bind mounts và các file cấu hình cũng cần được kiểm tra. Khi các volume chứa dữ liệu không thể thay thế, lệnh sao lưu chính xác quan trọng ngang với danh sách, đó là lý do tại sao bài so sánh PhotoPrism và Immich nêu rõ các lệnh dump và copy mà mỗi máy chủ ảnh cần. cp sao chép một file vào hoặc ra khỏi container mà không cần mở shell, sử dụng định dạng service:path trên phía là container.

down -v xóa các named volumes đã khai báo ngoại trừ các volume bên ngoài, cộng với các anonymous volumes gắn với container, cùng với chính các container đó. Đây là lệnh phù hợp để gỡ bỏ một stack thử nghiệm và là lệnh sai lầm cho bất kỳ thứ gì chứa dữ liệu quan trọng, vì không có xác nhận và không thể hoàn tác. Bind mounts vẫn tồn tại sau lệnh này vì chúng nằm trên filesystem của host. Khoảng cách về phạm vi ảnh hưởng đó là một lý do để lựa chọn cẩn thận giữa bind mounts và named volumes.

Dọn dẹp giải phóng dung lượng đĩa mà không mất dữ liệu

docker compose down --remove-orphans
docker system df
docker image prune -a
docker builder prune

--remove-orphans xóa các container thuộc dự án nhưng không còn xuất hiện trong file cấu hình, đây chính là kết quả sau khi bạn đổi tên một service. Nếu không có lệnh này, các container đó vẫn tiếp tục chạy, và docker compose ps mặc định bao gồm cả các container mồ côi đang chạy.

docker system df hiển thị nơi dung lượng đĩa đang bị chiếm dụng trước khi bạn xóa bất cứ thứ gì, phân loại rõ image, container, local volume và build cache cùng với dung lượng có thể thu hồi cho từng mục. image prune -a xóa mọi image không được container nào sử dụng, bất kể có tag hay không; trên một máy chủ đã pull nhiều phiên bản của một image lớn, đây thường là cách hiệu quả nhất. builder prune mặc định xóa build cache treo; thêm -a để xóa toàn bộ build cache không sử dụng.

Không lệnh nào trong số này tác động đến named volume. docker volume prune --all, docker compose down -v và docker volume rm dùng để xóa named volume. Lệnh docker volume prune đơn thuần chỉ xóa các anonymous volume.

Kiểm tra file trước khi gây lỗi

docker compose config --quiet
docker compose config --services
docker compose --dry-run up -d

config --quiet kiểm tra tính hợp lệ và không in gì nếu thành công, nên phù hợp để chạy trong bước pre-deploy hoặc git hook. config thông thường in ra file sau khi đã merge và nội suy đầy đủ. Đây là cách xác nhận biến đã được resolve và file override được áp dụng theo đúng thứ tự mong đợi. Biến chưa được set sẽ xuất hiện dưới dạng giá trị rỗng, cùng với cảnh báo The "X" variable is not set. Defaulting to a blank string.

--dry-run là global flag, không phải subcommand flag, nên phải đặt trước up. Lệnh này in ra mọi thao tác Compose sẽ thực hiện nhưng không thay đổi gì. Chạy kiểm tra này trước down trên stack quan trọng là 30 giây đáng bỏ ra.

Làm việc với nhiều file, profile và dự án

docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose --profile debug up -d
docker compose -p staging up -d

Nhiều flag -f sẽ được gộp theo thứ tự, và các file sau sẽ ghi đè lên các file trước theo từng key. Đây là cách tiêu chuẩn để duy trì một file cơ sở với một file ghi đè nhỏ cho môi trường production, tuy nhiên các quy tắc đối với danh sách và map sẽ khác nhau, vì vậy hãy đọc cách Compose gộp nhiều file trước khi bạn debug một kết quả không mong muốn.

--profile khởi chạy các service được gắn tag với profile đó cùng với các service không gắn tag, giúp giữ cho các công cụ debug không xuất hiện trong một up thông thường. -p thiết lập tên dự án, nhờ đó hai bản sao của cùng một stack có thể chạy song song với các network và volume riêng biệt. Để khôi phục stack sau khi reboot, hãy sử dụng chính sách restart hoặc một unit chạy Compose thay cho bạn, được mô tả trong khởi chạy các stack Compose khi boot.

FAQ

Cái gì đã thay thế docker-compose bằng dấu gạch nối?

Compose V2 và V5, được gọi bằng docker compose với một khoảng trắng. Trên Linux, Compose là một plugin được cài đặt riêng; Ubuntu 24.04 LTS vẫn cung cấp công cụ Python V1 trong kho lưu trữ universe của nó. Nếu không dùng được dạng có khoảng trắng, hãy thiết lập kho lưu trữ của Docker và cài đặt gói docker-compose-plugin. Hãy cập nhật các script cũ sang dạng có khoảng trắng thay vì thêm alias, vì V2 có các flag mà V1 không có.

Tại sao docker compose restart không nhận thay đổi cấu hình của tôi?

restart dừng và khởi động lại container hiện có với cấu hình lúc nó được tạo, và nó không bao giờ đọc lại compose.yaml. Mọi thay đổi đối với biến môi trường, cổng, volume hoặc image tag đều cần docker compose up -d, lệnh này sẽ so sánh từng service với container đang chạy và tạo lại những service có sự khác biệt. Thêm --force-recreate khi bạn muốn việc thay thế diễn ra ngay cả khi không có gì trong file thay đổi.

Làm thế nào để cập nhật service lên image mới hơn?

Chạy docker compose pull, sau đó là docker compose up -d. Lệnh pull sẽ lấy image hiện tại cho từng tag trong file, và up -d sẽ tạo lại bất kỳ service nào có ID image không còn khớp với container của nó nữa. Chạy up -d riêng lẻ sẽ tuân theo chính sách pull: chính sách mặc định missing sẽ tái sử dụng các image đã cache, ngoại trừ latest, vốn luôn luôn được pull về.

Lệnh dọn dẹp nào an toàn trên server đang chạy?

docker system df báo cáo dung lượng đĩa sử dụng mà không xóa bất cứ thứ gì; docker image prune -a và docker builder prune xóa các image và build cache không sử dụng, vì vậy các service đang chạy vẫn hoạt động bình thường và các named volume không bị ảnh hưởng. Cặp lệnh nguy hiểm là docker compose down -v, lệnh này xóa các named volume không phải external đã khai báo trong project và các anonymous volume đính kèm mà không cần xác nhận, và docker volume prune --all, lệnh này xóa các local volume không sử dụng, bao gồm cả các named volume, sau một lần xác nhận y/N, hoặc không cần xác nhận nếu dùng -f. Hãy chạy docker compose config --volumes trước để biết những gì có nguy cơ bị xóa.

Tôi có thể chạy một lệnh mà không cần khởi động toàn bộ stack không?

Có. docker compose run --rm --no-deps web sh khởi động một container đơn lẻ từ định nghĩa service web, bỏ qua các dependency của nó và xóa container khi bạn thoát. Hãy dùng exec thay thế khi container đã chạy, vì exec chạy lệnh của bạn bên trong container đang hoạt động và cho bạn thấy trạng thái thực tế của service đó.

Bài viết này có hữu ích không?