Dockerfile là gì? Hướng dẫn tạo Docker Image đầu tiên
Ở bài trước, chúng ta đã tìm hiểu Docker là gì, sự khác nhau giữa Docker Image và Docker Container, cũng như cách chạy một container Nginx đơn giản.
Trong bài này, chúng ta sẽ đi thêm một bước: tự đóng gói ứng dụng của mình thành Docker Image bằng Dockerfile.
Nếu bạn đang phát triển ứng dụng bằng Node.js, NestJS, Angular hoặc các công nghệ web khác thì Dockerfile là kiến thức rất quan trọng.
1. Dockerfile là gì?
Dockerfile là một file văn bản chứa các câu lệnh hướng dẫn Docker cách tạo ra một Docker Image.
Có thể hiểu đơn giản theo quy trình:
Source Code
↓
Dockerfile
↓
docker build
↓
Docker Image
↓
docker run
↓
Docker ContainerVí dụ chúng ta có một ứng dụng Node.js.
Thay vì phải cài Node.js, npm và các thư viện thủ công trên server, chúng ta có thể mô tả toàn bộ môi trường đó trong Dockerfile.
Docker sẽ dựa vào Dockerfile để tạo ra một Image hoàn chỉnh.
Hiểu đơn giản: Dockerfile giống như một “công thức”, còn Docker Image là “sản phẩm” được tạo ra từ công thức đó.
2. Cấu trúc Dockerfile cơ bản
Một Dockerfile đơn giản cho ứng dụng Node.js có thể như sau:
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]Nhìn qua có vẻ hơi nhiều câu lệnh, nhưng ý nghĩa của từng dòng khá đơn giản.
3. FROM – Chọn môi trường chạy
Dòng đầu tiên:
FROM node:22có nghĩa là chúng ta muốn sử dụng Docker Image có sẵn của Node.js phiên bản 22 làm nền tảng.
Có thể hình dung:
Node.js Image
↓
Ứng dụng của chúng ta
↓
Docker Image mớiNgoài Node.js, chúng ta cũng có thể sử dụng các image khác:
FROM nginxhoặc:
FROM ubuntu:24.04hoặc:
FROM python:3.12Tùy vào công nghệ mà ứng dụng đang sử dụng.
4. WORKDIR – Chọn thư mục làm việc
Tiếp theo:
WORKDIR /appDocker sẽ tạo và sử dụng thư mục:
/appbên trong container.
Các câu lệnh sau đó sẽ được thực hiện trong thư mục này.
Ví dụ:
/app
├── package.json
├── node_modules
├── src
└── main.js5. COPY – Copy source code vào Image
Dòng:
COPY package*.json ./sẽ copy:
package.json
package-lock.jsontừ máy của chúng ta vào thư mục /app trong Docker Image.
Sau đó:
COPY . .sẽ copy toàn bộ source code vào container.
Có thể hiểu như sau:
Máy Developer
project/
├── src/
├── package.json
└── Dockerfile
↓ COPY
Docker Image
/app/
├── src/
├── package.json
└── ...6. RUN – Chạy lệnh khi build Image
Dòng:
RUN npm installyêu cầu Docker chạy:
npm installtrong quá trình build image.
Docker sẽ cài đặt các dependency được khai báo trong package.json.
Ví dụ:
express
nestjs
rxjs
typeormsẽ được cài vào:
/app/node_modulesMột điều quan trọng cần nhớ:
RUNđược thực hiện khi build Docker Image.
Trong khi:
CMDđược thực hiện khi Container bắt đầu chạy.
7. EXPOSE – Khai báo port của ứng dụng
Nếu ứng dụng Node.js chạy ở port:
3000chúng ta có thể khai báo:
EXPOSE 3000Điều này giúp mô tả rằng ứng dụng bên trong container sử dụng port 3000.
Tuy nhiên, để truy cập ứng dụng từ máy bên ngoài, chúng ta vẫn cần map port khi chạy container.
Ví dụ:
docker run -p 3000:3000 my-appCó nghĩa là:
Máy tính Container
Port 3000 → Port 30008. CMD – Lệnh chạy ứng dụng
Dòng cuối cùng:
CMD ["npm", "start"]là lệnh sẽ chạy khi container được khởi động.
Nó tương đương với:
npm startNếu đang sử dụng NestJS production, chúng ta có thể dùng:
CMD ["node", "dist/main.js"]9. Build Docker Image
Sau khi tạo Dockerfile, cấu trúc project có thể như sau:
my-app/
├── src/
├── package.json
├── package-lock.json
└── DockerfileMở Terminal tại thư mục project và chạy:
docker build -t my-app .Trong đó:
docker buildyêu cầu Docker tạo một Image.
-t my-appđặt tên image là:
my-appDấu:
.có nghĩa là sử dụng thư mục hiện tại làm build context.
Sau khi build thành công, kiểm tra:
docker imagesBạn có thể thấy:
REPOSITORY TAG IMAGE ID
my-app latest xxxxxxxxx10. Chạy Container từ Image vừa tạo
Sau khi có Image:
my-appchúng ta có thể chạy:
docker run -d -p 3000:3000 --name my-app-container my-appGiải thích:
-dChạy container ở background.
-p 3000:3000Map port máy tính vào port container.
--name my-app-containerĐặt tên container.
my-appLà image được sử dụng.
Sau đó kiểm tra:
docker psNếu container xuất hiện trong danh sách thì ứng dụng đã chạy thành công.
[Hình minh họa: Docker Image my-app → docker run → Container → localhost:3000]
11. .dockerignore là gì?
Khi sử dụng Dockerfile, chúng ta thường nên tạo thêm file:
.dockerignoreFile này hoạt động khá giống .gitignore.
Ví dụ:
node_modules
.git
.env
dist
README.mdDocker sẽ không copy những file hoặc thư mục này vào build context.
Điều này giúp:
Docker Image nhỏ hơn.
Build nhanh hơn.
Tránh copy file không cần thiết.
Hạn chế đưa thông tin nhạy cảm vào Image.
Ví dụ bạn không nên copy .env chứa password hoặc API key vào Docker Image.
12. Một Dockerfile tốt hơn cho Node.js
Thay vì:
RUN npm installtrong môi trường CI/CD hoặc production, chúng ta thường dùng:
RUN npm ciDockerfile có thể viết như sau:
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "start"]npm ci sử dụng chính xác dependency trong package-lock.json, giúp môi trường build ổn định hơn.
13. Dockerfile trong dự án thực tế
Ví dụ chúng ta có hệ thống:
Angular
↓
NestJS
↓
MySQL
↓
RedisChúng ta có thể tạo Docker Image riêng cho từng ứng dụng.
Ví dụ:
Angular
↓
frontend-image
NestJS
↓
backend-imageSau đó chạy:
frontend-container
backend-container
mysql-container
redis-containerCác container này có thể được kết nối với nhau bằng Docker Network.
Khi số lượng container tăng lên, việc chạy từng container bằng docker run sẽ khá bất tiện.
Đây chính là lúc chúng ta sử dụng:
Docker Composeđể quản lý toàn bộ hệ thống chỉ bằng một file cấu hình.
14. Các lệnh cần nhớ
Build Image:
docker build -t my-app .Xem danh sách Image:
docker imagesChạy Container:
docker run -d -p 3000:3000 my-appXem Container đang chạy:
docker psXem log:
docker logs <container-name>Theo dõi log realtime:
docker logs -f <container-name>Dừng Container:
docker stop <container-name>Xóa Container:
docker rm <container-name>Xóa Image:
docker rmi <image-name>15. Tổng kết
Trong bài này, chúng ta đã đi từ source code đến một container hoàn chỉnh:
Source Code
↓
Dockerfile
↓
docker build
↓
Docker Image
↓
docker run
↓
Docker Container
↓
Application RunningNếu mới học Docker, bạn chỉ cần nhớ ba bước quan trọng:
1. Viết Dockerfile
Mô tả môi trường và cách chạy ứng dụng.2. Build Image
docker build3. Chạy Container
docker runSau khi hiểu được Dockerfile, Docker Image và Container, bước tiếp theo rất phù hợp là tìm hiểu Docker Compose.
Docker Compose sẽ giúp chúng ta chạy nhiều service như Angular, NestJS, MySQL và Redis cùng lúc chỉ bằng một câu lệnh.