Khi bắt đầu với Docker, Dockerfile chính là bước đầu tiên để “làm chủ” công nghệ container. Dù chỉ là một file văn bản đơn giản, Dockerfile lại đóng vai trò quan trọng trong việc tự động hóa quá trình tạo ra Docker image cho ứng dụng của bạn. Chúng ta sẽ cùng nhau tìm hiểu Dockerfile là gì, cách viết Dockerfile từ cơ bản đến nâng cao, với các ví dụ thực tế dễ hiểu – phù hợp cho cả người mới học và lập trình viên muốn làm việc hiệu quả hơn với Docker.
Đọc bài viết sau để biết thêm về:
- Tổng quan về Dockerfile
- Cấu trúc cơ bản của Dockerfile
- Hướng dẫn viết Dockerfile chi tiết từng bước
- Cách tạo image từ Dockerfile
- So sánh Docker File và Docker Compose
- Những lỗi phổ biến khi viết Dockerfile và cách khắc phục
Tổng quan về Dockerfile
Dockerfile là gì?
Dockerfile là một tập tin văn bản thuần túy (plain text file) chứa tập hợp các lệnh hướng dẫn Docker cách tạo ra một Docker image. Nói cách khác, Dockerfile chính là “công thức” giúp bạn đóng gói ứng dụng cùng toàn bộ môi trường chạy (runtime, thư viện, mã nguồn, biến môi trường, v.v.) vào trong một image duy nhất, sẵn sàng để triển khai dưới dạng container.
Mỗi dòng trong Dockerfile đại diện cho một bước trong quá trình build image, sử dụng các chỉ thị như FROM
, COPY
, RUN
, CMD
, EXPOSE
,… Khi chạy lệnh docker build, Docker sẽ đọc Dockerfile theo thứ tự từ trên xuống và tạo ra image tương ứng theo kiến trúc layer (lớp) – mỗi chỉ thị tạo ra một layer mới, giúp tối ưu hóa việc caching và tái sử dụng.
Mối liên hệ giữa Dockerfile và Image/Container
Để hiểu rõ cách Docker hoạt động, bạn cần nắm được mối liên hệ giữa Dockerfile, Docker Image và Docker Container – ba thành phần cốt lõi trong quy trình làm việc với Docker.
- Dockerfile là nơi khai báo mọi hướng dẫn để tạo ra một Docker Image. Trong đó, bạn định nghĩa hệ điều hành nền, các thư viện cần thiết, mã nguồn, tập lệnh cài đặt, cấu hình môi trường và cả lệnh khởi chạy ứng dụng.
- Khi bạn chạy lệnh docker build, Docker sẽ đọc Dockerfile và lần lượt thực hiện từng chỉ thị để tạo ra một Docker Image – đây là một bản đóng gói hoàn chỉnh, không thay đổi (immutable), chứa toàn bộ môi trường và ứng dụng của bạn. Image được cấu thành từ nhiều layer chồng lên nhau, cho phép Docker tái sử dụng các layer chung giữa các image khác nhau.
- Sau khi có image, bạn có thể chạy nó bằng lệnh docker run, lúc đó Docker sẽ tạo ra một Container – tức là một phiên bản đang hoạt động (runtime instance) của image đó. Mỗi container là một môi trường độc lập, cách ly với hệ điều hành máy chủ. Container thêm một writable layer trên cùng của image để lưu trữ các thay đổi trong quá trình chạy.
Đọc thêm: Docker Container là gì? Cách sử dụng Docker Container hiệu quả
8 chỉ thị cơ bản của mọi Dockerfile
Một Dockerfile bao gồm nhiều chỉ thị (instruction) – mỗi dòng là một lệnh giúp Docker biết cách xây dựng một image phù hợp. Việc nắm vững các chỉ thị cơ bản trong Dockerfile là bước đầu tiên để tạo ra container hoạt động ổn định và hiệu quả.
Dưới đây là các chỉ thị phổ biến nhất thường xuất hiện trong mọi Dockerfile:
FROM
Xác định image nền mà Dockerfile sẽ dựa vào. Đây luôn là chỉ thị đầu tiên và có thể xuất hiện nhiều lần trong một Dockerfile để thực hiện multi-stage builds.
Ví dụ:
FROM node:18
WORKDIR
Thiết lập thư mục làm việc bên trong container. Các lệnh tiếp theo sẽ được thực thi tại đây. Nếu thư mục chưa tồn tại, Docker sẽ tự động tạo.
Ví dụ:
WORKDIR /app
COPY
Sao chép file hoặc thư mục từ máy host vào image. Khác với ADD, COPY chỉ đơn giản sao chép file mà không giải nén archive hay download từ URL.
Ví dụ:
COPY . .
# Copy toàn bộ thư mục hiện tại vào /app (đã thiết lập từ WORKDIR)
RUN
Thực hiện một lệnh trong quá trình build image (thường để cài thư viện, build project,…). Mỗi lệnh RUN tạo ra một layer mới, nên gộp nhiều lệnh vào một RUN để tối ưu.
Ví dụ:
RUN npm install
CMD
Thiết lập lệnh mặc định sẽ được chạy khi container khởi động. Khác với ENTRYPOINT, CMD có thể bị ghi đè khi chạy docker run. Chỉ có một CMD duy nhất có hiệu lực trong Dockerfile (CMD cuối cùng sẽ ghi đè các CMD trước đó).
Ví dụ:
CMD ["node", "index.js"]
EXPOSE
Khai báo cổng (port) mà container sẽ sử dụng.
Ví dụ:
EXPOSE 3000
Lưu ý: EXPOSE chỉ có tác dụng mô tả (documentation), không thực sự publish cổng ra ngoài. Cần dùng -p khi chạy docker run để ánh xạ cổng (map port).
ENV
Thiết lập biến môi trường có hiệu lực cả trong quá trình build và khi container chạy.
Ví dụ:
ENV NODE_ENV=production
ARG
Khai báo biến truyền vào khi build image (build-time variable).
Ví dụ:
ARG APP_VERSION
ARG chỉ có hiệu lực trong quá trình build, không có hiệu lực trong container đang chạy. Có thể truyền giá trị qua --build-arg
khi build.
Cấu trúc phổ biến của một Dockerfile
Về bản chất, một Dockerfile giống như một “script hướng dẫn” cho Docker biết cách xây dựng môi trường vận hành ứng dụng từ đầu đến cuối. Do đó, để tạo ra một file đúng chuẩn, dễ bảo trì và hoạt động ổn định, thay vì viết các lệnh ngẫu nhiên, Dockerfile cần tuân theo một cấu trúc logic dưới đây:
Chọn image nền (Base image)
Dockerfile sẽ bắt đầu với một image có sẵn từ Docker Hub như Node.js, Python, Ubuntu, Nginx,…
Bước này giúp ứng dụng của bạn “kế thừa” một hệ điều hành hoặc nền tảng đã được chuẩn bị trước.
Chuẩn bị môi trường làm việc
Bao gồm thiết lập thư mục làm việc (WORKDIR), các biến môi trường, quyền truy cập… để sẵn sàng cho việc sao chép và xử lý mã nguồn.
Đây là bước “set up” không gian làm việc trong container.
Copy mã nguồn & cài đặt phụ thuộc
Dockerfile sẽ sao chép các file cấu hình dependency trước (như package.json, requirements.txt), cài đặt dependencies, sau đó mới sao chép toàn bộ mã ứng dụng vào container, sau đó chạy lệnh để cài đặt các dependency (ví dụ: npm install, composer install,…).
Đây là bước “build” chính, nơi toàn bộ code và thư viện được tích hợp.
Cấu hình thông tin vận hành
Bạn có thể thiết lập biến môi trường (ENV), khai báo port sẽ sử dụng (EXPOSE), hoặc phân quyền nếu cần.
Bước này nhằm đảm bảo container tương tác được với bên ngoài một cách an toàn và đúng mục đích.
Khởi động ứng dụng khi container chạy
Cuối cùng, Dockerfile chỉ định lệnh mặc định sẽ thực thi khi container bắt đầu – như khởi chạy server, ứng dụng, hoặc script chính.
Bước này biến container từ “đóng gói” thành “hoạt động”.
Hướng dẫn 8 bước viết Dockerfile chi tiết
Sau khi bạn đã nắm toàn bộ cấu trúc Docker File và hiểu mục đích của các bước, phần dưới đây sẽ hướng dẫn bạn viết Dockerfile chi tiết:
Bước 1: Khởi tạo Dockerfile
Trong thư mục gốc của dự án, tạo một file tên là Dockerfile (viết hoa chữ D, không có đuôi .txt hay .yaml).
touch Dockerfile
Gợi ý: Đặt Dockerfile cùng cấp với file chính (ví dụ: index.js, app.py, composer.json, v.v.)
Bước 2: Chọn base image (image nền)
Sử dụng chỉ thị FROM để chọn nền tảng cho ứng dụng. Base image là môi trường hệ điều hành hoặc runtime đã được cấu hình sẵn (Node, PHP, Python,…).
Ví dụ với Node.js:
FROM node:18
Các lưu ý quan trọng:
- Dùng phiên bản chính xác để tránh lỗi không tương thích, ví dụ dùng node:18-alpine nếu muốn image nhẹ hơn (Alpine Linux ~5MB; Ubuntu ~72MB).
- Luôn dùng tag cụ thể (ví dụ
node:18
,python:3.11
) thay vìlatest
để đảm bảo build nhất quán. - Kiểm tra CVE (Common Vulnerabilities and Exposures – lỗ hổng bảo mất) của base image thường xuyên.
Bước 3: Đặt thư mục làm việc trong container
Dùng lệnh:
WORKDIR /app
Lệnh này tạo (nếu chưa có) và chuyển vào thư mục /app bên trong container. Từ đây, mọi lệnh tiếp theo như COPY, RUN,… sẽ thực thi trong thư mục /app.
Bước 4: Copy mã nguồn và file cài đặt
Docker sử dụng layer caching, vì vậy bạn nên copy trước các file khai báo dependency (như package.json, composer.json,…) để tận dụng cache.
Ví dụ với Node.js:
COPY package*.json .
RUN npm install
COPY . .
Các lưu ý quan trọng:
- Nếu không tách COPY package*.json riêng, Docker sẽ phải cài lại toàn bộ mỗi lần build, dù mã nguồn không đổi.
- Sử dụng
./
thay vì.
để rõ ràng hơn về nơi file được sao chép đến. - Trong production, nên dùng
npm ci
thay vìnpm install
để cài đặt nhanh và chuẩn xác hơn. - Có thể thêm
npm cache clean --force
để giảm kích thước image.
Bước 5: Thiết lập biến môi trường (nếu cần)
Ví dụ dùng lệnh ENV:
ENV NODE_ENV=production
Các biến môi trường được khai báo bằng ENV sẽ có sẵn khi ứng dụng khởi chạy. ENV khác với ARG ở chỗ ENV có hiệu lực cả trong build time và runtime, trong khi ARG chỉ có hiệu lực trong build time.
Bước 6: Khai báo port container sẽ dùng
Khi container chạy, bạn cần xác định ứng dụng sẽ “lắng nghe” ở cổng nào để có thể truy cập từ bên ngoài.
Ví dụ:
EXPOSE 3000
Như vậy, Docker sẽ biết rằng container đang “lắng nghe” cổng 3000 – bạn có thể ánh xạ cổng này ra bên ngoài khi chạy container.
Bước 7: Khai báo lệnh chạy khi container khởi động
Sử dụng CMD để định nghĩa lệnh mặc định khi container bắt đầu:
CMD ["node", "index.js"]
Bước 8: Tạo file .dockerignore (Rất quan trọng!)
File .dockerignore giúp loại bỏ các file không cần thiết khỏi quá trình build, như node_modules, .git, *.log,… Điều này rất quan trọng trong việc giảm build context size và tăng tốc độ build.
Ví dụ:
node_modules
.git
Dockerfile
README.md
*.log
Ví dụ Dockerfile hoàn chỉnh (cho ứng dụng Node.js)
# 1. Chọn môi trường nền (sử dụng Alpine để giảm kích thước)
FROM node:18-alpine
# 2. Tạo user non-root để tăng bảo mật
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 3. Đặt thư mục làm việc
WORKDIR /app
# 4. Copy file khai báo phụ thuộc và cài đặt (tối ưu caching)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# 5. Copy toàn bộ mã nguồn
COPY . .
# 6. Chuyển ownership cho non-root user
RUN chown -R nextjs:nodejs /app
USER nextjs
# 7. Thiết lập biến môi trường và cổng
ENV NODE_ENV=production
EXPOSE 3000
# 8. Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# 9. Lệnh chạy khi container khởi động
CMD ["node", "index.js"]
Cách tạo image từ Dockerfile
Sau khi đã viết xong một Dockerfile chuẩn, bước tiếp theo là build image từ file đó. Image chính là “khuôn mẫu” để Docker tạo ra container – môi trường chạy ứng dụng của bạn.
Bước 1: Kiểm tra cấu trúc thư mục
Trước khi build image, hãy đảm bảo rằng bạn đã có:
/my-app/
├── Dockerfile
├── .dockerignore
├── package.json (hoặc requirements.txt, composer.json,...)
├── mã nguồn ứng dụng
Lưu ý:
- Dockerfile phải nằm ở thư mục gốc hoặc thư mục bạn chỉ định khi build.
- Đảm bảo
.dockerignore
đã loại trừ các file không cần thiết để giảmbuild context size-
. - Kiểm tra quyền truy cập file nếu build trên Linux/macOS.
Bước 2: Dùng lệnh Docker build
Mở terminal và điều hướng đến thư mục chứa Dockerfile, sau đó dùng lệnh:
docker build -t ten-image .
Trong đó:
docker build
: lệnh dùng để tạo image từ Dockerfile-t ten-image
: đặt tên cho image (tag). Ví dụ:myapp:v1
,node-api:latest
.
(dấu chấm): đại diện cho thư mục hiện tại – nơi chứa Dockerfile
Ví dụ:
docker build -t node-app:1.0 .
Kết quả: Docker sẽ đọc Dockerfile, thực hiện lần lượt các chỉ thị (FROM, COPY, RUN,…) và tạo ra một Docker image tên là node-app với tag 1.0.
Bước 3: Kiểm tra image đã tạo
Dùng lệnh sau để liệt kê tất cả các image có trong máy:
docker images
Bạn sẽ thấy kết quả tương tự:
REPOSITORY TAG IMAGE ID CREATED SIZE
Node-app. 1.0 83faab2c3a22 2 minutes ago 150MB
Bước 4: Chạy container từ image mới tạo
Sau khi tạo image từ Dockerfile, bạn có thể chạy container từ image đó bằng lệnh sau:
docker run -d -p 3000:3000 --name ten-container node-app:1.0
Trong đó:
-d
: chạy container ở chế độ nền-p
: ánh xạ port từ máy host (bên trái) sang container (bên phải)--name
: đặt tên containernode-app:1.0
: image vừa tạo
So sánh Dockerfile và Docker Compose
Dockerfile và Docker Compose đều là công cụ hỗ trợ triển khai ứng dụng với Docker. Tuy nhiên, chúng phục vụ các mục đích khác nhau:
- Dockerfile được dùng để tạo Docker image. Nó mô tả cách xây dựng một môi trường container cụ thể, bao gồm hệ điều hành, thư viện, mã nguồn và lệnh chạy ứng dụng. Bạn dùng lệnh docker build để thực thi nội dung trong Dockerfile và sinh ra một image.
- Docker Compose dùng để quản lý và khởi chạy nhiều container cùng lúc (ví dụ: một ứng dụng backend + frontend + database). Bạn định nghĩa các service trong file docker-compose.yml, sau đó dùng docker-compose up để khởi động toàn bộ hệ thống.
Điểm khác biệt chính:
- Dockerfile tạo ra một image duy nhất. Docker Compose khởi động nhiều container (từ nhiều image) và kết nối chúng với nhau.
- Dockerfile tập trung vào việc build, còn Docker Compose tập trung vào việc run & orchestrate (điều phối container).
- Trong thực tế, Docker Compose thường sử dụng Dockerfile để build image trước khi chạy container.
Dưới đây là bảng so sánh chi tiết:
Tiêu chí | Dockerfile | Docker Compose |
Chức năng chính | Tạo image cho một container | Khởi chạy và quản lý nhiều container cùng lúc |
Tệp cấu hình | Dockerfile | docker-compose.yml |
Cách sử dụng | Dùng với docker build | Dùng với docker-compose up/down |
Phạm vi tác động | Một image/container đơn lẻ | Nhiều service/container và mạng lưới kết nối |
Cài đặt package, mã nguồn | Thực hiện trực tiếp trong file | Không, chỉ định image có sẵn hoặc build từ Dockerfile |
Quản lý mạng, volume, env | Giới hạn | Hỗ trợ đầy đủ |
Tự động hóa triển khai | Không | Có thể điều phối toàn bộ hệ thống |
Khả năng dùng độc lập | Có thể dùng riêng | Thường phụ thuộc vào Dockerfile hoặc image |
Thường dùng khi | Tạo môi trường chạy ứng dụng cho một service cụ thể | Chạy hệ thống nhiều thành phần liên kết để build image |
Khả năng kết hợp | Có thể kết hợp với Compose | Thường sử dụng Dockerfile để build image hoặc pull từ registry |
Những lỗi phổ biến khi viết Dockerfile và cách khắc phục
Việc viết Dockerfile trên thực tế rất dễ mắc lỗi, đặc biệt với người mới bắt đầu. Dưới đây là các lỗi thường gặp nhất khi làm việc với Dockerfile và cách xử lý hiệu quả để tránh ảnh hưởng đến hiệu suất hoặc tính ổn định của image.
Dùng quá nhiều layer không cần thiết
- Vấn đề:
Mỗi chỉ thị (RUN, COPY, ADD…) trong Dockerfile tạo ra một layer mới. Việc viết nhiều lệnh RUN riêng lẻ dẫn đến image lớn không cần thiết.
Ví dụ lỗi:
RUN apt-get update
RUN apt-get install -y curl
- Cách khắc phục:
Gộp các lệnh thành một dòng để tối ưu layer:
RUN apt-get update && apt-get install -y curl
Không sử dụng .dockerignore
- Vấn đề:
Nếu không dùng .dockerignore, Docker sẽ copy toàn bộ file/folder trong thư mục build, bao gồm cả file log, node_modules, .git… Hậu quả là Image to, build lâu, lộ dữ liệu nhạy cảm.
- Cách khắc phục:
Tạo file .dockerignore với nội dung:
node_modules
.git
*.log
.env
Sử dụng image nền không phù hợp
- Vấn đề:
Dùng image quá lớn như ubuntu trong khi chỉ cần base nhẹ như alpine, dẫn đến image nặng, tốn tài nguyên.
- Cách khắc phục:
Chọn image base nhỏ gọn:
FROM node:18-alpine
Không pin (fix) phiên bản gói cài đặt
- Vấn đề:
Không chỉ định phiên bản (latest hoặc bỏ version) dẫn đến mất kiểm soát môi trường khi image được rebuild sau này.
- Cách khắc phục:
Luôn ghi rõ version:
RUN apt-get install -y nginx=1.18.*
Chạy lệnh không cần thiết trong môi trường production
- Vấn đề:
Thêm tool debug, test hoặc dependency dev trong image chạy production làm tăng dung lượng và rủi ro bảo mật.
- Cách khắc phục:
Tách Dockerfile theo môi trường (Dockerfile.dev, Dockerfile.prod) hoặc dùng ARG để linh hoạt theo mục đích.
Thiếu CMD hoặc ENTRYPOINT
- Vấn đề:
Không có lệnh nào để chạy container => Container khởi động xong rồi dừng ngay lập tức.
- Cách khắc phục:
Thêm lệnh khởi động cuối file:
CMD ["npm", "start"]
Dùng ADD thay vì COPY khi không cần thiết
- Vấn đề:
ADD có thêm chức năng giải nén và tải từ URL, nhưng dùng không đúng mục đích sẽ gây khó kiểm soát.
- Cách khắc phục:
- Dùng COPY cho file nội bộ
- Chỉ dùng ADD khi cần tính năng đặc biệt (giải nén file .tar,…)
Các câu hỏi thường gặp về Dockerfile
CMD và ENTRYPOINT trong Dockerfile khác nhau như thế nào?
Trong Dockerfile, cả CMD và ENTRYPOINT đều dùng để chỉ định lệnh sẽ chạy khi container khởi động, nhưng chúng có cách hoạt động khác nhau:
- ENTRYPOINT cố định lệnh chính và không thể bị override khi chạy docker run.
- CMD cung cấp giá trị mặc định có thể bị ghi đè hoàn toàn khi chạy container.
Khi kết hợp cả hai trong Dockerfile, CMD sẽ được truyền làm đối số cho ENTRYPOINT.
Dockerfile nên đặt ở đâu trong dự án?
Trong một dự án, Dockerfile thường được đặt tại thư mục gốc để dễ quản lý và build image nhanh chóng. Việc đặt Dockerfile ở cấp cao nhất giúp Docker dễ truy cập toàn bộ mã nguồn và tài nguyên cần thiết. Nếu cần tổ chức riêng cho từng service, bạn có thể tạo nhiều Dockerfile trong từng thư mục con và chỉ định đường dẫn khi build.
Có thể có nhiều CMD trong Dockerfile không?
Trong Dockerfile, bạn có thể khai báo nhiều lệnh CMD, nhưng chỉ lệnh CMD cuối cùng mới có hiệu lực. Các lệnh CMD trước đó sẽ bị ghi đè trong quá trình build. Do đó, khi viết Dockerfile, hãy đảm bảo CMD cuối phản ánh đúng lệnh khởi động bạn muốn sử dụng cho container.
Tổng kết Dockerfile là gì
Việc nắm vững cách viết và sử dụng Dockerfile là bước quan trọng giúp bạn xây dựng môi trường phát triển và triển khai ứng dụng hiệu quả, nhất quán và có thể tái sử dụng. Từ cấu trúc cơ bản đến các chỉ thị thường dùng, mỗi phần trong Dockerfile đều đóng vai trò then chốt trong quá trình tạo image tối ưu. Hy vọng qua bài viết này, bạn đã có cái nhìn rõ ràng và có thể tự tin tạo ra Dockerfile phù hợp với dự án của mình. Đừng quên kết hợp với Docker Compose nếu ứng dụng của bạn cần nhiều service cùng lúc!