Docker giúp bạn chạy ứng dụng trong container với đầy đủ thư viện đi kèm, không ảnh hưởng hệ thống chính. Docker Compose cho phép mô tả nhiều container trong một file và khởi chạy bằng một lệnh. Bài này hướng dẫn cài Docker từ kho chính thức trên Ubuntu 24.04, chạy một ứng dụng mẫu bằng Compose, rồi đặt nó sau Nginx reverse proxy có HTTPS. Bạn cũng sẽ biết cách tránh lỗi bảo mật phổ biến: cổng Docker bỏ qua UFW.
Bạn cần chuẩn bị:
- VPS Ubuntu 24.04 (hoặc 22.04) có user sudo, tối thiểu 1 GB RAM.
- Nginx đã cài trên máy chủ (không chạy trong Docker).
- Tên miền phụ
app.example.comtrỏ bản ghi A về203.0.113.10. - Certbot để cấp SSL.
Bước 1: Gỡ các gói Docker cũ hoặc không chính thức
Các gói như docker.io, podman-docker có thể xung đột với gói chính thức. Lệnh dưới đây gỡ chúng nếu có, báo lỗi "not installed" thì bỏ qua:
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove -y $pkg; done
Bước 2: Thêm kho apt chính thức của Docker
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/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/ubuntu $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Biến UBUNTU_CODENAME trên Ubuntu 24.04 là noble. Khóa GPG giúp apt xác minh gói tải về đúng do Docker phát hành.
Bước 3: Cài Docker Engine và plugin Compose
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker run --rm hello-world
docker compose version
Nếu thấy dòng "Hello from Docker!", Docker đã chạy đúng. Để chạy lệnh docker không cần sudo:
sudo usermod -aG docker $USER
newgrp docker
Thành viên nhóm docker có quyền tương đương root. Chỉ thêm user bạn tin cậy.
Bước 4: Giới hạn dung lượng log container
Mặc định Docker ghi log container không giới hạn, lâu ngày có thể làm đầy ổ đĩa. Tạo file cấu hình daemon:
sudo nano /etc/docker/daemon.json
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
sudo systemctl restart docker
Cấu hình này chỉ áp dụng cho container tạo mới sau khi restart.
Bước 5: Viết file compose.yaml cho ứng dụng
Ví dụ dưới dùng image traefik/whoami, một web server nhỏ trả về thông tin yêu cầu. Bạn thay bằng image ứng dụng của mình.
mkdir -p ~/apps/whoami && cd ~/apps/whoami
nano compose.yaml
services:
web:
image: traefik/whoami:latest
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
environment:
- TZ=Asia/Ho_Chi_Minh
Điểm quan trọng nhất là 127.0.0.1:8080:80. Container chỉ nhận kết nối từ chính máy chủ, Internet không gọi thẳng được cổng 8080. Nếu viết "8080:80", Docker sẽ mở cổng ra mọi địa chỉ và bỏ qua quy tắc UFW.
Trường version: ở đầu file đã lỗi thời với Compose v2, bạn không cần khai báo.
Với ứng dụng có database, khai báo thêm service và volume để dữ liệu không mất khi tạo lại container:
services:
app:
image: ghcr.io/vi-du/ung-dung:1.0
restart: unless-stopped
ports:
- "127.0.0.1:8080:3000"
env_file: .env
depends_on:
- db
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
Service db không có ports, nên chỉ các container trong cùng project gọi được qua tên db. Mật khẩu đặt trong file .env cùng thư mục, nhớ chmod 600 .env.
Bước 6: Khởi chạy và quản lý
docker compose up -d
docker compose ps
docker compose logs -f --tail=50
curl -s http://127.0.0.1:8080
Các lệnh hay dùng:
| Lệnh | Tác dụng |
|---|---|
docker compose pull && docker compose up -d | Cập nhật image và tạo lại container |
docker compose restart | Khởi động lại |
docker compose down | Dừng và xóa container (volume vẫn giữ) |
docker compose exec web sh | Mở shell trong container |
docker system df | Xem dung lượng Docker đang dùng |
Bước 7: Cấu hình Nginx reverse proxy
sudo nano /etc/nginx/sites-available/app.example.com
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name app.example.com;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 120s;
}
}
Khối map và hai header Upgrade, Connection cần cho ứng dụng dùng WebSocket. Khối map phải nằm ngoài server, và chỉ khai báo một lần trong toàn bộ cấu hình Nginx. Nếu đã có ở file khác, hãy bỏ nó ở đây.
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Bước 8: Bật HTTPS
sudo certbot --nginx -d app.example.com
sudo certbot renew --dry-run
Certbot tự thêm cấu hình SSL và chuyển hướng HTTP sang HTTPS. Một số ứng dụng cần biết URL công khai, ví dụ biến môi trường APP_URL=https://app.example.com. Hãy khai báo trong .env rồi chạy lại docker compose up -d.
Kiểm tra lại
curl -I https://app.example.comtrả về 200 với chứng chỉ hợp lệ.sudo ss -tlnp | grep 8080hiển thị127.0.0.1:8080, không phải0.0.0.0:8080.- Từ máy khác,
curl -m 5 http://203.0.113.10:8080phải thất bại. - Sau
sudo reboot,docker compose pscho thấy container tự chạy lại nhờrestart: unless-stopped.
Lỗi thường gặp và cách xử lý
permission denied while trying to connect to the Docker daemon socket
User chưa thuộc nhóm docker hoặc chưa đăng nhập lại sau khi thêm. Chạy groups để kiểm tra, đăng xuất rồi vào lại SSH.
502 Bad Gateway từ Nginx
Container chưa chạy hoặc sai cổng. Kiểm tra docker compose ps, docker compose logs và thử curl http://127.0.0.1:8080 trên máy chủ. Cổng bên phải trong ports phải là cổng ứng dụng lắng nghe bên trong container.
Bind for 127.0.0.1:8080 failed: port is already allocated
Cổng đã có tiến trình khác dùng. Tìm bằng sudo ss -tlnp | grep 8080 và đổi sang cổng khác như 127.0.0.1:8081:80, nhớ sửa cả proxy_pass.
Ổ đĩa đầy dần dù ứng dụng nhỏ
Image cũ và build cache tích tụ sau nhiều lần cập nhật. Xem bằng docker system df, dọn bằng docker image prune -a. Cẩn thận với docker system prune --volumes vì lệnh này xóa cả volume không dùng, có thể chứa dữ liệu.