Vấn Đề Mà Mọi Developer Đều Gặp Phải
Bạn clone project của đồng nghiệp về, chạy thử và ngay lập tức bị một màn hình lỗi chào đón. “Máy tao chạy được mà” — câu nói quen thuộc đã làm hỏng không biết bao nhiêu dự án. Nguyên nhân gốc rễ hầu như lúc nào cũng là sự khác biệt về môi trường: sai phiên bản Python, thiếu thư viện hệ thống, đường dẫn OS khác nhau, xung đột dependencies.
Docker giải quyết vấn đề này bằng cách thay đổi thứ bạn giao cho người khác. Thay vì chỉ đưa code và hy vọng máy họ chịu hợp tác, bạn đưa luôn cả môi trường — thư viện OS, runtime, dependencies, config — tất cả được đóng gói trong một đơn vị di động gọi là container. Sau ba năm dùng Docker trong môi trường production cho hàng chục dự án, đây thực sự là một trong những công cụ hữu ích nhất tôi từng học.
Bắt Đầu Nhanh — Cài Docker Và Chạy Thử Trong 5 Phút
Hãy cài Docker và xác nhận nó hoạt động trước khi đi sâu vào lý thuyết.
Cài Docker trên Linux (Ubuntu/Debian)
# Xóa các phiên bản cũ nếu có
sudo apt-get remove docker docker-engine docker.io containerd runc
# Cài các gói phụ thuộc
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# Thêm GPG key chính thức của Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Thêm repository Docker
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Cài Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Chạy Docker không cần sudo (tùy chọn nhưng nên làm)
sudo usermod -aG docker $USER
newgrp docker
Kiểm tra cài đặt
docker --version
# Docker version 26.x.x, build ...
docker run hello-world
Thấy thông báo thân thiện từ Docker? Vậy là xong. Lệnh hello-world vừa pull một image từ Docker Hub, khởi chạy container, in ra nội dung rồi thoát gọn gàng. Pull → chạy → thoát. Đó là toàn bộ vòng đời của container trong một lệnh duy nhất.
Docker Hoạt Động Như Thế Nào
Container vs Máy Ảo (VM)
Nhiều người quen dùng VM thường nghĩ container chỉ là VM nhỏ hơn. Thực ra không phải — sự khác biệt nằm ở tầng sâu hơn chứ không đơn giản là kích thước.
- VM ảo hóa phần cứng. Mỗi VM chạy toàn bộ OS kernel riêng — khởi động mất 30–60 giây và có thể ngốn 1–2GB RAM trước khi ứng dụng của bạn kịp chạy.
- Container dùng chung OS kernel của máy host. Chúng cô lập các tiến trình bằng Linux namespaces và cgroups. Khởi động dưới một giây; overhead bộ nhớ thường chỉ vài MB.
Một cách hình dung dễ hiểu: VM giống như chạy một máy tính riêng biệt bên trong máy tính của bạn. Container thì giống như một tiến trình tưởng rằng nó đang độc chiếm cả cái máy.
Các Khái Niệm Cốt Lõi Của Docker
- Image — Bản thiết kế chỉ đọc. Giống như class trong lập trình hướng đối tượng.
- Container — Một instance đang chạy của image. Giống như object được tạo ra từ class đó.
- Dockerfile — Công thức để build image, từng dòng một.
- Docker Hub — Registry công khai lưu trữ các image. Hãy nghĩ như npm, nhưng dành cho container.
- Volume — Bộ nhớ lưu trữ bền vững, tồn tại sau khi container khởi động lại.
Các Lệnh Docker Cơ Bản
# Pull một image từ Docker Hub
docker pull nginx:latest
# Liệt kê các image đã tải về
docker images
# Chạy container (chế độ nền, ánh xạ cổng)
docker run -d -p 8080:80 --name my-nginx nginx
# Xem các container đang chạy
docker ps
# Xem tất cả container (kể cả đã dừng)
docker ps -a
# Xem log của container
docker logs my-nginx
# Mở shell bên trong container đang chạy
docker exec -it my-nginx bash
# Dừng và xóa container
docker stop my-nginx
docker rm my-nginx
# Xóa một image
docker rmi nginx:latest
Flag -p 8080:80 ánh xạ cổng 80 bên trong container ra cổng 8080 trên máy host. Mở http://localhost:8080 trong trình duyệt là nginx đã chạy ngay — không cần cài thêm gì ngoài lệnh docker run đó.
Tự Build Docker Image Của Riêng Bạn
Pull image có sẵn chỉ đưa bạn đến một mức nhất định. Đóng gói ứng dụng của chính mình mới là lúc mọi thứ trở nên thú vị.
Viết Dockerfile cho Ứng Dụng Python
Đây là một ứng dụng Flask tối giản để thực hành:
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Xin chào từ Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
# requirements.txt
flask==3.0.3
Tạo file Dockerfile trong cùng thư mục:
# Dùng image Python slim chính thức làm base
FROM python:3.12-slim
# Đặt thư mục làm việc bên trong container
WORKDIR /app
# Copy file dependencies trước (tối ưu layer caching)
COPY requirements.txt .
# Cài đặt dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy mã nguồn ứng dụng
COPY app.py .
# Khai báo cổng mà ứng dụng lắng nghe
EXPOSE 5000
# Lệnh chạy khi container khởi động
CMD ["python", "app.py"]
Build và Chạy Image
# Build image (dấu . nghĩa là dùng thư mục hiện tại làm build context)
docker build -t my-flask-app:1.0 .
# Chạy ứng dụng
docker run -d -p 5000:5000 --name flask-demo my-flask-app:1.0
# Kiểm tra
curl http://localhost:5000
# Xin chào từ Docker!
Dùng Docker Volume để Lưu Dữ Liệu Bền Vững
Container có tính tạm thời — bất kỳ dữ liệu nào ghi bên trong sẽ mất khi bạn xóa container. Database và các dịch vụ có trạng thái cần volume để tồn tại qua đó:
# Chạy PostgreSQL với named volume
docker run -d \
--name postgres-db \
-e POSTGRES_PASSWORD=mysecretpassword \
-e POSTGRES_DB=myapp \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
# Liệt kê các volume
docker volume ls
# Xem vị trí volume trên máy host
docker volume inspect pgdata
Volume pgdata vẫn tồn tại sau khi docker rm. Tạo lại container với cùng flag -v pgdata:... và dữ liệu của bạn quay trở lại nguyên vẹn.
Mẹo Thực Tế Từ Kinh Nghiệm Thực Tế
1. Layer Caching — Build Image Nhanh Hơn
Docker cache từng instruction layer. Copy requirements.txt trước khi copy mã nguồn — khi chỉ code thay đổi, Docker tái sử dụng layer dependencies đã cache và bỏ qua việc cài lại packages. Với project có 40+ dependencies, điều này rút ngắn thời gian build từ ~90 giây xuống còn dưới 5 giây.
2. Dùng .dockerignore
File .dockerignore giúp loại bỏ những thứ không cần thiết khỏi build context:
__pycache__
*.pyc
.env
.git
venv/
*.log
Build context nhỏ hơn đồng nghĩa build nhanh hơn. Quan trọng hơn, nó ngăn bạn vô tình đưa secrets hoặc config nội bộ vào trong image.
3. Không Bao Giờ Lưu Secrets Trong Image
Hardcode API key hay mật khẩu vào Dockerfile là lỗi phổ biến và rất nguy hiểm. Thay vào đó, hãy truyền chúng vào lúc runtime:
docker run -e DATABASE_URL=postgres://user:pass@host/db my-app
Hoặc trỏ đến file .env nằm ngoài version control:
docker run --env-file .env my-app
4. Multi-stage Build để Giảm Kích Thước Image
Với các ngôn ngữ biên dịch như Go hay Java, multi-stage build cho phép bạn compile trong một container và chỉ ship binary trong container khác:
# Giai đoạn build
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o server .
# Giai đoạn cuối — chỉ có binary, không có Go toolchain
FROM debian:bookworm-slim
COPY --from=builder /app/server /usr/local/bin/server
CMD ["server"]
Chỉ riêng điều này có thể thu nhỏ image Go từ ~850MB (bao gồm cả toolchain) xuống còn 12–20MB. Hoàn toàn xứng đáng với bốn dòng code thêm vào mỗi lần.
5. Kiểm Tra Container Khi Có Sự Cố
# Thông tin chi tiết về container (mạng, mount, biến môi trường)
docker inspect my-container
# Theo dõi tài nguyên theo thời gian thực
docker stats
# Mở shell vào container đã dừng để debug
docker run -it --entrypoint bash my-image
Bước Tiếp Theo
Hãy nắm vững container đơn lẻ trước, rồi mới chuyển sang Docker Compose. Công cụ này cho phép bạn định nghĩa toàn bộ một stack — web app, PostgreSQL, Redis, message queue — trong một file docker-compose.yml và khởi chạy tất cả chỉ bằng một lệnh docker compose up. Không còn phải mở năm terminal tab chạy năm lệnh docker run riêng lẻ nữa.
Sau đó, Kubernetes đảm nhiệm việc chạy container ở quy mô lớn trên nhiều máy chủ. Nhưng đừng vội vàng tiến đến đó. Chỉ riêng những kiến thức Docker cơ bản cũng sẽ giúp bạn làm việc hiệu quả hơn rõ rệt chỉ sau một tuần sử dụng hàng ngày — và đó là nền tảng vững chắc để xây dựng tiếp.

