Ở Bài 1 của series, chúng ta đã dùng các image có sẵn như nginx hay hello-world để chạy container. Nhưng khi có một website hay ứng dụng của riêng mình, bạn sẽ cần tự đóng gói nó thành image. Công cụ để làm việc đó chính là Dockerfile — vậy Dockerfile là gì, viết như thế nào và build ra image ra sao?
Bài viết này sẽ trả lời trọn vẹn các câu hỏi trên bằng thực hành: bạn sẽ viết một Dockerfile thật, build thành image web-custom:1.0, chạy container từ chính image đó và hiểu rõ cơ chế cache khi build. Mọi lệnh đều được chạy thật trên máy lab của series kèm ảnh chụp kết quả.
Điều kiện thực hành: máy đã cài Docker (xem bài cài đặt Docker Engine) và bạn đã quen các lệnh cơ bản như docker run, docker images ở Bài 1.
Mục lục bài viết:
- 1. Dockerfile là gì và vì sao nên dùng?
- 2. 10 chỉ thị quan trọng nhất trong Dockerfile
- 3. Thực hành: viết Dockerfile cho một web tĩnh
- 4. Build image bằng lệnh docker build
- 5. Kiểm tra image vừa build: images và history
- 6. Chạy container từ image tự build
- 7. Cơ chế cache khi build: sửa gì thì build lại nấy
- 8. Ví dụ mở rộng: Dockerfile cho ứng dụng Python
- 9. Ba chỉ thị bổ sung đáng biết: ARG, USER và HEALTHCHECK
- 10. Lỗi thường gặp khi viết Dockerfile và build image
- 11. Lời kết
1. Dockerfile là gì và vì sao nên dùng?
Dockerfile là gì? Đó là một file văn bản thuần (không có đuôi mở rộng, tên cố định là Dockerfile) chứa danh sách các chỉ thị theo từng dòng, mô tả cách tạo ra một Docker image: bắt đầu từ image nền nào, copy file gì vào, chạy lệnh cài đặt nào và khởi động ứng dụng ra sao. Docker đọc file này qua lệnh docker build để tạo image một cách hoàn toàn tự động.
Hiểu đơn giản, nếu image là “món ăn thành phẩm” thì Dockerfile chính là “công thức nấu ăn”. Có công thức trong tay, ai cũng nấu lại được món y hệt ở bất kỳ đâu — đó chính là giá trị lớn nhất của Dockerfile: tính tái lập. Máy của bạn, VPS của công ty hay server của khách hàng đều cho ra cùng một image từ cùng một Dockerfile.
Ngoài tính tái lập, Dockerfile còn mang lại ba lợi ích thực tế. Thứ nhất, nó là tài liệu sống: đọc Dockerfile là biết ngay ứng dụng cần hệ điều hành gì, thư viện nào, mở cổng bao nhiêu. Thứ hai, nó lưu được cùng mã nguồn trong Git — mỗi thay đổi về môi trường đều có lịch sử rõ ràng như thay đổi code. Thứ ba, nó tự động hóa hoàn toàn việc đóng gói, là nền tảng cho các quy trình CI/CD sau này.
Người mới đôi khi hỏi: không dùng Dockerfile, cứ chạy container rồi cài tay và docker commit thành image có được không? Được, nhưng cách đó không để lại “công thức”, người khác không biết bạn đã cài những gì trong container và gần như không thể tái lập khi cần. Dockerfile luôn là cách làm đúng đắn và chuyên nghiệp hơn.
Một nhầm lẫn phổ biến khác là giữa Dockerfile và Docker Compose. Hai công cụ này bổ trợ chứ không thay thế nhau: Dockerfile lo phần đóng gói — tạo ra image từ mã nguồn; còn Compose lo phần vận hành — định nghĩa cách chạy một hoặc nhiều container từ các image đã có (mở cổng, gắn volume, biến môi trường). Hiểu nôm na: Dockerfile là công thức nấu ăn, image là món đã nấu xong, còn Compose là thực đơn bữa tiệc gồm nhiều món. Trong series này, chúng ta tập trung vào Dockerfile; Compose đã có mặt trong bài WordPress trên blog nếu bạn muốn xem cách nó vận hành một hệ thống thật.
2. 10 chỉ thị quan trọng nhất trong Dockerfile
Để hiểu rõ Dockerfile là gì trong thực tế, hãy nắm 10 chỉ thị xuất hiện trong hầu hết mọi Dockerfile. Bảng dưới đây tóm tắt công dụng của từng chỉ thị; danh sách đầy đủ có tại tài liệu Dockerfile reference chính thức.
| Chỉ thị | Công dụng | Ví dụ |
|---|---|---|
FROM | Khai báo image nền (base image) — luôn là chỉ thị đầu tiên | FROM nginx:alpine |
RUN | Chạy một lệnh trong lúc build (cài gói, tạo thư mục…) | RUN apt-get update && apt-get install -y curl |
COPY | Copy file/thư mục từ máy host vào image | COPY index.html /usr/share/nginx/html/ |
ADD | Giống COPY nhưng thêm giải nén tar và tải file từ URL | ADD app.tar.gz /opt/ |
WORKDIR | Đặt thư mục làm việc mặc định bên trong image | WORKDIR /app |
ENV | Khai báo biến môi trường cho container | ENV APP_ENV=production |
EXPOSE | Ghi chú cổng mà ứng dụng lắng nghe (mang tính tài liệu) | EXPOSE 80 |
VOLUME | Khai báo điểm gắn volume lưu dữ liệu | VOLUME /data |
CMD | Lệnh mặc định chạy khi container khởi động (bị ghi đè bởi tham số của docker run) | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT | Lệnh chính cố định của container, khó bị ghi đè hơn CMD | ENTRYPOINT ["python", "app.py"] |
Hai chỉ thị hay bị nhầm nhất là CMD và ENTRYPOINT. Quy tắc nhớ nhanh: ENTRYPOINT xác định “container này là chương trình gì”, còn CMD là “tham số mặc định cho chương trình đó”. Khi chạy docker run image <tham số>, phần tham số sẽ thay thế CMD nhưng vẫn được nối sau ENTRYPOINT. Nhiều image kết hợp cả hai: ENTRYPOINT cố định là python, CMD mặc định là tên file script.
Một vài chỉ thị phụ cũng nên biết: LABEL gắn metadata mô tả image; ARG khai báo biến chỉ dùng trong lúc build (ví dụ truyền phiên bản); USER chỉ định user chạy ứng dụng thay vì root để tăng an toàn; và file .dockerignore (không phải chỉ thị, nhưng đi kèm Dockerfile) để loại trừ file không cần thiết khỏi quá trình build giống như .gitignore.
3. Thực hành: viết Dockerfile cho một web tĩnh
Chúng ta sẽ đóng gói một trang web tĩnh đơn giản vào image riêng. Tạo thư mục project web-static gồm ba file: index.html là trang web với một thẻ tiêu đề, Dockerfile là công thức build, và .dockerignore loại trừ các file rác như node_modules, *.log, .git khỏi ngữ cảnh build.

Nội dung Dockerfile chỉ có 4 dòng nhưng đầy đủ các thành phần cốt lõi. Dòng FROM nginx:alpine chọn image nền là nginx bản Alpine — một bản Linux siêu nhẹ, giúp image cuối nhỏ hơn nhiều so với bản đầy đủ. Dòng LABEL ghi mô tả cho image. Dòng COPY đưa file index.html của chúng ta vào đúng thư mục chứa web của nginx bên trong image. Cuối cùng EXPOSE 80 ghi chú rằng ứng dụng lắng nghe ở cổng 80.
Chú ý chúng ta không cần viết CMD vì image nền nginx đã có sẵn lệnh khởi động web server; image con kế thừa cấu hình đó. Đây là một đặc điểm quan trọng khi tìm hiểu Dockerfile là gì: image mới luôn được xây chồng lên image nền và kế thừa mọi thứ chưa bị ghi đè.
4. Build image bằng lệnh docker build
Sau khi đã rõ Dockerfile là gì và viết xong file, bước tiếp theo là biến nó thành image. Đứng ngay trong thư mục project, chạy lệnh build với cú pháp docker build -t <tên>:<tag> . — trong đó cờ -t đặt tên và phiên bản cho image, còn dấu chấm cuối câu lệnh chỉ “ngữ cảnh build” (build context) là thư mục hiện tại, nơi Docker sẽ tìm Dockerfile và các file cần copy:

Đọc output theo từng bước: mỗi chỉ thị trong Dockerfile tương ứng một dòng STEP. Docker thực thi lần lượt từ trên xuống — lấy image nền, ghi label, copy file, ghi chú cổng — sau đó COMMIT kết quả và thông báo “Successfully tagged” kèm tên image. Chuỗi mã ở cuối là ID của image vừa tạo.
Một chi tiết cần hiểu đúng: toàn bộ thư mục ngữ cảnh sẽ được gửi cho Docker daemon khi build. Nếu thư mục chứa nhiều file nặng không liên quan (video, node_modules hàng trăm MB), quá trình build sẽ chậm đi rõ rệt — đó là lý do file .dockerignore luôn nên đi kèm project, đúng như các khuyến nghị trong tài liệu best practices của Docker.
Vài tùy chọn build khác cũng đáng biết. Cờ -f <đường-dẫn> cho phép chỉ định một Dockerfile nằm ngoài ngữ cảnh (ví dụ thư mục chứa nhiều Dockerfile cho các môi trường khác nhau như Dockerfile.dev, Dockerfile.prod). Về đặt tên, hãy gắn tag theo phiên bản có ý nghĩa (1.0, 1.1, 2.0) thay vì lúc nào cũng đè lên latest — nhờ vậy bạn luôn quay lui được phiên bản cũ khi bản mới có vấn đề, và đồng nghiệp nhìn vào danh sách tag là hiểu ngay lịch sử phát hành của image.
5. Kiểm tra image vừa build: images và history

Lệnh docker images cho thấy image web-custom tag 1.0 đã xuất hiện với dung lượng 64.4 MB — bằng đúng nền nginx:alpine cộng thêm file HTML vài KB. Lệnh docker history liệt kê các layer cấu thành image theo thứ tự từ mới nhất: ba layer trên cùng do chính Dockerfile của chúng ta tạo ra (ứng với các chỉ thị EXPOSE, COPY và LABEL), còn các layer phía dưới thuộc về image nền nginx:alpine.
Khái niệm layer là chìa khóa khi học Dockerfile là gì: mỗi chỉ thị FROM, RUN, COPY tạo ra đúng một layer xếp chồng lên nhau. Các layer là chỉ đọc và được dùng chung giữa các image có chung nền — nên mười image cùng dựa trên nginx:alpine chỉ tốn dung lượng của một bản nền duy nhất trên ổ đĩa.
6. Chạy container từ image tự build
Đến phần đáng mong chờ nhất: chạy container từ chính image vừa đóng gói và kiểm tra bằng curl:

Container web2 đã chạy (trạng thái Up) và curl trả về đúng dòng tiêu đề “Xin chao! Day la web chay tu image tu build” trong file index.html của chúng ta. Từ một file HTML trên máy host, qua Dockerfile, chúng ta đã có một web server hoàn chỉnh chạy trong container — quy trình này áp dụng y hệt cho các ứng dụng thật như web PHP, Node.js hay Python.
7. Cơ chế cache khi build: sửa gì thì build lại nấy
Một khía cạnh nữa cần nắm khi học Dockerfile là gì chính là cơ chế cache. Để lần build sau nhanh hơn, Docker lưu cache cho từng bước. Nếu một chỉ thị và các file đầu vào của nó không thay đổi, Docker dùng lại layer đã có thay vì chạy lại. Hãy build lại y hệt khi chưa sửa gì:

Mọi bước đều báo “Using cache” và việc build kết thúc gần như tức thì. Bây giờ hãy sửa nội dung file index.html (đổi dòng tiêu đề thành phiên bản 2) rồi build lại:

Lần này các bước trước COPY vẫn dùng cache, nhưng từ bước COPY trở đi phải chạy lại vì file nguồn đã đổi — và mọi layer phía sau bước thay đổi đều bị vô hiệu hóa cache theo nguyên tắc xếp chồng. Từ cơ chế này rút ra quy tắc vàng khi viết Dockerfile: đặt các chỉ thị ít thay đổi lên trên (FROM, RUN cài thư viện) và các chỉ thị hay thay đổi xuống dưới (COPY mã nguồn). Nhờ vậy, mỗi lần sửa code, Docker chỉ phải chạy lại vài bước cuối thay vì cài lại toàn bộ thư viện từ đầu.
Bản thân cache build cũng chiếm dung lượng đĩa và tích tụ theo thời gian, nhất là trên máy build thường xuyên. Docker cung cấp lệnh docker builder prune để dọn phần cache này — chi tiết nằm ở Bài 6 của series về dọn dẹp và tối ưu Docker. Trong quá trình học, bạn không cần lo về nó; cứ để cache giúp các lần build sau nhanh hơn.
8. Ví dụ mở rộng: Dockerfile cho ứng dụng Python
Sau khi nắm Dockerfile là gì qua ví dụ web tĩnh, hãy xem Dockerfile áp dụng cho ứng dụng thật — dưới đây là một mẫu Dockerfile điển hình cho web Python — bạn có thể lưu lại làm khung tham khảo khi cần đóng gói ứng dụng của mình:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
Điểm đáng học trong mẫu này là thứ tự chỉ thị đúng quy tắc cache vừa nêu: file requirements.txt (ít đổi) được copy và cài thư viện trước, toàn bộ mã nguồn (hay đổi) copy sau cùng. Nhờ đó sửa code hằng ngày không khiến Docker phải cài lại thư viện. Bản slim cũng được chọn thay cho bản đầy đủ để image nhẹ hơn — chủ đề tối ưu dung lượng sẽ được đào sâu ở Bài 6 của series.
9. Ba chỉ thị bổ sung đáng biết: ARG, USER và HEALTHCHECK
ARG khai báo biến chỉ tồn tại trong lúc build, truyền giá trị từ ngoài vào qua cờ --build-arg. Nó hợp cho những thứ thay đổi theo môi trường build như phiên bản phần mềm cần cài:
ARG PHP_VERSION=8.3
FROM php:${PHP_VERSION}-fpm-alpine
Khi build, gõ docker build --build-arg PHP_VERSION=8.2 . là bạn đã đổi được image nền mà không cần sửa Dockerfile. Lưu ý biến ARG không còn tồn tại khi container chạy; muốn biến môi trường lúc chạy hãy dùng ENV.
USER chỉ định tài khoản sẽ chạy tiến trình trong container thay vì root mặc định. Chạy bằng root tiện nhưng rủi ro: tiến trình bị khai thác sẽ có toàn quyền trong container. Nhiều image cung cấp sẵn user thường (như node, www-data) — chỉ cần thêm dòng USER www-data gần cuối Dockerfile là ứng dụng chạy với quyền tối thiểu cần thiết.
Cuối cùng, HEALTHCHECK khai báo một lệnh kiểm tra sức khỏe mà Docker chạy định kỳ bên trong container, ví dụ HEALTHCHECK CMD curl -f http://localhost/ || exit 1. Nhờ đó docker ps hiển thị thêm trạng thái healthy/unhealthy của container — dữ kiện rất quý khi cần biết ứng dụng “còn sống” thật hay chỉ là tiến trình treo.
10. Lỗi thường gặp khi viết Dockerfile và build image
Phần lớn vướng mắc của người mới tìm hiểu Dockerfile là gì đều rơi vào vài lỗi build lặp đi lặp lại dưới đây.
- Build báo không tìm thấy Dockerfile: bạn đang đứng sai thư mục hoặc ngữ cảnh build (dấu chấm) trỏ sai chỗ. Hãy
cdvào đúng thư mục chứa Dockerfile, hoặc chỉ rõ bằng cờ-f <đường-dẫn>. - COPY báo file không tồn tại: đường dẫn trong COPY luôn tính tương đối theo ngữ cảnh build, không phải theo vị trí Dockerfile. Cũng kiểm tra xem file có bị
.dockerignoreloại trừ nhầm không. - Nghĩ rằng EXPOSE là mở cổng: EXPOSE chỉ là dòng ghi chú tài liệu trong image. Muốn truy cập được từ ngoài, vẫn phải publish cổng bằng
-pkhi chạy container như đã học ở Bài 1. - Image nặng bất thường: nguyên nhân thường do chọn image nền bản đầy đủ (hãy cân nhắc bản
slimhoặcalpine) hoặc copy cả thư mục rác vào image vì thiếu.dockerignore. - Mỗi lần sửa code là build lại từ đầu: dấu hiệu của việc xếp sai thứ tự chỉ thị — kiểm tra lại quy tắc cache ở mục 7, đưa phần cài thư viện lên trước phần copy mã nguồn.
11. Lời kết
Đến đây bạn đã hiểu rõ Dockerfile là gì: một file công thức mô tả cách build image, với các chỉ thị cốt lõi FROM, RUN, COPY, CMD và cơ chế layer + cache quyết định tốc độ build. Hãy thử tự viết Dockerfile cho một trang web hoặc script nhỏ của riêng bạn và build vài lần để cảm nhận cơ chế cache — đó là cách nhanh nhất để thuộc bài.
Ở bài tiếp theo, chúng ta sẽ giải quyết bài toán dữ liệu: container bị xóa thì dữ liệu bên trong cũng mất, trừ khi bạn dùng Docker Volume — đó chính là chủ đề của Bài 3 trong series Docker cơ bản trên phongdinh.info.vn.

Thảo luận 0 bình luận Viết bình luận Thu gọn