# Cài Docker trên VPS Ubuntu, Debian kèm Docker Compose từ A đến Z

URL: https://cloudj.net/vi/blog/install-docker-vps
Published: 2026-11-11 · Updated: 2026-11-11

> Hướng dẫn cài Docker trên VPS Ubuntu hoặc Debian từ repository chính thức, thêm Docker Compose, chạy stack đầu tiên và tránh bẫy firewall giữa Docker và ufw.

Cách gọn gàng nhất để cài Docker trên VPS là dùng repository apt chính thức của Docker, thay vì gói `docker.io` của bản phân phối hay script cài đặt một dòng. Bạn sẽ có Docker Engine bản mới, plugin Compose và được cập nhật qua `apt upgrade` như mọi gói khác. Bài này hướng dẫn cài trên Ubuntu và Debian, chạy stack Compose đầu tiên, rồi đi vào phần mà nhiều hướng dẫn bỏ qua: mặc định các cổng container được publish sẽ đi vòng qua `ufw`, có thể làm lộ những dịch vụ bạn tưởng đã được firewall chặn.

Hãy bắt đầu từ một máy chủ đã được gia cố. Nếu máy còn mới tinh, hãy làm [checklist bảo mật VPS trong giờ đầu tiên](https://cloudj.net/vi/blog/secure-new-vps-checklist) trước, và xem [hướng dẫn các vị trí VPS](https://cloudj.net/vi/blog/vps-locations-guide) nếu bạn vẫn đang chọn khu vực.

## Cần chuẩn bị gì để cài Docker trên VPS

- Một VPS Linux chạy bản Ubuntu LTS hoặc Debian stable hiện hành. Danh sách hệ điều hành theo từng vị trí có trong [console Cloudj](https://app.cloudj.net).
- Một user có quyền `sudo` và đăng nhập bằng SSH key. Xem bài [tạo SSH key cho VPS](https://cloudj.net/vi/blog/ssh-key-vps-setup) nếu cần.
- Đủ bộ nhớ cho các container. Bản thân Docker Engine khá nhẹ, ứng dụng của bạn mới quyết định cấu hình. Một stack thử nghiệm nhỏ chạy được trên các gói thấp nhất, và bạn có thể nâng CPU, RAM sau khi tải tăng lên.

## Bước 1: gỡ các gói xung đột

Các gói của bản phân phối như `docker.io` hay `podman-docker` xung đột với gói chính thức của Docker. Hãy gỡ những gói đang có; apt sẽ báo các gói chưa được cài, điều đó không sao:

```bash
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove -y $pkg; done
```

Lệnh này không xóa image và volume đang có trong `/var/lib/docker/`.

## Bước 2: thêm repository chính thức của Docker

Các lệnh dưới đây chạy được trên cả Ubuntu lẫn Debian, vì chúng đọc tên bản phân phối và codename từ `/etc/os-release`. Chúng không dùng được trên các bản dẫn xuất như Linux Mint; với những bản đó, hãy làm theo hướng dẫn của bản phân phối gốc tương ứng.

```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL https://download.docker.com/linux/$ID/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $VERSION_CODENAME stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
```

Repository được ký số, và tùy chọn `signed-by` giới hạn key đó chỉ dùng cho repository này. Nếu `apt-get update` báo không tìm thấy codename của bạn, hãy xem danh sách phiên bản được hỗ trợ trong [hướng dẫn cài Docker Engine cho Ubuntu](https://docs.docker.com/engine/install/ubuntu/) và [bản dành cho Debian](https://docs.docker.com/engine/install/debian/).

## Bước 3: cài Docker Engine và Compose

```bash
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

Trên Ubuntu và Debian, dịch vụ tự khởi động và được bật cùng hệ thống. Kiểm tra lại:

```bash
sudo systemctl status docker --no-pager
sudo docker run --rm hello-world
docker compose version
```

Lưu ý Compose giờ là một plugin, gọi bằng `docker compose` có dấu cách ở giữa. Bạn không cần binary `docker-compose` kiểu cũ nữa.

## Bước 4: chọn cách chạy lệnh docker

Mặc định chỉ root mới giao tiếp được với Docker daemon. Bạn có thể thêm user của mình vào nhóm `docker`:

```bash
sudo usermod -aG docker $USER
```

Đăng xuất rồi đăng nhập lại để nhóm có hiệu lực. Hãy hiểu rõ điều này: thuộc nhóm `docker` gần như tương đương quyền root, vì user có thể chạy một container mount toàn bộ hệ thống file của máy chủ. Với máy chủ chỉ một người quản trị thì thường chấp nhận được. Với máy dùng chung, hãy tiếp tục dùng `sudo docker` hoặc tìm hiểu chế độ rootless của Docker.

## Bước 5: thiết lập mặc định hợp lý cho daemon

Hai thiết lập sẽ giúp bạn đỡ rắc rối về sau. Log của container mặc định tăng không giới hạn và có thể làm đầy ổ đĩa nhỏ, còn các cổng publish sẽ bind vào mọi interface nếu bạn không chỉ định. Tạo file `/etc/docker/daemon.json`:

```bash
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" },
  "ip": "127.0.0.1"
}
EOF
sudo systemctl restart docker
```

Thiết lập `ip` khiến `-p 8080:80` bind vào `127.0.0.1`, trừ khi bạn ghi rõ một địa chỉ khác. Giới hạn log chỉ áp dụng cho container tạo sau thay đổi; hãy tạo lại các container cũ để chúng nhận cấu hình mới.

## Bước 6: chạy stack đầu tiên với Docker Compose

Tạo thư mục dự án và file `compose.yaml`:

```bash
mkdir -p ~/apps/hello && cd ~/apps/hello
cat > compose.yaml <<'EOF'
services:
  web:
    image: nginx:stable
    ports:
      - "127.0.0.1:8080:80"
    restart: unless-stopped
EOF
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080
```

Bạn sẽ nhận được header `HTTP/1.1 200 OK` từ nginx. Một số lệnh hữu ích tiếp theo: `docker compose logs -f` để theo dõi log, `docker compose pull && docker compose up -d` để cập nhật image, và `docker compose down` để dừng và gỡ stack.

## Bẫy firewall giữa Docker và ufw

Đây là phần quan trọng nhất. `ufw` lọc lưu lượng ở chain INPUT của máy chủ. Khi bạn publish một cổng, Docker tự thêm các rule iptables chuyển tiếp lưu lượng vào container trước khi rule của ufw kịp xét. Hệ quả: bạn chạy `sudo ufw deny 5432`, publish `-p 5432:5432` cho cơ sở dữ liệu, và database vẫn truy cập được từ internet.

Các cách giữ an toàn, từ đơn giản đến phức tạp:

1. **Chỉ publish trên localhost.** Ghi cổng dạng `127.0.0.1:8080:80`, hoặc dựa vào thiết lập mặc định `"ip": "127.0.0.1"` ở trên. Sau đó đặt một reverse proxy ở cổng 80 và 443 phía trước. Bài [cấu hình nginx reverse proxy kèm SSL](https://cloudj.net/vi/blog/nginx-reverse-proxy-ssl-vps) hướng dẫn chi tiết.
2. **Không publish các dịch vụ nội bộ.** Các container trong cùng network của Compose gọi nhau bằng tên service, nên database thường không cần mục `ports:` nào cả.
3. **Lọc trong chain DOCKER-USER.** Docker dành chain iptables này cho rule của bạn, được xét trước các rule chuyển tiếp của Docker. Cách này hiệu quả nhưng cần cẩn thận, và rule phải được lưu lại để còn sau khi reboot.

Hãy kiểm tra từ bên ngoài, đừng kiểm tra ngay trên máy chủ. Từ máy tính của bạn, thử kết nối tới một cổng lẽ ra phải đóng:

```bash
nc -vz 203.0.113.10 5432
```

Kết quả timeout hoặc bị từ chối là điều bạn mong muốn. Trên máy chủ, `sudo ss -tlnp` liệt kê mọi socket đang lắng nghe và địa chỉ mà nó bind vào. Xem bài [cấu hình firewall ufw cho VPS](https://cloudj.net/vi/blog/ufw-firewall-vps) để viết rule cho chính máy chủ.

## Cập nhật Docker

Các gói Docker được cập nhật qua apt như mọi gói khác:

```bash
sudo apt-get update && sudo apt-get upgrade
```

Trên Ubuntu và Debian, unattended upgrades mặc định chỉ cài bản cập nhật bảo mật từ bản phân phối, không lấy từ repository của Docker, nên bạn cần lên lịch cập nhật thủ công. Image là chuyện riêng: hãy pull bản mới và tạo lại container định kỳ, rồi dọn image không dùng bằng `docker image prune`.

## Câu hỏi thường gặp

### Có nên dùng gói docker.io của Ubuntu cho nhanh không?

Gói đó vẫn chạy được, nhưng thường chậm hơn bản gốc và đóng gói Compose theo cách khác. Repository chính thức cho bạn bản mới nhất và plugin `docker compose` mà phần lớn tài liệu hiện nay mặc định.

### Vì sao container vẫn truy cập được dù ufw đã chặn cổng?

Rule iptables của Docker cho các cổng publish được xét trước rule của ufw. Hãy bind cổng vào `127.0.0.1`, bỏ mục `ports:` hoặc lọc trong chain DOCKER-USER.

### Docker trên VPS cần bao nhiêu RAM?

Bản thân Docker dùng rất ít, các container mới là phần quyết định. Hãy theo dõi bộ nhớ trong phần số liệu của console và nâng cấu hình khi thường xuyên chạm ngưỡng.

### Cài Docker trên Windows VPS được không?

Bài này chỉ nói về Linux. Để chạy container trên máy chủ, image Linux là lựa chọn đơn giản và phổ biến hơn.

## Triển khai và bắt đầu xây dựng

Hãy triển khai một máy chủ Ubuntu hoặc Debian từ [console Cloudj](https://app.cloudj.net), làm theo các bước trên và stack Compose đầu tiên của bạn sẽ sớm chạy. Bạn có thể so sánh các vị trí trên [trang máy chủ cloud](https://cloudj.net/vi/cloud-servers), xem giá trực tiếp trên [trang bảng giá](https://cloudj.net/vi/pricing) và nạp tiền bằng chuyển khoản VND.
