SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-28

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ự dùng

Docker Compose cung cấp hơn 40 subcommand. Công việc hằng ngày trên server thường chỉ dùng khoảng một tá lệnh. Trang này nhóm các lệnh theo công việc bạn đang thực hiện, nêu một lý do rõ ràng cho từng lệnh và dẫn đến phần chuyên sâu khi lệnh đó có điểm dễ gây lỗi.

Mọi nội dung ở đây đều dùng Compose V2: docker compose có một khoảng trắng, không phải script docker-compose cũ. V2 là một plugin Go được cài cùng Docker Engine. V1 đã bị loại khỏi các package hiện tại, nên docker-compose: command not found trên một máy Ubuntu mới cài vào tháng 7 năm 2026 là hành vi bình thường, không phải lỗi. Kiểm tra bằng docker compose version. Nếu lệnh không in ra gì, hãy cài package docker-compose-plugin.

Mọi lệnh bên dưới đều chạy từ thư mục chứa compose.yaml, vì Compose lấy project name từ thư mục đó và tìm file theo đường dẫn tương đối với thư mục này. Nếu chạy cùng lệnh ở thư mục cha một cấp, Compose sẽ dừng với no configuration file provided: not found. Nếu định dạng file còn mới với bạn, hãy bắt đầu từ file Compose đầu tiên trên VPS, rồi quay lại đây để xem các lệnh.

Vòng đời: 4 lệnh bạ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 rồi trả về. Lệnh này trả về ngay khi các container vừa được tạo, vì vậy script deploy chạy tiếp bằng probe curl thường thất bại ở lần thử đầu tiên. up -d --wait chờ cho đến khi mọi service khai báo healthcheck đều báo healthy, và thoát với mã khác 0 nếu có service không bao giờ đạt trạng thái đó. Flag này chỉ đáng tin cậy khi healthcheck phía sau được viết đúng, vì vậy hãy viết healthcheck mà Compose có thể tin cậy trước khi dùng nó trong automation.

stop dừng các container nhưng giữ lại chúng, vì vậy start khởi động lại đúng các container đó với cùng writable layer. down dừng các container rồi xóa chúng cùng network của project. Mọi dữ liệu được ghi bên trong container nhưng nằm ngoài volume đều bị xóa theo. Đây là hiểu nhầm gây hậu quả lớn nhất khi dùng Compose, và toàn bộ khác biệt giữa down và stop giải thích rõ vấn đề này.

restart không phải là thao tác reload. Lệnh này dừng rồi khởi động lại cùng container với cấu hình hiện có, nên biến môi trường đã thay đổi, image tag mới hoặc port mapping đã chỉnh sửa đều hoàn toàn không có hiệu lực. Để áp dụng thay đổi trong file, bạn chạy lại up -d. Compose so sánh từng service với container đang chạy và chỉ tạo lại những service có cấu hình 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ự nó không làm gì nếu không có thay đổi. Vì vậy, bạn có thể chạy lặp lại lệnh này một cách an toàn. --force-recreate bỏ qua phép so sánh đó và thay thế mọi container ngay cả khi cấu hình giống hệt nhau. Đây là cách nhanh nhất để xóa trạng thái bất thường bên trong container.

Cập nhật một image cần 2 command vì chúng thực hiện 2 việc khác nhau. pull tải image hiện tại cho từng tag được khai báo trong file. Sau đó, up -d phát hiện ID của image trong service không còn khớp với container đang chạy và tạo lại container đó. Bỏ qua bước pull thì up -d vẫn giữ latest của tháng trước đang chạy mà không báo lỗi. Rủi ro ngược lại xuất hiện với stack nhiều service: pull latest cho tất cả service cùng lúc có thể làm hỏng một app vừa hoạt động bình thường 10 giây trước. Vì vậy, một workspace AFFiNE tự host pin riêng từng tag trong 4 image. Pinning cũng biến việc upgrade thành một chỉnh sửa có chủ đích đối với tag, sau đó chạy lại chính quy trình pull và tạo lại container. Với stack thực hiện migration database khi khởi động, bạn nên có sẵn một bản dump trước khi chạy một trong hai command. Đây là quy trình mà một help desk Chatwoot tự host áp dụng cho mỗi lần nâng version.

build áp dụng cho các service khai báo phần build: thay vì image:. up -d --build build và start trong một bước. Đây là vòng lặp thông thường khi bạn đang thay đổi code. Chỉ dùng --no-cache khi chắc chắn một cached layer đã cũ, vì lệnh này rebuild mọi layer từ đầu. Khi stack được deploy từ một git tag đã checkout thay vì từ image trong registry, vòng lặp build đó cũng chính là quy trình cập nhật. Đây là cách trình theo dõi bài tập openGym tự host chuyển từ một phiên bản đã pin sang phiên bản tiếp theo.

Xem những gì đ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 xuất hiện ở đó cho đến khi bạn thêm -a. Vì vậy, một container không có trong ps nhưng ps -a lại hiển thị container đó ở trạng thái Exited (1) là dạng lỗi khởi động thường gặp. Hãy đọc exit code, sau đó đọc log.

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

top liệt kê các process bên trong từng container. Lệnh này giúp phân biệt “container đang chạy” với “process bên trong container đang chạy”. ls chuyển ra ngoài thư mục hiện tại và liệt kê mọi Compose project trên host cùng trạng thái của chúng, để bạn tìm được stack đã khởi động từ ba tháng trước.

Mở 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 command bên trong container đang hoạt động. run khởi động một container mới từ cùng service definition; bạn cần cách này khi service không chạy đủ lâu để exec vào. Luôn dùng run cùng với --rm, vì nếu thiếu tùy chọn này, mỗi lần gọi sẽ để lại một container đã dừng. Các container đó sẽ tích tụ cho đến khi docker compose ps -a không còn đọc được.

Hãy thử sh trước bash. Các image dựa trên Alpine không có bash, nên lỗi 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, nhờ đó việc kiểm tra nhanh cấu hình 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 mà service thực sự nhận được, sau khi mọi file .env, block environment: và biến shell đã được gộp. Khi một giá trị sai, nguyên nhân thường là thứ tự gộp. cách Compose phân giải env file và secret giải thích source nào được ưu tiên.

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

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

Compose đặt mọi service vào một project network duy nhất, và tên của mỗi service là một DNS name trên network đó. Chạy getent hosts db bên trong web sẽ in ra IP của container nếu phân giải thành công và không in gì nếu phân giải thất bại, nên bạn có thể trả lời câu hỏi “các container này có nhìn thấy nhau không” trong 2 giây. Nếu tên được phân giải nhưng kết nối bị từ chối, process bên trong db đang bind vào 127.0.0.1 thay vì 0.0.0.0, nên nó không bao giờ nhận packet từ container khác. Cùng một ranh giới đó giải thích vì sao một container được khởi chạy bên ngoài project, dù bằng docker run hay dưới dạng stack riêng, hoàn toàn không thể phân giải tên như jellyfin. Đây là điều đầu tiên cần kiểm tra khi front end Halcyon cho thư viện Jellyfin của bạn không thể kết nối đến server mà nó được cấu hình trỏ tới. Phần còn lại của mô hình này được trình bày trong cách Compose network và DNS của service hoạt động.

port web 80 in ra địa chỉ host và port mà một port của container được publish, giúp không phải đoán khi mapping lấy từ một biến. Việc publish port cũng tạo một firewall rule do Docker tự quản lý. Rule đó được áp dụng trước rule của bạn, nên một service mà bạn nghĩ là private có thể bị mở ra Internet. Trường hợp này được trình bày trong vì sao port Docker được publish bypass ufw. Để các port đó không được publish và đặt một proxy có xác thực duy nhất trên project network ở phía trước các service là mô hình an toàn hơn. Đó là mô hình mà chạy Authentik làm lớp single sign-on cung cấp.

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 in các named volume mà project khai báo, mỗi volume trên một dòng. Danh sách đó chính là những gì bạn phải backup. Khi các volume chứa dữ liệu không thể thay thế, lệnh backup chính xác quan trọng không kém danh sách này. Vì vậy, phần so sánh PhotoPrism và Immich nêu rõ các lệnh dump và copy mà từng photo server cần. cp copy một file vào hoặc ra khỏi container mà không cần mở shell, bằng cách dùng dạng service:path ở phía nào là container.

down -v xóa các named volume đó cùng với các container. Đây là lệnh phù hợp để dỡ một test stack và là lệnh không nên dùng cho bất kỳ stack nào chứa dữ liệu bạn cần giữ, vì lệnh không yêu cầu xác nhận và không thể undo. Bind mount không bị ảnh hưởng, vì chúng nằm trên filesystem của host. Khác biệt về blast radius này là một lý do để lựa chọn có chủ đích giữa bind mount và named volume.

Dọn dẹp để giải phóng disk 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 project nhưng không còn xuất hiện trong file. Đây chính xác là tình huống xảy ra sau khi bạn đổi tên service. Nếu không xóa, các container đó vẫn tiếp tục chạy và không hiển thị trong docker compose ps.

docker system df cho biết disk đang được sử dụng ở đâu trước khi bạn xóa bất kỳ thứ gì. Lệnh này tách riêng image, container, local volume và build cache, đồng thời hiển thị dung lượng có thể reclaim cho từng loại. image prune -a xóa mọi image không còn tag nào trỏ tới. Trên máy chủ đã pull nhiều version của một image lớn, đây thường là cách giải phóng được nhiều disk nhất. builder prune xóa build cache. Cache này âm thầm tăng trên mọi server tự build image.

Không lệnh nào trong số đó tác động đến named volume. Chỉ docker volume prunedocker compose down -v mới làm việc đó.

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à project

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 được gộp theo thứ tự, và các file đứng sau sẽ ghi đè từng key của các file đứng trước. Đây là cách tiêu chuẩn để giữ một file cơ sở kèm một file override nhỏ cho production. Tuy nhiên, quy tắc gộp list và map khác nhau, nên hãy đọc cách Compose gộp nhiều file trước khi debug một kết quả bất ngờ.

--profile khởi động các service được gắn profile đó cùng với các service không gắn profile, nhờ vậy các công cụ debug không xuất hiện trong một up thông thường. -p đặt tên project, vì vậy bạn có thể chạy song song 2 bản sao của cùng một stack với network và tên volume riêng. Để stack tự khởi động lại sau reboot, bạn không cần gõ lệnh thủ công. Thay vào đó, một unit sẽ chạy lệnh này cho bạn, như mô tả trong khởi động Compose stack khi boot.

FAQ

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

Compose V2, được gọi bằng docker compose với một dấu cách. Đây là plugin đi kèm Docker Engine, còn công cụ Python V1 không còn được các package hiện tại cài đặt. Nếu dạng có dấu cách không in ra gì, hãy cài package docker-compose-plugin cho bản phân phối của bạn. Cập nhật các script cũ sang dạng có dấu cách thay vì thêm alias, vì V2 có những flag mà V1 không có.

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

restart dừng rồi khởi động lại container hiện có với cấu hình được dùng khi tạo container, và không đọc lại compose.yaml. Mọi thay đổi đối với biến môi trường, port, volume hoặc image tag đều cần docker compose up -d. Lệnh này so sánh từng service với container đang chạy và tạo lại những container có khác biệt. Thêm --force-recreate nếu muốn luôn thay thế container, ngay cả khi file không có 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 đó chạy docker compose up -d. Lệnh pull tải image hiện tại cho từng tag trong file, còn up -d tạo lại mọi service có image ID không còn khớp với container của service đó. Tự chạy up -d sẽ dùng lại image đã có trên disk. Vì vậy, một stack được ghim vào latest có thể tiếp tục chạy trên bản build đã cũ nhiều tháng mà không in ra lỗi nào.

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

docker system df, docker image prune -adocker builder prune chỉ xóa image và cache, nên các service đang chạy vẫn hoạt động và named volume không bị ảnh hưởng. Cặp lệnh nguy hiểm là docker compose down -vdocker volume prune, vì chúng xóa named volume mà không hỏi xác nhận. Trước tiên hãy chạy docker compose config --volumes để biết những gì có nguy cơ bị xóa.

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

Có. docker compose run --rm --no-deps web sh khởi động một container duy nhất từ định nghĩa service web, bỏ qua các dependency của service đó và xóa container khi bạn thoát. Dùng exec thay thế nếu container đang chạy, vì exec kết nối vào tiến trình đang hoạt động và hiển thị đúng trạng thái hiện tại của service.