Dockerfile là gì: Hướng dẫn viết Dockerfile theo cấu trúc chuẩn

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ảm build 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 container
  • node-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íDockerfileDocker Compose
Chức năng chínhTạo image cho một containerKhởi chạy và quản lý nhiều container cùng lúc
Tệp cấu hìnhDockerfiledocker-compose.yml
Cách sử dụngDùng với docker buildDùng với docker-compose up/down
Phạm vi tác độngMột image/container đơn lẻNhiều service/container và mạng lưới kết nối
Cài đặt package, mã nguồnThực hiện trực tiếp trong fileKhông, chỉ định image có sẵn hoặc build từ Dockerfile
Quản lý mạng, volume, envGiới hạnHỗ trợ đầy đủ
Tự động hóa triển khaiKhôngCó thể điều phối toàn bộ hệ thống
Khả năng dùng độc lậpCó thể dùng riêngThường phụ thuộc vào Dockerfile hoặc image
Thường dùng khiTạ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ợpCó thể kết hợp với ComposeThườ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!

TÁC GIẢ
Hiếu Phan
Hiếu Phan

Content Writer

Với kinh nghiệm hơn 2 năm sản xuất nội dung đa lĩnh vực, trong đó có cả phần mềm máy tính, Hiếu Phan mang đến cho người đọc những bài viết đa chiều cùng với độ chính xác cao và đầy đủ thông tin được cập nhật mới nhất. Hiếu luôn chủ động nghiên cứu và mang đến những nội dung, thông tin thuộc chủ đề IT Support, System, DevOps,... sát với nhu cầu người đọc nhất có thể.