Docker cho người mới: đưa app từ máy bạn vào container
Có một câu nói huyền thoại trong giới lập trình: “máy tôi chạy được mà”. App chạy ngon trên máy bạn, gửi cho đồng nghiệp thì hỏng, đưa lên máy chủ thì lỗi. Nguyên nhân gần như luôn giống nhau: máy bạn và máy kia không giống nhau về phiên bản, thư viện, cấu hình. Docker sinh ra để dẹp câu nói đó.
Container là gì, nói cho dễ hiểu
Hãy tưởng tượng bạn đóng app vào một cái hộp kín, bên trong có đủ mọi thứ app cần để chạy: đúng phiên bản ngôn ngữ, đúng thư viện, đúng cấu hình. Cái hộp đó mang đi đâu cũng chạy y hệt, vì nó không phụ thuộc vào máy bên ngoài. Cái hộp đó chính là container.
Nhiều người nhầm container với máy ảo. Khác biệt cốt lõi: máy ảo mô phỏng cả một máy tính kèm hệ điều hành riêng, rất nặng. Container thì nhẹ hơn nhiều vì chia sẻ nhân hệ điều hành của máy chủ, chỉ đóng gói app và những thứ nó cần. Kết quả là container khởi động trong vài giây và tốn ít tài nguyên.
Image và container: bản thiết kế và ngôi nhà
Đây là hai khái niệm hay bị lẫn:
- Image là bản đóng gói tĩnh, giống bản thiết kế của ngôi nhà. Nó bất biến, dùng đi dùng lại được.
- Container là một image đang chạy, giống ngôi nhà đã xây từ bản thiết kế đó. Từ một image, bạn dựng được nhiều container.
Nếu bạn từng học lập trình hướng đối tượng: image giống class, container giống object tạo ra từ class đó.
Dockerfile: công thức để đóng gói app
Để tạo image, bạn viết một tập tin tên là Dockerfile. Nó là công thức từng bước, mô tả cách dựng nên image. Đây là một ví dụ đơn giản cho một app Node:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Đọc từng dòng:
- FROM chọn một image nền để bắt đầu. Ở đây là Node phiên bản 22 trên nền Alpine, một bản Linux rất nhẹ.
- WORKDIR đặt thư mục làm việc bên trong container.
- COPY package.json ./* chép riêng tập tin khai báo thư viện vào trước. Lý do cho việc tách riêng này sẽ nói kỹ ở bài sau.
- RUN npm install cài các thư viện.
- COPY . . chép toàn bộ mã nguồn còn lại vào.
- EXPOSE khai báo app lắng nghe ở cổng nào.
- CMD là lệnh chạy khi container khởi động.
Đừng quên .dockerignore
Bên cạnh Dockerfile, hãy tạo một tập tin .dockerignore để loại những thứ không nên đưa vào image, ví dụ thư mục node_modules trên máy bạn, tập tin bí mật, hay thư mục .git. Nó hoạt động giống .gitignore và giúp image gọn, an toàn hơn:
node_modules
.git
.env
Build image và chạy container
Có Dockerfile rồi, bạn build image bằng lệnh dưới. Dấu chấm ở cuối nghĩa là “dùng thư mục hiện tại làm bối cảnh build”:
docker build -t my-app .
Chạy container từ image vừa tạo:
docker run -p 8080:3000 my-app
Chú ý đoạn -p 8080:3000. App bên trong container lắng nghe ở cổng 3000, nhưng container như một cái hộp kín, bên ngoài không tự thấy cổng đó. Lệnh này nối cổng 8080 của máy bạn vào cổng 3000 bên trong container. Giờ mở trình duyệt vào localhost:8080 là thấy app. Con số bên trái là cổng trên máy bạn, con số bên phải là cổng bên trong container.
Điều kỳ diệu ở đây: bạn không cần cài Node, không cần cài thư viện gì lên máy. Mọi thứ nằm trong container. Chính là lời hứa “mang đi đâu cũng chạy”.
Vài lệnh hay dùng
- Xem các container đang chạy: docker ps
- Xem nhật ký của một container: docker logs tên-container
- Dừng một container: docker stop tên-container
Định nghĩa xong
Bạn coi như nắm được bài này khi:
- docker build tạo ra image cho app của bạn mà không lỗi.
- docker run cho app chạy lên được, và mở trình duyệt vào đúng cổng đã map là thấy app.
- Máy chủ không cần cài sẵn ngôn ngữ hay thư viện của app, mọi thứ đã nằm gọn trong container.
Image ở bài này chạy được, nhưng có thể còn to và build chậm. Bài tiếp theo sẽ nâng cấp Dockerfile để image nhỏ lại và build nhanh hơn nhiều lần.