Docker Toàn Tập Trên VPS: Từ Chưa Biết Gì Đến Triển Khai Production — Tôi Đã Học Bằng Cách Phá Tan Một Server

📅 01/07/2026 · ⏱ 16 phút đọc · 🏷 DevOps · Docker · VPS

1. Lần Đầu Tôi Dùng Docker: Xóa Nhầm Container Đang Chạy Production

Năm 2023, tôi quyết định "học Docker" bằng cách cài lên VPS production luôn. Đừng làm vậy. Tôi gõ docker rm -f $(docker ps -aq) nghĩ là xóa container cũ — hóa ra đang SSH nhầm server. 3 website, 2 API, 1 database — tất cả biến mất trong 0.5 giây.

Đó là bài học đắt giá: Docker mạnh nhưng cũng nguy hiểm nếu không hiểu nó hoạt động thế nào. Sau 3 năm dùng Docker hàng ngày — từ blog cá nhân đến hệ thống 20+ microservices cho khách hàng — tôi viết bài này như một cẩm nang Docker toàn tập cho VPS. Từ cài đặt, Dockerfile, Compose, network, volume, đến bảo mật và Swarm. Không lan man — chỉ những thứ bạn thực sự cần để chạy production.

2. Docker Là Gì? Tại Sao Không Cần Cài Trực Tiếp Lên VPS Nữa?

Trước Docker: cài app Node.js → cài Node.js phiên bản X → cài npm packages → app chạy. 6 tháng sau, cài app Python → cần Python 3.11 → xung đột với Node.js → mất 3 tiếng debug. Trên cùng một server, mỗi app đòi phiên bản runtime khác nhau, thư viện khác nhau → "dependency hell".

Docker giải quyết bằng cách đóng gói app + runtime + thư viện + config vào một container — một môi trường độc lập, nhẹ hơn VM (máy ảo) rất nhiều vì dùng chung kernel Linux với host. Không còn "trên máy tôi chạy mà". Container chạy giống hệt nhau trên mọi VPS, mọi môi trường.

Tiêu chíKhông Docker (cài trực tiếp)Docker ContainerVirtual Machine (KVM)
IsolationKhông — app dùng chung OSCó — mỗi container độc lậpCó — mỗi VM có OS riêng
Dung lượngNhẹ nhấtVài MB đến vài trăm MBVài GB (cả OS)
Khởi độngNgay lập tức< 1 giây30-60 giây
DeployThủ công (scp, ssh, cài đặt)1 lệnh docker compose upClone VM hoặc cài lại
RollbackKhó (phải có backup)Đổi tag image, up lạiRevert snapshot
Tốn RAMChỉ RAM của appRAM app + ~20MB Docker overheadRAM app + RAM OS (300MB-1GB)

3. Cài Docker Trên Ubuntu 24.04

# Cài Docker Engine + Compose plugin
curl -fsSL https://get.docker.com | sudo sh
sudo apt install -y docker-compose-plugin
sudo usermod -aG docker $USER

# Đăng xuất & đăng nhập lại để có quyền docker
docker --version      # >= 26.x
docker compose version # >= 2.x
⚠️ Bẫy #1: Không bao giờ chạy Docker container với user root bên trong container trừ khi bắt buộc. Luôn tạo user non-root trong Dockerfile. Container chạy root có thể thoát ra host nếu có kernel exploit.

4. Dockerfile: Bản Thiết Kế Cho Container

Dockerfile là file text định nghĩa cách build một image. Mỗi dòng là một layer — Docker cache từng layer để build nhanh hơn.

4.1. Dockerfile Cho Node.js App

# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2: Production
FROM node:22-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
RUN chown -R app:app /app
USER app
EXPOSE 3000
CMD ["node", "server.js"]

Multi-stage build: Stage 1 cài dependencies, Stage 2 chỉ copy node_modules cần thiết → image nhẹ hơn 60% so với build một stage.

4.2. Dockerfile Cho Python FastAPI

FROM python:3.12-slim
RUN groupadd -r app && useradd -r -g app app
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN chown -R app:app /app
USER app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

4.3. Các Lệnh Docker Cơ Bản

docker build -t myapp:latest .           # Build image
docker images                              # Xem danh sách image
docker run -d --name myapp -p 3000:3000 myapp:latest  # Chạy container
docker ps                                  # Container đang chạy
docker ps -a                               # Tất cả container (kể cả đã dừng)
docker logs -f myapp                       # Xem log real-time
docker exec -it myapp sh                   # Vào shell trong container
docker stop myapp && docker rm myapp       # Dừng và xóa container
docker rmi myapp:latest                    # Xóa image
docker system prune -a                     # Dọn rác (image, container, network cũ)

5. Docker Compose: Quản Lý Nhiều Container Cùng Lúc

App thực tế không chỉ có 1 container. Một web app điển hình = Node.js + PostgreSQL + Redis + Nginx. Docker Compose quản lý tất cả qua một file docker-compose.yml.

5.1. Stack Node.js + PostgreSQL + Redis + Nginx

version: '3.8'
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 10s
      retries: 5
  
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redisdata:/data
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
  
  app:
    build: .
    restart: unless-stopped
    environment:
      DATABASE_URL: postgresql://myapp:${DB_PASSWORD}@postgres:5432/myapp
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
  
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./static:/usr/share/nginx/html/static
    depends_on:
      - app

volumes:
  pgdata:
  redisdata:
🔧 Mẹo: Dùng condition: service_healthy thay vì depends_on đơn thuần. Mặc định depends_on chỉ đợi container start — không đợi service sẵn sàng. PostgreSQL cần 5-10 giây để ready sau khi container start. Nếu app kết nối quá sớm → lỗi.

5.2. Các Lệnh Docker Compose Quan Trọng

docker compose up -d              # Khởi động tất cả service
docker compose ps                 # Xem trạng thái
docker compose logs -f app        # Log của service "app"
docker compose restart app        # Restart một service
docker compose down               # Dừng + xóa container, network
docker compose down -v            # + xóa cả volume (MẤT DATA!)
docker compose pull && docker compose up -d --build  # Update image

6. Docker Network: Container Giao Tiếp Với Nhau Thế Nào?

Mặc định Docker Compose tạo một bridge network cho project. Các container gọi nhau bằng service name (không cần IP): postgres, redis, app.

# Tạo network riêng cho nhiều project cùng dùng
docker network create shared

# docker-compose.yml
networks:
  default:
    name: shared
    external: true

Pattern này cực kỳ hữu ích khi bạn có: app A cần gọi API của app B, nhưng app A và B ở 2 project Compose khác nhau. Cho chúng dùng chung network → gọi http://app-b:3000 là xong.

⚠️ Bẫy #2: Không expose database port ra host (dòng ports: "5432:5432"). Nếu bạn expose port database, hacker scan port 5432 → brute force PostgreSQL → toàn bộ dữ liệu bị đánh cắp. App và DB trong cùng Docker network đã giao tiếp được qua service name — không cần expose port ra ngoài.

7. Docker Volume: Dữ Liệu Không Biến Mất Khi Container Bị Xóa

Container là ephemeral — xóa container là mất hết dữ liệu bên trong (trừ khi có volume). Volume là nơi lưu dữ liệu bền vững, gắn vào container.

# Named volume (Docker quản lý)
volumes:
  pgdata:
    driver: local

# Bind mount (thư mục host gắn vào container)
volumes:
  - ./uploads:/app/uploads
  - ./nginx.conf:/etc/nginx/nginx.conf:ro  # ro = read-only

Backup volume PostgreSQL:

# Backup
docker exec -t postgres pg_dumpall -U myapp > backup.sql

# Hoặc dùng volume backup
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
  tar czf /backup/pgdata_backup.tar.gz -C /data .

8. Bảo Mật Docker Trên VPS

8.1. Không Chạy Container Với --privileged

--privileged cấp cho container toàn bộ quyền của host — gần như root. Chỉ dùng khi thực sự cần (vd: chạy Docker trong Docker). 99% trường hợp không cần.

8.2. Scan Image Vulnerability

# Dùng Docker Scout (tích hợp sẵn)
docker scout quickview myapp:latest

# Hoặc Trivy (mã nguồn mở)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image myapp:latest

8.3. Read-only Root Filesystem

services:
  app:
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

Container không thể ghi vào filesystem — hacker có exploit RCE cũng không tạo được file backdoor. Nếu app cần ghi (upload, cache), mount volume riêng vào thư mục đó.

8.4. Resource Limits

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

Không giới hạn = một container lỗi memory leak có thể ngốn hết RAM host → toàn bộ service khác chết theo.

8.5. Không Hardcode Secret Trong Dockerfile/Compose

# Dùng .env file (đã có trong .gitignore)
DB_PASSWORD=supersecret

# docker-compose.yml
environment:
  POSTGRES_PASSWORD: ${DB_PASSWORD}

# Hoặc Docker Secrets (Swarm)
echo "supersecret" | docker secret create db_password -

9. Docker Swarm: Khi Một VPS Không Đủ

Docker Swarm là giải pháp orchestration đơn giản nhất, tích hợp sẵn trong Docker. Không cần cài Kubernetes phức tạp. Swarm = nhiều VPS Docker kết nối thành một cluster.

# Khởi tạo Swarm trên manager node
docker swarm init --advertise-addr 1.2.3.4

# Join worker node (chạy trên VPS khác)
docker swarm join --token SWMTKN-xxx 1.2.3.4:2377

# Deploy stack (giống docker compose nhưng trên Swarm)
docker stack deploy -c docker-compose.yml mystack

# Scale service
docker service scale mystack_app=5

# Rolling update
docker service update --image myapp:v2 mystack_app
Tính năngDocker Compose (1 VPS)Docker Swarm (nhiều VPS)
Số node1Nhiều (manager + worker)
Load balancing✅ Tích hợp (routing mesh)
Rolling update✅ Zero-downtime deploy
Auto restart khi node chết✅ Reschedule sang node khác
Secret management❌ (dùng .env)✅ docker secret
Config management❌ (bind mount file)✅ docker config
Phù hợpBlog, app nhỏ, devProduction, multi-service, cần HA
⚠️ Bẫy #3: Swarm không phải là Kubernetes. Nếu bạn cần auto-scaling, service mesh (Istio), GitOps (ArgoCD), hoặc ecosystem rộng — dùng Kubernetes. Swarm phù hợp team nhỏ (1-3 người) muốn orchestration đơn giản, không muốn học K8s.

10. Docker Cheatsheet Cho Người Mới

# Build & Run
docker build -t tên-app .                    # Build image từ Dockerfile
docker run -d --name tên -p host:container image  # Chạy container

# Debug
docker logs -f tên-container                 # Xem log
docker exec -it tên-container sh             # Vào container
docker inspect tên-container                 # Xem chi tiết config

# Dọn dẹp
docker system df                             # Xem disk usage
docker system prune -a --volumes             # Xóa mọi thứ không dùng

# Compose
docker compose up -d                         # Start stack
docker compose down                          # Stop stack
docker compose logs -f                       # Log tất cả service
docker compose exec app sh                   # Vào service "app"

11. Kết Luận

Docker không khó — cái khó là hiểu đúng triết lý của nó: container là bò dùng một lần (ephemeral), dữ liệu nằm ở volume, config nằm ở Compose file, và mỗi container chỉ làm một việc. Khi bạn quen với mindset này, việc deploy một stack 5 service chỉ còn là một lệnh docker compose up -d.

Với VPS 4GB RAM (~137K/tháng), bạn có thể chạy: 1 Node.js app + PostgreSQL + Redis + Nginx + Caddy — tất cả trong Docker, tất cả tự restart khi crash, tất cả deploy trong 30 giây. Đây là cách tôi chạy mọi project từ 2 năm nay và chưa có lý do gì để quay lại cài trực tiếp.

🐳 Cần VPS mạnh để chạy Docker 24/7?

Netihot VPS từ 77K/tháng: AMD EPYC, NVMe SSD, hỗ trợ Docker, uptime 99.9%. Cài sẵn Ubuntu + Docker nếu yêu cầu.

Thuê VPS Cho Docker