Dockerfile cơ bản: Tạo Image của riêng bạn
Bạn có biết?
Bạn có bao giờ nấu ăn theo công thức chưa? Công thức ghi rõ: nguyên liệu gì, bao nhiêu gam, bước nào trước bước nào sau. Cuối cùng bạn có một món ăn hoàn chỉnh.
Dockerfile cũng giống vậy — nó là “công thức nấu ăn” để tạo ra Docker Image. Bạn viết các bước trong Dockerfile, Docker “nấu” (build) ra Image, rồi từ Image đó chạy Container. Hôm nay chúng ta sẽ học cách viết công thức này!
Dockerfile là gì?
Dockerfile là một file text chứa danh sách các instructions (lệnh hướng dẫn) để build Docker Image. Mỗi dòng trong Dockerfile là một bước trong quá trình tạo Image.
Bạn có thể hình dung:
- Dockerfile = Công thức nấu ăn (blueprint)
- Image = Món ăn hoàn chỉnh (bản sao sẵn sàng dùng)
- Container = Bữa ăn được phục vụ (instance đang chạy)
Cấu trúc Dockerfile cơ bản
Mỗi Dockerfile gồm các instruction chính sau:
FROM — Chọn base image
Đây luôn là dòng đầu tiên. Chọn “nền tảng” để build lên:
FROM node:18-alpine
FROM python:3.11-slim
FROM ubuntu:22.04
Bạn có 3 lựa chọn phổ biến:
- Alpine — nhỏ nhất (~5MB base), bảo mật tốt, phù hợp production
- Slim — nhỏ hơn full, bỏ bớt package không cần thiết
- Full — đầy đủ tool, nhưng nặng hơn nhiều
💡 Mẹo: Ưu tiên dùng Alpine trừ khi cần package đặc biệt.
WORKDIR — Đặt working directory
Giống như cd vào thư mục làm việc. Các lệnh COPY, RUN, CMD sẽ thực thi trong thư mục này:
WORKDIR /app
COPY — Copy files vào Image
Copy file từ máy host vào trong Image:
COPY package.json ./
COPY . .
Dòng đầu copy riêng package.json, dòng thứ hai copy toàn bộ source code.
RUN — Chạy lệnh khi build
Thực thi lệnh trong quá trình build Image. Dùng để cài đặt package, compile code:
RUN npm install
RUN apt-get update && apt-get install -y curl
EXPOSE — Khai báo port
Thông báo Container sẽ listen port nào. Đây chỉ là “khai báo”, không tự động mở port:
EXPOSE 3000
ENV — Đặt biến môi trường
Set environment variable cho Container:
ENV NODE_ENV=production
ENV APP_PORT=3000
CMD — Lệnh chạy khi start Container
Lệnh mặc định khi Container khởi động. Mỗi Dockerfile chỉ có một CMD:
CMD ["node", "server.js"]
ARG — Build-time variables
Biến chỉ có giá trị khi build, không tồn tại trong Container đang chạy:
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine
Ví dụ hoàn chỉnh
Dockerfile cho Node.js app
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Giải thích từng dòng
FROM node:18-alpine— dùng Node.js 18 trên Alpine LinuxWORKDIR /app— mọi thứ sẽ nằm trong thư mục/appCOPY package*.json ./— copy package.json trước (tận dụng Docker cache)RUN npm ci— cài đặt dependenciesCOPY . .— copy source code sau cùngEXPOSE 3000— app chạy trên port 3000CMD ["node", "server.js"]— lệnh khởi động
Build và chạy
# Build Image từ Dockerfile trong thư mục hiện tại
docker build -t myapp:1.0 .
# Chạy Container từ Image vừa build
docker run -d -p 3000:3000 myapp:1.0
# Kiểm tra Container đang chạy
docker ps
Bảng so sánh: COPY vs ADD
| Tiêu chí | COPY | ADD |
|---|---|---|
| Chức năng | Copy files từ host vào Image | Copy files + tự động extract tar, download từ URL |
| Đơn giản | ✅ Đơn giản, dễ hiểu | ❌ Có hành vi ẩn, khó predict |
| Khuyến nghị | ✅ Luôn ưu tiên dùng | ❌ Chỉ dùng khi cần extract tar |
| Ví dụ | COPY app.js ./ |
ADD archive.tar.gz ./ |
💡 Quy tắc: Luôn dùng COPY trừ khi bạn cần chức năng đặc biệt của ADD.
Bảng so sánh: CMD vs ENTRYPOINT
| Tiêu chí | CMD | ENTRYPOINT |
|---|---|---|
| Mục đích | Tham số mặc định cho Container | Định nghĩa lệnh chính, cố định |
| Override | ✅ Dễ override khi docker run |
❌ Khó override, cần --entrypoint |
| Kết hợp | Có thể dùng với ENTRYPOINT | Có thể dùng với CMD |
| Ví dụ | CMD ["node", "app.js"] |
ENTRYPOINT ["python"] |
Ví dụ kết hợp ENTRYPOINT + CMD:
ENTRYPOINT ["python"]
CMD ["app.py"]
# Chạy mặc định: python app.py
# Override CMD: docker run myimage script.py → python script.py
Best Practices
6 nguyên tắc quan trọng khi viết Dockerfile:
1. Dùng .dockerignore — Loại trừ files không cần thiết, giảm kích thước Image:
# .dockerignore
node_modules
.git
.env
*.log
2. Sắp xếp Dockerfile đúng thứ tự — Ít thay đổi ở trên, nhiều thay đổi ở dưới. Docker cache layer theo thứ tự, nếu một layer thay đổi thì tất cả layer bên dưới đều build lại.
3. Dùng Alpine images — Nhỏ gọn, bảo mật tốt hơn. Image node:18 full ~900MB, node:18-alpine chỉ ~170MB.
4. Gộp RUN commands — Giảm số layers, giảm kích thước Image:
# ❌ Tạo 2 layers riêng biệt
RUN apt-get update
RUN apt-get install -y curl
# ✅ Gộp thành 1 layer
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
5. Không dùng root user — Tăng bảo mật bằng cách chạy Container với user thường:
# Cho Node.js
USER node
# Tạo user mới
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
6. Multi-line commands — Dùng \ và && để viết lệnh dài dễ đọc:
RUN apt-get update && \
apt-get install -y \
curl \
wget \
git && \
rm -rf /var/lib/apt/lists/*
Bước tiếp theo
Bạn đã biết cách viết Dockerfile cơ bản! Nhưng Dockerfile còn có nhiều kỹ thuật nâng cao giúp Image nhỏ hơn, build nhanh hơn và bảo mật tốt hơn.
👉 Bài tiếp theo: Dockerfile nâng cao: Multi-stage Build và tối ưu