Giao diện
Module 2: The Art of Building (Nghệ thuật xây dựng Image)
🎓 Instructor Profile
Kỹ sư Raizo (Phó CTO HPN) - người bị ám ảnh bởi việc tối ưu từng KB dung lượng Image, đồng hành cùng Giáo sư Tom - chuyên gia phân tích nguyên lý.
Chào mừng đến với "nhà máy" Docker. Viết Dockerfile không khó, nhưng viết Dockerfile để Build nhanh, Image nhẹ và An toàn là cả một nghệ thuật (Engineering Art).
Trong module này, chúng ta sẽ giải phẫu Dockerfile và học các kỹ thuật tối ưu mà 90% Junior Developer thường bỏ qua.
🧬 Phần 1: Anatomy of a Dockerfile (Giải phẫu cơ bản)
Dockerfile không chỉ là một kịch bản (script) cài đặt. Nó là Bản thiết kế hạ tầng (Infrastructure Blueprint). Một Dockerfile chuẩn sẽ kể câu chuyện về cách ứng dụng của bạn được hình thành.
Các từ khóa cốt lõi (Keywords)
| Keyword | Giải thích | Best Practice |
|---|---|---|
FROM | Chọn nền móng cho ngôi nhà. | Ưu tiên alpine (siêu nhẹ, ~5MB) hoặc slim thay vì bản full (hàng trăm MB). |
WORKDIR | Thiết lập thư mục làm việc. | Luôn dùng. Đừng vứt code bừa bãi ở thư mục gốc /. Giúp cô lập không gian ứng dụng. |
COPY | Sao chép file từ máy host vào image. | Dùng COPY thay vì ADD. COPY chỉ làm một việc là copy. |
RUN | Thực thi lệnh trong quá trình build. | Gộp các lệnh RUN bằng && để giảm số lượng layer (sẽ giải thích sau). |
⚠️ COPY vs ADD
Tại sao các chuyên gia bảo mật khuyên dùng COPY?
ADDquá "thông minh": Nó có thể tự động giải nén file tarball hoặc tải file từ URL độc hại. Điều này tạo ra bề mặt tấn công không cần thiết.COPYđơn giản, minh bạch và dễ kiểm soát. Keep It Simple.
🚀 Phần 2: The Layer Caching Strategy (Chiến lược bộ đệm)
Đây là kiến thức Core Knowledge phân định Senior và Junior.
Cơ chế UnionFS & Layers
Hãy tưởng tượng Docker Image như một chiếc Bánh Crepe ngàn lớp. Mỗi dòng lệnh trong Dockerfile (RUN, COPY, FROM) sẽ tạo ra một lớp (layer) mới đè lên lớp cũ. Docker sử dụng Layer Caching: Nếu nội dung của một layer và các layer trước nó không thay đổi, Docker sẽ dùng lại "bánh cũ" thay vì nướng lại từ đầu.
Quy tắc vàng: Sắp xếp theo tần suất thay đổi
Sắp xếp lệnh từ "Ít thay đổi nhất" đến "Hay thay đổi nhất".
❌ Bad Practice (Cách làm non tay)
dockerfile
WORKDIR /app
# Copy toàn bộ code (Hay thay đổi) vào trước
COPY . .
# Cài dependencies (Ít thay đổi) sau
RUN npm installHậu quả: Chỉ cần bạn sửa 1 dấu phẩy trong code (COPY . . thay đổi), Docker sẽ hủy cache từ dòng đó trở đi. Lệnh npm install nặng nề sẽ phải chạy lại mỗi lần build. Lãng phí thời gian khủng khiếp.
✅ Good Practice (Chuẩn Engineering)
dockerfile
WORKDIR /app
# 1. Copy file định nghĩa dependency trước
COPY package.json package-lock.json ./
# 2. Cài đặt dependency (Tận dụng Cache tối đa)
RUN npm install
# 3. Mới copy source code vào sau
COPY . .Lợi ích: Code sửa thoải mái, npm install vẫn được cache lại (trừ khi bạn cài thêm thư viện mới). Tốc độ build lại giảm từ phút xuống giây.
🎭 Phần 3: CMD vs ENTRYPOINT (Cuộc chiến định danh)
Hai lệnh này đều dùng để chạy ứng dụng khi container khởi động, nhưng ngữ nghĩa khác nhau hoàn toàn.
Metaphor: Máy xay sinh tố
ENTRYPOINT: Là Cái máy xay. Nó là thành phần cố định, khó thay đổi.CMD: Là Hoa quả bỏ vào máy. Nó là tham số mặc định, dễ dàng thay thế.
Công thức: Container Runtime = ENTRYPOINT + CMD
Ví dụ thực chiến
dockerfile
# Máy xay (Executable)
ENTRYPOINT ["/bin/ping"]
# Nguyên liệu mặc định (Default Argument)
CMD ["ganvn.cn"]- Trường hợp 1: Chạy
docker run my-pinger.- Thực thi:
/bin/ping ganvn.cn(Dùng CMD mặc định).
- Thực thi:
- Trường hợp 2: Chạy
docker run my-pinger google.com.- Thực thi:
/bin/ping google.com(google.comghi đèganvn.cn). Cái máy xay (ping) vẫn giữ nguyên.
- Thực thi:
💡 Lời khuyên
Sử dụng ENTRYPOINT cho các ứng dụng đóng gói dạng CLI tool, và dùng CMD để cung cấp các cờ (flags) hoặc tham số mặc định.
🏗️ Phần 4: Multi-Stage Builds (Kỹ thuật "Vắt chanh bỏ vỏ")
Tư duy Architect: Tại sao bạn lại mang cả bộ compiler (GCC, Go, JDK), Maven, Gradle vào môi trường Production trong khi server chỉ cần đúng file binary để chạy?
Kỹ thuật Multi-Stage Builds cho phép bạn dùng nhiều FROM trong một Dockerfile.
- Stage 1 (Build): Dùng image đầy đủ công cụ để biên dịch code.
- Stage 2 (Run): Dùng image siêu nhẹ, chi copy kết quả (artifact) từ Stage 1 sang.
Ví dụ mô hình Golang
dockerfile
# --- Stage 1: Builder (Cồng kềnh) ---
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Build ra file binary tên là 'myapp'
RUN go build -o myapp main.go
# --- Stage 2: Runner (Siêu nhẹ) ---
# Dùng Alpine trắng trơn
FROM alpine:latest
WORKDIR /root/
# Chỉ copy file binary từ Stage 1. Bỏ lại toàn bộ source code và bộ Go SDK.
COPY --from=builder /app/myapp .
CMD ["./myapp"]Kết quả: Image giảm từ ~800MB (Golang base) xuống còn ~10MB (Alpine + Binary). Tiết kiệm băng thông, deploy nhanh thần tốc và giảm bề mặt tấn công.
🛡️ Phần 5: Security & Optimization (An toàn & Tối ưu)
1. .dockerignore - Cái "thùng rác" thông minh
Đừng bao giờ để Docker copy mọi thứ vào Image. Tạo file .dockerignore ngay lập tức để loại bỏ rác và scret.
text
# .dockerignore
.git
node_modules
Dockerfile
.env # ⚠️ QUAN TRỌNG: Không bao giờ build kèm file chứa mật khẩu
dist
coverage2. User Permission - Đừng chạy với quyền Root!
Mặc định, Container chạy với quyền root của Linux. Nếu hacker chiếm được container, họ có quyền cao nhất. Hãy tạo một user thường (non-root) để chạy ứng dụng.
dockerfile
# Tạo group và user tên 'appuser'
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Chuyển quyền sở hữu thư mục
RUN chown -R appuser:appgroup /app
# Kích hoạt user (Từ dòng này trở đi, lệnh chạy bởi appuser)
USER appuser🛑 Security First
Nguyên tắc Least Privilege (Quyền hạn tối thiểu) là bắt buộc trong môi trường Production chuyên nghiệp. Đừng lười.