Container hóa (containerization) cho phép chúng ta đóng gói ứng dụng Node.js cùng tất cả các thư viện và các thành phần phụ thuộc của nó vào một container nhỏ gọn, có khả năng chạy nhất quán trên mọi môi trường (từ máy local của bạn đến môi trường production triển khai trong thực tế). Kết quả là “không còn vấn đề works on my machine” – tức là một khi container đã chạy được ở đâu thì sẽ chạy được y hệt ở tại mọi nơi hay mọi môi trường khác. Bài viết này sẽ trình bày chi tiết từng bước: Từ cấu trúc dự án, viết Dockerfile, dựng image, đẩy image lên registry, chạy container, đến việc kiểm thử ứng dụng trên máy client. Tất cả sẽ được minh họa bằng một ví dụ Node.js đơn giản.
Nếu trên máy tính hay server của bạn chưa có môi trường Docker, hãy tham khảo bài viết này của mình để setup môi trường docker nhé: Tạo máy chủ Web Server Nginx hoàn chỉnh chỉ trong vài phút với Docker – Luu Ho Phuong Blog
Contents
- 1 Cấu trúc ví dụ của ứng dụng Node.js
- 2 Tạo Dockerfile và .dockerignore
- 3 Để download thư mục và các files của dự án này hãy làm các bước sau:
- 4 Xây dựng Docker image (docker build)
- 5 Đẩy image lên Docker Hub (Registry)
- 6 Chạy ứng dụng dưới dạng container (docker run)
- 7 Tiếp theo chúng ta test lại image bằng cách pull từ Docker Hub (clean test)
- 8 Bước 1 – Stop container myapp đang chạy
- 9 Bước 2 – Xóa container myapp
- 10 Bước 3 – Xóa image myapp khỏi server
- 11 Bước 4 – Pull image myapp từ Docker Hub về Server
- 12 Bước 5 – Chạy container và test ứng dụng
- 13 Checklist cuối (rất quan trọng) để bạn kiểm tra các bước làm cho phần này
- 14 Tổng kết và một số lưu ý
Cấu trúc ví dụ của ứng dụng Node.js
Giả sử chúng ta có một dự án Node.js đơn giản có cấu trúc thư mục như sau:
-
app.js– file chính của ứng dụng Node.js, có thể là một Express server. Ví dụ, nội dung có thể như sau:Đoạn code trên khởi tạo một Express app đơn giản, lắng nghe cổng 8080 (mặc định), và trả về trang
home.pugtại route/. Khi chạynode app.js, trình duyệt truy cập theo địa chỉhttp://localhost:8080/sẽ thấy nội dung do Pug sinh ra. -
package.json– khai báo thông tin dự án và thư viện cần thiết. Ví dụ:File trên định nghĩa tên gói là
myapp, các phụ thuộc gồmexpressvàpug. Chúng ta sẽ cài đặt các gói này bằng cách chạy lệnhnpm installtrong thư mục dự án. -
README.md– hướng dẫn hoặc mô tả dự án. Ví dụ ngắn gọn:Nội dung
README.mdkhông quan trọng cho container, nhưng giúp người khác biết cách khởi động ứng dụng. -
views/home.pug– mẫu giao diện Pug cho trang chủ. Ví dụ:Với
title = "MyApp Home", khi Express gọires.render('home', { title }), trang sẽ hiển thị tiêu đề và dòng chào.
Như vậy, chúng ta đã có một ứng dụng Node.js cơ bản, sẵn sàng để container hóa. Mục tiếp theo sẽ hướng dẫn bạn viết các file cấu hình cần thiết cho Docker.
Tạo Dockerfile và .dockerignore
Để đóng gói ứng dụng Node.js thành một Docker image, bước quan trọng đầu tiên là viết Dockerfile. Đây là file chỉ dẫn Docker cách thức xây dựng image, gồm cơ bản các bước: Lấy base image Node.js, cài đặt thư viện, copy mã nguồn, cấu hình cổng, và lệnh chạy. Ngoài ra, chúng ta cũng nên tạo file .dockerignore (tương tự .gitignore) để loại bỏ các thư mục không cần thiết (như node_modules, log) khỏi build context, giúp image sạch và nhỏ gọn hơn.
Ví dụ, nội dung có thể như sau:
-
.dockerignore:Như [2] đã gợi ý, chúng ta liệt kê những mục không cần sao chép vào image: thư mục
node_modules(sẽ được cài mới trong container), file log, chínhDockerfile, và file.dockerignorenữa. -
Dockerfile: dưới đây là một Dockerfile mẫu cho Node.js (trong đóapp.jslà file khởi chạy):LABEL org.opencontainers.image.authors="@phuongluuho"Giải thích ngắn các chỉ thị lệnh Docker trên:
-
FROM node:current-alpine: sử dụng Node.js mới nhất phiên bản Alpine (nhỏ gọn) làm Node.js. Sử dụng base image có dung lượng nhẹ (ví dụnode:alpinehoặcnode:lts-bullseye-slim) giúp giảm dung lượng image. -
WORKDIR /app: chuyển thư mục làm việc sang/apptrong container. Mọi lệnh sau đó đều thực thi trong thư mục này. -
COPY package*.json ./vàRUN npm install: copy filepackage.json(vàpackage-lock.jsonnếu có) vào container rồi cài đặt dependencies. Thao tác này tách biệt để tận dụng cache của Docker: nếu file package không thay đổi, Docker sẽ không chạy lại lệnhnpm install, giảm thời gian build. -
COPY . .: copy tất cả mã nguồn còn lại vào container. Lưu ý.dockerignoređã loại bỏnode_modulesnên không copy thư mục này. -
EXPOSE 8080: khai báo cho Docker biết container sẽ nghe cổng 8080. -
CMD ["node", "app.js"]: lệnh mặc định chạy khi container khởi động, sẽ khởi động Node server. (Nếu file chính làindex.js, chúng ta sẽ chạynode index.jsthay thế.)
-
Sau khi có Dockerfile và .dockerignore như trên, chúng ta đã sẵn sàng bước build Docker image.
Để download thư mục và các files của dự án này hãy làm các bước sau:
Bạn hãy sử dụng lệnh git để lấy (clone) repo https://github.com/phuongluuho/myapp.git về máy tính của bạn rất đơn giản 👇
Sau đó vào thư mục dự án với lệnh sau:
Xây dựng Docker image (docker build)
Với Dockerfile đã định nghĩa, bước tiếp theo là build image. Giả sử bạn đang ở trong thư mục myapp, chúng ta chạy lệnh:
-
Tham số
-t <DOCKER_USERNAME>/myapp:1.0đặt tên (repository) và tag cho image. Thông thường, chúng ta nên gắn thêm tên đăng nhập Docker Hub ở đầu (ví dụluuhophuong/myapp:1.0), để thuận tiện khi đẩy lên registry. Ví dụ, lệnh mẫu trong tài liệu Docker làdocker build -t <DOCKER_USERNAME>/getting-started-todo-app .. -
Dấu
.ở cuối chỉ định build context là thư mục hiện tại. Docker sẽ lấy fileDockerfilevà các file trong thư mục này (trừ các mục trong.dockerignore) để tạo image.
Sau khi chạy docker build, Docker sẽ thực hiện các bước tức là chạy lần lượt các lệnh trong Dockerfile , hiển thị log tiến trình. Ví dụ đầu ra có thể cho biết các layer được tạo. Khi hoàn tất, bạn sẽ có một image mới có tên <DOCKER_USERNAME>/myapp với tag 1.0.

Bạn có thể kiểm tra bằng lệnh:
Lệnh này liệt kê các image đã cài trên máy tính của bạn, và bạn sẽ thấy dòng DOCKER_USERNAME/myapp 1.0 ... cùng dung lượng tương ứng. (Nếu bạn quên thêm <DOCKER_USERNAME>/, file image sẽ có tên là myapp:1.0 trong local mà thôi.)

Tóm lại, bước này đã tạo ra một Docker image chứa toàn bộ ứng dụng dự án Node.js của bạn. Tiếp theo, chúng ta sẽ đẩy image này lên một registry để chia sẻ cho mọi người.
Đẩy image lên Docker Hub (Registry)
Khi đã có image trên máy tính, chúng ta cần chia sẻ nó cho các thành viên khác hoặc dùng trong máy tính khác, nên thường đẩy lên một public registry như Docker Hub hoặc Repo của riêng bạn. Docker Hub là registry phổ biến nhất bạn có thể tạo 1 account cho bạn trên trang này. Trước khi đẩy, bạn cần đăng nhập vào tài khoản Docker Hub (với lệnh docker login) và cần có một repository trên đó (bạn có thể tạo repo có tên là myapp trên trang web Docker Hub).
Giả sử bạn đã có docker login và repository DOCKER_USERNAME/myapp trên Docker Hub, hãy gán đúng tên cho image (nếu bạn chưa gán khi build). Bạn có thể gán lại tag bằng lệnh docker tag, ví dụ:
nhưng nếu đã build đúng với tên DOCKER_USERNAME/myapp:1.0, bước này có thể bỏ qua.
Nếu chưa có repo hãy click vào Create a repository để tạo 1 repo và đặt tên cho nó là myapp.

Để Login từ terminal của bạn vào docker hub registry, từ trình duyệt web của bạn click vào account của bạn > click Account settings:

Tiếp theo để tạo token cho login vào tài khoản Docker Hub của bạn. Cuộn xuống dưới phía các menus bên phải click vào Personal access tokens> click Generate new token:

Trong màn hình Create access token (có nghĩa là tạo một token cho việc login), bạn hãy chọn các mục mô tả về token, Expiration date (ngày hết hạn của token) là Custom, và chọn ngày tiếp theo Date, và thời gian hết hạn của token là Time, chọn Access permessions với quyền: Read, Write, Delete> Click Generate để tạo token:

Sau đó copy các lệnh như hình dưới và chạy trong terminal của bạn để logging vào Repo của bạn:

Bạn sẽ thấy Login Succeeded, như vậy bạn đã login vào Docker Hub của bạn thành công.

Sau đó, dùng lệnh push:
Lệnh docker push sẽ upload image của bạn lên Docker Hub (theo namespace/repo mà bạn chỉ định). Ví dụ, tài liệu hướng dẫn Docker chỉ docker push <DOCKER_USERNAME>/getting-started-todo-app. Quá trình này có thể mất một lúc tùy theo dung lượng và tốc độ mạng internet của bạn. Khi xong, mọi layer của image sẽ được lưu trên registry của bạn, và đồng nghiệp hay máy khác có thể chạy lệnh docker pull DOCKER_USERNAME/myapp:1.0 để download image về máy tính của bạn và chạy được dự án này.
(Thực tế, bạn cũng có thể dùng registry riêng như GitHub Container Registry, nhưng về cơ bản lệnh docker push tương tự, chỉ cần đổi tên host.)

Chạy ứng dụng dưới dạng container (docker run)
Bây giờ image đã được upload lên registry, chúng ta có thể chạy container từ image này. Ví dụ, để chạy cục bộ trong máy tính của bạn, dùng lệnh:
Giải thích:
-
-d(detached) để chạy container ngầm nền. Nếu bạn bỏ tùy chọn-d, container sẽ chạy ở foreground trong terminal. -
-p 8080:8080ánh xạ cổng 8080 của máy chủ (host) sang cổng 8080 trong container. Cách này cho phép chúng ta có thể truy cập ứng dụng từ bên ngoài container thông qua cổng 8080. -
DOCKER_USERNAME/myapp:1.0là tên image (với tag) mà bạn vừa tạo/đẩy. Lần đầu docker sẽ tự động download image nếu chưa có (nếu bạn đang chạy trên máy khác chưa có image).
Sau khi lệnh docker run thực hiện, container sẽ chạy và khởi động server Node. Bạn có thể kiểm tra bằng lệnh docker ps, sẽ thấy tên container và cổng được publish (ví dụ 0.0.0.0:8080->8080/tcp). Để dừng container, dùng docker stop <CONTAINER_ID> hoặc docker rm -f.

Cuối cùng, để kiểm thử website, mở trình duyệt trên máy (hay máy khách khác trong mạng LAN) và truy cập http://localhost:8080/ (nếu bạn đang dùng máy host đó), hoặc http://<địa_chỉ_IP_của_host>:8080/ (nếu test từ máy khác trong cùng mạng). Ví dụ, tài liệu đã hướng dẫn khi chạy docker run -p 8080:8080 my-node-app, bạn “mở http://localhost:8080 — ứng dụng đang chạy trong container!. Tương tự, bạn sẽ thấy hiển thị trang chủ do file home.pug render, với tiêu đề “MyApp Home”.
Nếu mọi thứ hoạt động, bạn đã thành công trong việc đóng gói và chạy ứng dụng Node.js dưới dạng Docker container.

Tiếp theo chúng ta test lại image bằng cách pull từ Docker Hub (clean test)
Mục tiêu của phần này là chứng minh image trên registry chạy độc lập, không phụ thuộc image local.
Đây là bước rất quan trọng để đảm bảo:
-
Image push lên là dùng được
-
Người khác pull image về chạy được ngay
-
Không “ăn gian” image còn sót trên server
Bước 1 – Stop container myapp đang chạy
Trước hết, liệt kê container với lệnh:
Ví dụ output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
752023ee26cf phuongluuho/myapp:1.0 “docker-entrypoint.s…” 8 minutes ago Up 8 minutes 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp dazzling_knuth
Và cần phải Stop container để nó không chạy trên local nữa:
Hoặc nếu bạn đặt tên container là myapp khi run:
👉 Mục đích: đảm bảo app không còn chạy trên local nữa.

Bước 2 – Xóa container myapp
Sau khi stop, container vẫn còn tồn tại trên local, vậy chũng ta cần xóa hẳn nó đi.
Liệt kê các containers đã bị dừng:
Tiếp theo chúng ta xóa container:
Hoặc lệnh:
👉 Mục đích: xóa instance container, để tránh nhầm lẫn khi thực hiện lấy image từ Registry và chạy để test lại.

Bước 3 – Xóa image myapp khỏi server
Bước này rất quan trọng.
Nếu không xóa image local, Docker sẽ dùng image cũ do đó chúng ta không test được việc lấy image từ registry và chạy lại trên server.
Liệt kê image để xem các images có trong local:
Ví dụ:
Chạy lệnh xóa image:
Nếu Docker báo image đang được dùng → kiểm tra lại xem còn container nào sót không.
👉 Sau bước này chúng ta có thể thấy:
-
Server KHÔNG còn image
myapp -
Nếu muốn chạy dự án Node.js của chúng ta thì bắt buộc phải pull từ registry

Bước 4 – Pull image myapp từ Docker Hub về Server
Bây giờ chúng ta pull lại image từ repo của bạn:
Output kết quả của lệnh bạn sẽ thấy đại loại như:
Chúng ta kiểm tra lại xem image đã có trong server chưa, chạy lệnh sau:
👉 Nếu thấy image xuất hiện như vậy pull là OK mọi thứ hoạt động tốt.

Bước 5 – Chạy container và test ứng dụng
Chạy container với lệnh sau
Giải thích nhanh:
-
-d: chạy nền -
--name myapp: đặt tên container cho dễ quản lý -
-p 8080:8080: map port host ↔ container -
phuongluuho/myapp:1.0: image vừa pull
Kiểm tra lại xem container đã chạy và cung cấp dịch vụ chưa:
Test từ server (curl)
Nếu trả về HTML từ home.pug như vậy app đang chạy OK.

Test từ máy client khác
Từ một máy client trong cùng mạng:
Hoặc mở trình duyệt trong local của bạn:
👉 Nếu thấy trang MyApp Home như vậy chúng ta test thành công.
Checklist cuối (rất quan trọng) để bạn kiểm tra các bước làm cho phần này
✔ Container cũ đã stop
✔ Container cũ đã xóa
✔ Image local đã xóa
✔ Image được pull từ registry
✔ App chạy bình thường sau khi pull
👉 Nếu tất cả đều OK, bạn có thể khẳng định:
“Image myapp của tôi là self-contained, portable và deploy được trong bất kỳ môi trường của server nào có setup Docker.”
Tổng kết và một số lưu ý
Qua các bước trên, bạn đã biến một ứng dụng Node.js “bình thường” thành một Docker image có thể chia sẻ và triển khai mọi nơi. Docker giúp đóng gói ứng dụng cùng môi trường của nó, nhờ đó quá trình từ mã nguồn đến chạy container trở nên nhất quán. Một vài lưu ý bổ sung:
-
.dockerignore: luôn dùng để loại trừ file/thư mục không cần thiết (nhưnode_modules) khi build. Điều này giúp image của bạn nhỏ gọn và quá trình build nhanh hơn. -
Hãy tag thật rõ ràng: Khi build hoặc tag image, hãy dùng tên và tag có ý nghĩa (ví dụ tên người dùng Docker và phiên bản) thay vì chỉ
latest. Tài liệu Docker khuyên “đừng dùng taglatesttrong môi trường production” vì có thể gây ra những bất ngờ không mong muốn. -
Dockerfile: sử dụng base image phù hợp. Ở ví dụ trên ta dùng
node:alpineđể giảm dung lượng. Cũng nên tránh chạy ứng dụng dưới quyềnroottrong container, và nếu cần build phức tạp có thể xem xét multi-stage build để tạo image nhỏ và an toàn hơn. -
Kiểm thử: Như trình bày, sau khi chạy container, luôn đảm bảo ánh xạ đúng cổng và mở firewall cho phép truy cập. Mở
http://localhost:8080(hoặc địa chỉ IP của máy chủ) sẽ cho thấy giao diện của ứng dụng.
Với quy trình này, bạn có thể chia sẻ image đã tạo bằng cách push lên Docker Hub (hoặc registry khác), và các thành viên dự án khác chỉ cần chạy lệnh docker pull và docker run để chạy ứng dụng trên máy tính của họ. Docker thực sự giúp quá trình deploy ứng dụng trở nên đơn giản, nhất quán, và đảm bảo “ứng dụng chạy giống nhau trên mọi máy tính.
Chúc mừng bạn! Bạn đã hoàn thành việc container hóa ứng dụng Node.js, từ gói gọn app thành Docker image, đẩy lên registry, cho đến chạy và kiểm thử trên máy khách. Hãy tiếp tục khám phá các khả năng khác của Docker như sử dụng Docker Compose, quản lý dữ liệu đa container, hoặc triển khai lên cloud khi cần mở rộng trong tương lai.
Nếu bạn thấy hướng dẫn này hữu ích, đừng quên nhấn Like và share bài viết, bấm Đăng ký kênh youtube của mình và để lại bình luận về chủ đề #Docker hoặc #DevOps mà bạn muốn mình thực hiện trong tương lai. Mình đọc tất cả bình luận và luôn lựa chọn những chủ đề thiết thực, thực tế nhất để hướng dẫn cho các bạn.
Mình là Phương, và sứ mệnh của mình là giúp bạn làm chủ công nghệ nhanh hơn, hiệu quả hơn và thực tế hơn. Hẹn gặp lại các bạn trong bài viết/video tiếp theo!
#Docker#NodeJS#Containerization#DockerTutorial#NodeJSDeveloper#DockerForDevelopers#BackendDevelopment
#DevOps#DockerImage#DockerHub#ContainerizeApp#CloudNative#LapTrinh#BlogKyThuat#DevViet




