Dockerize project NodeJS, MongoDB, Redis, Passport
Cập nhật gần nhất: 10/11/2024
Xin chào các bạn quay trở lại với Series học Docker và.... "Rồi rồi biết rồi bài nào cũng có mỗi 1 câu chào" 😂😂
Ở bài trước chúng ta đã cùng nhau dockerize ứng dụng Laravel, hiểu được EXPOSE là gì, sự khác nhau giữa EXPOSE và MAPPING PORT, cách các container giao tiếp với nhau, cùng với đó là chút chút kiến thức về Linux và webserver(nginx)
Và ta sẽ tiếp tục series với việc Dockerize ứng dụng NodeJS, MongoDB, Redis, Passport nhé .
Lí do mình tách bài này ra thành một bài riêng so với bài Dockerize ứng dụng NodeJS vì mình không muốn các bạn bị bội thực ngay từ ban đầu (mình thương người lắm 😊😊)
Bắt đầu từ bài này chúng ta sẽ thực hành nâng cao hơn chút đó là ta sẽ dockerize những project giống như lúc ta làm thật, đầy đủ các thứ như databse, redis, cấu hình cho môi trường dev và deploy, và ta sẽ thử deploy trực tiếp project nodejs ở server thật xem sao nhé 😉
Bắt đầu thôi nào
Tiền Setup
Lại tiền setup, bài nào cũng thấy tiền setup 😝😝. Vì có thể có nhiều bạn đọc trực tiếp bài này của mình mà không đọc các bài trước dẫn tới việc các bạn ấy chưa chuẩn bị sẵn "đạn dược" 💪💪
Nhớ check là các bạn đã cài Docker và Docker-compose rồi nhé. Ở thời điểm hiện tại 2025 thì Docker compose đã được tích hợp luôn vào Docker cli rồi. Nếu các bạn dùng docker bản cũ mà không có thì nhớ check lại phần cuối bài trước của mình để biết cách cài đặt nhé.
Setup project
Clone source
Vì trong series này ta tập trung vào học Docker nên với mỗi bài mình sẽ chuẩn bị sẵn cho các bạn các project, có thể chạy được không cần đến Docker, và việc của ta là dockerize các project đó.
Ở bài này các bạn clone source code ở đây nhé(nhánh master nhé các bạn)
Code cho bài này được để ở thư mục docker-node-mongo-redis, cả bài này ta chỉ làm việc với thư mục này thôi nhé
Tổng quan project
Project này có chức năng gì:
- Trang đăng nhập, đăng kí
- Bên trong có thêm mới sản phẩm, liệt kê danh sách sản phẩm ứng với user đăng nhập
Project này kiến trúc có những gì:
- Ta chỉ có 2 model là User và Product
- Dùng MongoDB để lưu trữ dữ liệu
- Dùng Redis để lưu trữ session của user đăng nhập
- Để xử lý Login/Logout ta dùng PassportJS
- Để xử lý upload file ta dùng Multer
Khá đơn giản phải không 🥰
Build docker image
Và lại như thường lệ như bao bài khác, để dockerize project ta sẽ tiến hành cấu hình Dockerfile cho project và build image nhé các bạn
Lắc não trước khi sử dụng
Trước khi bắt đầu cấu hình Dockerfile ta sẽ phân tích một chút để biết ta sẽ cần cấu hình những gì nhé
Để dockerize project này ta có thể "đút" tất cả mọi thứ vào trong 1 file Dockerfile và lát nữa sẽ có 1 container chưa môi trường có tất cả mọi thứ (từ nodejs, mongo, redis,....). Nhưng như thế sẽ không tốt, 1 container đảm nhận quá nhiều nhiệm vụ, thay vào đó ta sẽ chia nhỏ ra thành nhiều container đảm nhận các nhiệm vụ khác nhau nhé 😉
Dựa vào phần kiến trúc ta đã phân tích bên trên, ta sẽ chia project thành các phần như sau, mỗi phần sẽ tương ứng với một service ta định nghĩa ở docker-compose.yml:
- Có MongoDB là database -> ta có service db, dùng image mongo được build sẵn
- Có redis để lưu session của user -> ta có serivce redis, dùng image redis được build sẵn
- Phần còn lại là serivce để chạy project nodejs của chúng ta và kết nối tới 2 service bên trên -> ta chỉ cần cấu hình dockerfile cho service này, ta gọi là app
Cấu hình Dockerfile
Lại vẫn như thường lệ, ở folder gốc ta tạo 1 file tên là Dockerfile với nội dung như sau:
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm install
# Development
CMD ["npm", "run", "dev"]
# Production
# RUN npm install -g pm2
# CMD ["pm2-runtime", "ecosystem.config.js", "--env", "production"]
Những gì có ở trên mình đã giải thích rất kĩ ở các bài trước rồi các bạn có thể xem lại mình sẽ không nói nhiều nữa nhé, chỉ có một chút khác biệt ở đây là ta sẽ chia Dockerfile thành 2 phần: 1 cho môi trường dev (lúc ta code), 1 cho phần chạy production, vì khi code ta sẽ cần code được cập nhật liên tục mỗi khi ta thay đổi từ bên ngoài, còn khi production thì không (ở bài này ta dùng nodemon để cập nhật code mỗi khi có thay đổi từ bên ngoài)
Ở trên mặc định ta build image này là ta build cho môi trường dev, cuối bài khi deploy production thì ta sẽ dùng đoạn comment bên dưới nhé 😘
Build image
Và lại vẫn như thường lệ ta dùng command sau để build image nhé:
docker build -t learning-docker/docker-node-mongo-redis:v1 .
Chạy project
Data persistent
Ở bài này ta có databse là MongoDB để lưu trữ dữ liệu, ta có Redis để lưu trữ session của user.
Khi ta chạy project bên trong docker container, khi container khởi động lại thì mọi thứ sẽ mất và reset lại như ban đầu, tức là data trong database sẽ mất, session của những user đang đăng nhập cũng sẽ mất.
Do đó ở bài này ta sẽ tạo ra những folder để lưu lại dữ liệu để khi container có khởi động lại thì dữ liệu của ta vẫn sẽ được lưu lại nhé (việc này tiếng anh gọi là persist data), để làm được việc đó thì ta mount những folder chứa dữ liệu này vào trong container dùng volumes nha
Ở folder gốc, các bạn tạo cho mình folder .docker (để ý dấu chấm ở đầu nhé). Trong .docker ta tạo folder data, trong data ta tạo 2 folder tên là db (cho mongodb) và redis cho redis
Cấu hình docker-compose
Và lại vẫn cứ như cái thường lệ để chạy project ta sẽ tạo file docker-compose.yml với nội dung như sau nhé:
version: "3.4"
services:
app:
image: learning-docker/docker-node-mongo-redis:v1
volumes:
- ./:/app # mount từ môi trường gốc vào trong để nếu các bạn thay đổi code thì bên trong sẽ tự động cập nhật
environment: # phần này ta định nghĩa ở file .env nhé
- DB_HOST=${DB_HOST}
- DB_NAME=${DB_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- PORT=${PORT}
ports:
- "${PORT}:${PORT}" # phần này ta định nghĩa ở file .env nhé
restart: unless-stopped
depends_on:
- redis
- db
db:
image: mongo:4.4
volumes:
- .docker/data/db:/data/db
restart: unless-stopped
redis:
image: redis:5-alpine
volumes:
- .docker/data/redis:/data
restart: unless-stopped
Nếu các bạn đã làm qua các bài trước thì đến bài này nhìn vào bên trên các bạn sẽ thấy hiểu đúng không nào, còn không thì mình khuyến khích bạn xem lại các bài trước trong series này nhé.
Giải thích một số chỗ mới như sau
- Ở service db cho mongodb ta dùng image tên mongo (bản chính thức, các bạn search google là ra nhé)
- Tiếp đó ta mount volume từ folder .docker/data/db ta tạo ở phần trước vào bên trong container ở đường dẫn data/db và lát nữa khi khởi chạy Mongo sẽ tự tìm đến /data/db và load những dữ liệu vào trong database nhé
- Sao mình lại biết được đường dẫn /data/db ở trong container mà viết ở đây, thì ở phần mô tả của image ở đây họ đã nói rõ rồi nhé
- Điều tương tự cho service redis các bạn xem ở bên dưới nhé
Có 1 điều các bạn để ý là ờ service app chúng ta có để một trường tên là depends_on (phụ thuộc vào), ý bảo là service app sẽ phụ thuộc vào 2 service db và redis, điều này thực tế sẽ xảy ra như sau:
- Khi chạy docker compose up thì service db và redis sẽ khởi động trước service app
- Khi chạy docker compose up app thì đồng thời sẽ tạo ra 2 service db và redis (dù khi khởi động ta chỉ nói là "khởi động mỗi service app")
- Khi chạy docker compose stop thì service app sẽ bị stop trước 2 service kia
Note: mặc dù service app khởi động sau service db nhưng sẽ không đảm bảo service db sẵn sàng ngay thời điểm service app khởi động và kết nối tới nhé, vì service db tốn 1 khoảng thời gian nhỏ từ lúc khởi động đến lúc sẵn sàng cho kết nối. Đó là lí do vì sao ở file app.js mình lại phải có đoạn code connectionRetry để thử kết nối từ NodeJS tới MongoDB cho tới khi nào thành công
Note tiếp: Vì bản chính thức image của Mongo không có cho Alpine (các bạn có thể tự cấu hình rồi build) nhưng ở đây mình dùng luôn image được build sẵn, image này được build dựa trên môi trường hệ điều hành Ubuntu (lát nữa khi chạy các bạn exec vào trong chạy cat /etc/os-release sẽ thấy).
Chắc bạn sẽ thắc mắc: có service thì chạy trên môi trường HĐH Alpine có service chạy trên HĐH Ubuntu, có được hay không? Thì câu trả lời là chạy bình thường như cân đường hộp sữa nhé, các bạn tưởng tượng chúng giống như kiểu API, API viết bằng NODEJS vẫn sẽ gọi được API viết bằng Python như thường, miễn là trả ra cái gì đó chung kiểu như dữ liệu JSON là được (đó là mình ví dụ thế 😁)
Chạy Project
Ổn rồi đó các bạn, bây giờ chúng ta cùng chạy project lên thôi nào. Các bạn chạy command sau:
docker compose up -d
Và....... BÙM 💣, mở trình duyệt ở địa chỉ http://localhost:3000
mà không thấy có gì:
Check logs app
ở terminal thì in ra như sau:
Lỗi ở terminal báo là không tìm thấy nodemon, ta thử chui vào container và xem tại sao nhé. Các bạn chạy command sau :
docker compose exec app sh
Nhưng cũng không vào được @@, container reset liên tục.
Vấn đề của chúng ta ở đây là, khi chạy thì không có thư mục node_modules bên trong container dẫn tới việc project lỗi không chạy được và PM2 liên tục reset project của chúng ta.
Ồ, really?? Ta đã chạy npm install ở trong Dockerfile rồi cơ mà???
Giải thích: bởi vì trong quá trình dev, ta muốn sửa đổi code thì bên trong container sẽ tự động cập nhật lại, do đó ở docker-compose.yml chúng ta mount volume của service app điều này dẫn tới việc toàn bộ file từ môi trường ngoài sẽ thay thế cho toàn bộ file bên trong container, mà ở môi trường ngoài ta không hề có node_modules.
Giải pháp: ta có 2 cách như mình đã trình bày ở bài Dockerize ứng dụng VueJS, ReactJS
- dùng container tạm thời để chạy
npm install
- dùng Anonymous volume
Và ở đây ta sẽ chọn dùng Anonymous volume nhé vì nó tiện hơn 🥰🥰
Ta update lại docker-compose.yml
ở service app
nha:
services:
app:
image: learning-docker/docker-node-mongo-redis:v1
volumes:
- ./:/app # mount từ môi trường gốc vào trong để nếu các bạn thay đổi code thì bên trong sẽ tự động cập nhật
- /app/node_modules # dùng anonymous volume
environment: # phần này ta định nghĩa ở file .env nhé
- DB_HOST=${DB_HOST}
- DB_NAME=${DB_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- PORT=${PORT}
ports:
- "${PORT}:${PORT}" # phần này ta định nghĩa ở file .env nhé
restart: unless-stopped
depends_on:
- redis
- db
db:
image: mongo:4.4
volumes:
- .docker/data/db:/data/db
restart: unless-stopped
redis:
image: redis:5-alpine
volumes:
- .docker/data/redis:/data
restart: unless-stopped
Ok rồi đó, các bạn restart lại project bằng cách chạy command sau:
docker compose down
docker compose up -d
# lúc khởi động Mongo DB cần vài giây để chạy nên nếu bạn quay lại trình duyệt F5 ngay lập tức thì có thể không thấy gì nhé
Sau đó chúng ta mở trình duyệt ở địa chỉ localhost:3000 (cổng 3000 ta khai báo ở file .env nhé), nếu thấy như sau là oke rồi đó 💪
Các bạn tự nghịch xem sao nhé, thử quay lại code sửa một chỗ nào đó (sửa HTML là nhanh nhất) và F5 lại trình duyệt là các bạn sẽ thấy thay đổi ngay nhé 😘
Note cho bạn nào dùng Windows: khi up
lên có thể sẽ bị lỗi MongoDB không thể mount volume
vào môi trường gốc được. (có thể dẫn tới db
bị restart liên tục), đây là 1 issue với MongoDB và Docker đã được giải thích ở đây. Giải pháp cũng khá đơn giản, ta tạo 1 Docker volume
- volume riêng biệt được quản lý bởi Docker, volume mà ta dùng đường dẫn (như mình vẫn làm) là dạng local volume
. Ta cập nhật lại docker-compose.yml
một chút như sau:
version: "3.4"
services:
....
db:
image: mongo:4.4
volumes:
- mongodata:/data/db # ----->>> Ở đây
restart: unless-stopped
volumes:
mongodata: # ---->> Ở đây
Khi
up
thì mongo có thể mất một chút thời gian để khởi động, nên nếu các bạn có login/register mà thấy bị treo, check logapp
thấy báo lỗi connectdb
thì chờ 1-2-3 phút rồi làm lại là được nhé (nhất là các bạn dùng Windows) 😊
Vọc vạch
Các bạn thử tạo cho mình 1 account rồi login vào tạo vài ba sản phẩm.
Sau đó các bạn thử shutdown project này đi sau đó khởi động lại bằng cách chạy command sau:
docker compose down
docker compose up -d
Sau đó thử login lại bằng tài khoản cũ và ta sẽ vẫn thấy data được giữ nguyên nhé.
Thử mở folder .docker/data/db, trong đó các bạn sẽ thấy rất nhiều file, đó là những thứ cần thiết để lưu lại data cho MongoDB, tương tự các bạn mở folder .docker/data/redis cũng thế nhé:
Tiếp theo ta lại thử shutdown project đi bằng command:
docker compose down
Sau đó ở file docker-compose.yml ta thử không mount dữ liệu của mongo vào và xem thế này nhé, ở service db các bạn sửa lại như sau:
db:
image: mongo:4.4
#volumes: # comment đoạn này lại
# - .docker/data/db:/data/db
restart: unless-stopped
Sau đó ta khởi động lại project bằng command:
docker compose up -d
Thử login lại thì sẽ không được nữa nhé, vì khi container được khởi tạo lại thì mọi thứ lại trống trơn, data của ta ko được mount nên cũng sẽ không còn nữa
Tương tự ở service app nếu ta không mount volume thì khi sửa code và F5 trìnhd duyệt sẽ không có gì thay đổi cả, hoặc khi bạn tạo sản phẩm upload ảnh check ở folder /public/images thì cũng không thấy ảnh đâu nữa cả
Deploy
Ở phần này chúng ta sẽ cùng nhau chạy project này như một project lúc chạy thật (production) xem sao nhé.
Vì có thể có nhiều bạn không có điều kiện để thuê một server (VPS) riêng, nên chúng ta sẽ làm "mô phỏng" bằng cách tạo một folder bất kì ở trên máy và thực hành nha
Build Image cho production
Giả sử các bạn thấy code của mình dev đã ngon, mọi thứ ổn và giờ chúng ta sẽ build image để chạy thật
Ở file Dockerfile các bạn sửa lại như sau:
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm install
# Development
# CMD ["npm", "run", "dev"]
# Production
RUN npm install -g pm2
CMD ["pm2-runtime", "ecosystem.config.js", "--env", "production"]
Ở trên điều khác biệt duy nhất là ta dùng PM2 để chạy project: https://pm2.keymetrics.io/
PM2 (process manager) là một tool để ta chạy và quản lý ứng dụng NodeJS (có cả python nữa), dùng PM2 ta có thể thiết lập nhiều cấu hình chi tiết cho việc chạy project, đồng thời điều hay ho của PM2 là sẽ tự động restart project nếu như quá trình chạy có lỗi xảy ra, ví dụ như các lỗi mà ta không bắt Exception chẳng hạn
Ở trên ta dùng PM2 chạy project với những cấu hình ta đặt ở file ecosystem.config.js
Nhưng trước khi build ta sẽ tạo file .dockerignore để bỏ qua một số file/folder không cần thiết cho quá trình build nhé, các bạn tạo file .dockerignore với nội dung như sau:
# Bỏ qua node_modules vì lát nữa ở Dockerfile sẽ chạy npm install
node_modules
# Bỏ qua folder .docker vì folder này chỉ có service db và redis mới cần (ta đang build image cho service app)
.docker
# Bỏ qua ảnh mà chúng ta đã upload lên trong quá trình test
public/images/*
# Bỏ qua file .env, file này dùng cho docker-compose khi chạy nên không cần đưa vào image
.env
# Bỏ qua Dockerfile không đưa vào image vì file này chỉ dùng tại thời điểm build image
Dockerfile
# Bỏ qua file docker-compose vì file này chỉ dùng tại thời điểm khởi chạy project
docker-compose.yml
Sau đó ta build lại image bằng command:
docker build -t learning-docker/docker-node-mongo-redis:production .
Push image lên registry
Như ở bài dockerize ứng dụng Python mình đã hướng dẫn các bạn cách đẩy image lên registry (nơi lưu Docker image, giống như Github lưu code), và cùng với đó là cách pull về và chạy rồi nhé
Ở bài này ta lại làm tương tự, yêu cầu là các bạn đã có tài khoản Gitlab và có tạo sẵn 1 repository đặt tên là docker-node-mongo-redis
nha:
Sau khi tạo Repo thì ta vào Container registry để xem tên image ta cần đặt là gì nhé:
Như ở trên các bạn thấy thì ta cần phải để tên image theo pattern: registry.gitlab.com/<username>/docker-node-mongo-redis
(username các bạn thay vào cho đúng với của các bạn nha)
Để đẩy image lên registry thì việc đầu tiên ta cần làm là đổi lại tên image cho đúng với chuẩn của Gitlab. Các bạn chạy command sau:
docker tag learning-docker/docker-node-mongo-redis:production registry.gitlab.com/maitrungduc1410/docker-node-mongo-redis
thay tên Gitlab username của các bạn vào nha
Command hơi dài tí nhưng mình muốn làm rõ để các bạn được rõ 😁
Oke thì ta push image lên Gitlab nha:
docker push registry.gitlab.com/maitrungduc1410/docker-node-mongo-redis
thay tên Gitlab username của các bạn vào nha
Sau khi command chạy xong chúng ta lên Gitlab, chọn Repository và mở Deploy -> Container Registry
ta sẽ thấy image của ta ở đó nhé:
Bắt đầu deploy
Như ở đầu mình đã nói là chúng ta sẽ "mô phỏng" deploy vì khả năng cao rất nhiều bạn không có điều kiện để có server thật để test, nhưng nếu các bạn có server (VPS) xịn thì cứ follow những bước mình hướng dẫn vẫn chạy bình thường nhé.
Chúng ta tạo riêng một folder tên test-deploy ở bất kì đâu các bạn muốn nha.
Ở trong folder đó ta tạo file docker-compose.yml với nội dung như sau:
services:
app:
image: registry.gitlab.com/maitrungduc1410/docker-node-mongo-redis
volumes:
- ./public/images:/app/public/images # ta chỉ cần mount mỗi folder image thôi nhé
environment: # phần này ta định nghĩa ở file .env nhé
- DB_HOST=${DB_HOST}
- DB_NAME=${DB_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- PORT=${PORT}
ports:
- "${PORT}:${PORT}" # phần này ta định nghĩa ở file .env nhé
restart: unless-stopped
depends_on:
- redis
- db
db:
image: mongo:4.4
volumes:
- .docker/data/db:/data/db
restart: unless-stopped
redis:
image: redis:5-alpine
volumes:
- .docker/data/redis:/data
restart: unless-stopped
thay tên Gitlab username của các bạn vào nha
Sau đó ta tạo file .env với nội dung như sau:
PORT=3000
DB_HOST=db
DB_PORT=27017
DB_NAME=my_db
REDIS_HOST=redis
REDIS_PORT=6379
Và cuối cùng là chạy project thôi 💪, giờ phút của sự thật đến rồi, ta chạy command sau:
docker compose up -d
Khi chạy lên, docker-compose sẽ check những volume mà ta mount nếu ở bên ngoài chưa tạo folder thì docker-compose sẽ tự tạo cho ta nhé. Các bạn sẽ thấy như sau:
Và ta lại truy cập trình duyệt ở địa chỉ localhost:3000 (nếu bạn nào có server VPS riêng thì thay localhost bằng IP server của các bạn là được) và sẽ lại có được điều tương tự.
Vậy là ta đã hoàn thành việc deploy project như chạy thật rồi đó.
Điều hay ho của Docker
Như ở bài đầu tiên giới thiệu về Docker mình đã "quảng cáo" về độ tiện lợi của Docker khi ta chuyển môi trường, chuyển server.
Thì ở đây giả sử sau khi bạn đã deploy thành công project chạy thật ngon lành rồi, mà có lí do nào đó bạn cần phải chuyển sang server khác, Thì việc bạn cần làm là copy nguyên cái "đống" 😁 bên trên, tức folder test-deploy mà ta làm ở phần trên, và copy sang server mới, sau đó dùng 1 command:
docker compose up -d
Và Bùm 💥 mọi thứ lại chạy y như ở server cũ, không cần phải config gì thêm 😎 (nếu không có gì đặc biệt). Các bạn thử test bằng việc chạy ở một máy khác xem nhé 😉
Đây là 1 trong những điều tuyệt vời nhất mình yêu ở Docker ❤️❤️
Kết bài
Vậy là lại một bài nữa qua đi 💪
Hi vọng qua bài này các bạn đã hiểu được cách setup Docker cho project khi dev và khi deploy chạy thật sẽ như thế nào
Nếu có gì thắc mắc thì các bạn để lại comment bên dưới cho mình nhé
Toàn bộ source code cho bài này các bạn xem ở đây nhé (nhánh complete-tutorial nhé)
Cám ơn các bạn đã theo dõi và hẹn gặp lại các bạn ở những bài sau ^^
All rights reserved
Bình luận
Cho mình hỏi nếu mình chỉ cần quan tâm đến Nodejs, React, Mongodb... thì có cần phải đọc mấy bài Dockerize Larevel, Python,... để nắm thêm khái niệm gì ko nhỉ, hay mỗi bài là 1 bài độc lập ? Thanks
@hoc_anms e cũng có hỏi @maitrungduc1410 về vấn đề này, và ảnh cũng suggest là học từ đầu đến cuối series, e khuyên bác nên đọc hết vì còn nhiều thứ khá thú vị trong mỗi bài
@duongrickysun thanks bạn nhiều nhé, mình đọc ko xót bài nào về chủ đề này của đại ca mình đâu b
Theo dõi từ bài đầu tiên của series, do lướt FB thấy. Bài viết rất hay, chi tiết và tốc độ publish bài mới chóng mặt =)), tưởng nhìn nhầm ngày T5-T6 (2 bài)-T7-CN
Cho mình hỏi thêm, mình có 1 project Laravel (gọi là app), và cấu hình webserver cho 2 sub-domain dùng chung 1 folder app, thì Docker có hướng giải quyết không bạn? VD:
tất cả đều được nhé bạn. Bạn có thể check qua website cá nhân của mình, tất cả 5 trang đó đều chạy trên Docker, 2/5 trang chạy bằng Laravel (có cả redis, queue, job, schedule task, laravel echo, socketio,...):
5 Trang trên mình có 1 bạn "Nginx" (đứng trước tất cả) làm nhiệm vụ là vừa là 1 webserver vừa là 1 reverse proxy điều hướng request vào trong các app chạy bằng Docker ở sau
@maitrungduc1410 , cám ơn bạn
, mình sẽ tìm hiểu thêm. tài khoản mình mới nên ko like cho cmt được 
oke bạn nhé
Em có một góp ý nho nhỏ là trong file bin/www nếu đại ca khai báo process.env.PORT thì trong nodejs nó không hiểu được mà cần install package dotenv và khai báo để tạo biến môi trường .env cho global, như thế cho dễ thay đổi port ak
port.png
E check lại trong bài ở file docker-compose trường environment của service app a đã thêm vào PORT=${PORT} nhé
hehe success rồi anh ơi :V công nhận tiện quá cơ
Chẳng là mình muốn thực hành từ bài trc dùng volumn để mapping code ra ngoài. Cấu trúc thư mục của mình.
Dockerfile và docker-compose
Mình đã thêm trong file .dockerignore node_modules để k mapping thư mục này. Sau đó chạy docker build và compose up thì đang bị báo lỗi là k tìm đc thư việc vì thư viện chưa đc install. Cho mình xin cách clear nhất để người khác clone project mình về chạy 2 lệnh docker-compose build và up là chạy đc. Tks bạn
src chỉ có source code thôi bạn, cùng cấp với node_modules còn nhiều file liên quan khác như package.json, public... mình vẫn đang tìm giải pháp cho vụ này.
Theo mình dựa vào nhu cầu của bạn bạn chỉ cần map những thứ nào hay thay đổi nhiều nhất (source code, và public nơi chứa các file dạng image/ audio, thường xuyên sinh ra).
CÒn nếu bạn vẫn muốn map toàn bộ tất cả folder ngoài vào trong thì đương nhiên là bên ngoài sẽ luôn "ghi đè" và làm mất node_modules bên trong container vì bên ngoài ko có node_modules bạn à
Dear Chung,
Cảm ơn Chung
Chào Huy,
Sorry bạn mình viết thiếu một bước, không biết sao lúc viết bài đầu óc mình lại thế nào
Mình đã sửa lại trong bài mục Chạy Project, bạn có thể đọc lí do ở đó nhé
Cách fix nhanh cho bạn, bạn chạy command sau là được nhé:
Sau đó khởi động lại project bằng cách chạy docker-compose down/up
Hi vọng giúp được bạn
Dear Chung,
Cám ơn bạn đã theo dõi blog của mình,
Mình sẽ cố gắng chia sẻ nhiều nhất có thể với các bạn nhé
Mình gặp lỗi "MongoNetworkError: failed to connect to server [db:27017]" mà chưa hiểu rõ nguyên nhân lắm, dùng luôn source của bạn để run, môi trường Windows.
@maitrungduc1410 em bị lỗi này ngồi fix 2 ngày không ra, sau đó thấy mấy anh tây hay để standalone volume cũng bắt chước làm theo và bùm, nó run, cái lỗi này rất take time cho anh em tìm kiếm. có 1 điều nữa là lúc khai báo services nên khai báo thêm container_name, container_name của db em khai báo là db luôn thì được, không cần dùng cái standalone volume.
@tinpro123 tuyệt vời e , chạy đc là ngon.
container_name
thì a ko khuyến khích, vì như thế container của e sẽ có tên "chính xác" thay vì để Docker tự generate thêm prefix để đảm bảo ko bị conflict với các project khácdocker-compsoe.yml sai tên file này bạn ơi
Cám ơn bạn nhiều nhé, mình đã sửa lại những chỗ viết sai, 1 sự sai lầm nhỏ nhưng hậu quả to
B cho mình hỏi chút, mình muốn cấu hình với mysql, mình chạy kết nối với mysql, ko dùng docker chạy ok rồi. Nhưng chạy với docker đang báo lỗi
ConnectionRefusedError [SequelizeConnectionRefusedError]: connect ECONNREFUSED 127.0.0.1:3306
. Trong code mình ko dùng .env mà dùng một file js config để lấy username, host, password... Có cần phải cấu hình để kết nối với mysql dùng .env ko b nhỉ? Mình cũng muốn hỏi thêm là làm sao mysql trong docker-compose khi pull về có thể kết nối với username, password... đã khai báo b nhỉ, mình đang hiểu là khi pull image mysql thì nó chưa có username nào? Cảm ơn b!@maitrungduc1410 ok, cảm ơn b nhé
@nguyennhudat
Thấy bài của trung là 1 vote + 1 cmt xong đọc bài. Rất tích cực trả lời cmt. Keep going bro!!!
@NanaCongchua thời gian tới có thời gian mình sẽ quay lại viết tiếp series về Vue, vẫn còn nhiều điều ấp ủ nhưng chưa viết được

@maitrungduc1410 I'm hungry for more
sao tui chạy docker run --rm -v /$(pwd):/app -w //app node:13-alpine npm install rồi mà khi run vẫn báo error
@maitrungduc1410 ok bạn, đã chạy được
@myphuong tuyệt vời

Volume riêng biệt được quản lý bởi Docker là nó lưu ở đâu trên máy vậy a. khi compose-down nó k bị mất data, vậy làm sao để xoá a
Dạng volume đó gọi là
), dạng volume này được quản lý bởi Docker ở
).
Docker volume
(Trong bài a gọi nó làstandalone volume
nghe có vẻ không đúng/var/lib/docker/volumes/
(nếu máy gốc của e là Linux, trên Windows thì a search thấy đường dẫn nó dao động, mỗi người bảo 1 kiểuVolume bình thường mình mount từ đường dẫn thì gọi là
local volume
. Cái này a thường dùng và e đọc các bài của a cũng thấy ta đều dùng nó cả. Vì dạng volume này do chúng ta quản lý nên sẽ dễ hơn.Để xoá
Docker volume
thì đầu tiên e list ra danh sách Docker volume để tìm tên chính xác của nó (lí do là vì nếu e khai báo Docker volume ở trongdocker-compose.yml
thì nó sẽ được gắn thêm tiền tố nên ta phải check chính xác tên volume là gì trước khi xoá)Sau đó e muốn xoá volume nào thì e dùng command:
Chào anh, anh cho e hỏi để có thể truy cập dữ liệu ở .docker/data/db bằng MongodbCompass thì truy cập như thế nào ạ? Em cảm ơn ạ!
để truy cập data của Mongo bằng 1 tool quản trị CSDL (MongoDB compass, Studio 3T,....) thì e cần phải map port của container
db
để nó có thể được truy cập từ máy của e.Sau đó ở MongoDB Compass e connect tới cổng 8000 là đc nhé
Chào a, e chạy lần đầu đăng ký tài khoản, đăng nhập , thêm sản phẩm bình thường. Xong e docker-compose down --> docker-compose up để restart lại docker thì bị lỗi " Failed to connect to mongo on startup - retrying in 5 sec MongoNetworkError: failed to connect to server [db:27017] on first connect [Error: getaddrinfo ENOTFOUND db app_1 | 2020-11-08T04:54:54: at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26) { app_1 | 2020-11-08T04:54:54: name: 'MongoNetworkError' app_1 | 2020-11-08T04:54:54: }]"
Em docker ps thì 2 cái image kia đã chạy. Còn mongo thì Restaring suốt. Có cách khắc phục k ạ? Em cảm ơn.
@maitrungduc1410 ok a. E cảm ơn a ạ. Các bài viết của a hay và dễ hiểu quá.
@nambui2000k
oke e nhé


A cho em hỏi. Sao e chạy trên centos7 thì n không nhận file .env ạ?
@nambui2000k ủa trước giờ e vẫn quen làm như vậy à
, a xem ảnh của e cũng ko để ý cái đó thật 
Từ giờ nhớ cho chỗ này và cả các project khác nhé e, đừng để space vào, luôn viết liền

SOME_VAR=123
nhé@maitrungduc1410 ok . e cảm ơn a nha
a cho e hỏi: Ví dụ app nodejs port 3000 E dùng container nginx để proxy_pass sang port 3000 của app với e map thẳng port "80:3000" ở container app thì có khác thì có khác gì nhau không ạ? Em cảm ơn ạ.
@nambui2000k ,
về cơ bản dùng 1 webserver để serve request từ user (proxy, cache, load balance,....) sẽ tốt hơn, đặc biệt nginx làm rất tốt điều đó. Do vậy e nên có 1 con nginx đứng trước để proxy request là tốt nhất, nó ko chỉ đơn thuần để proxy mà e có thể có nhiều config để xử lý request từ user hoặc response từ phía backend của e (load balance, redirect, rewrite, deny, add header,...)
Thế nhưng việc thêm vào 1 container proxy này cũng có khi gây ra nhiều vấn đề nếu e ko quen vận hành nó, trong trường hợp đó thì e cứ dùng luôn service app là cũng đủ rồi từ từ học dần thêm
@maitrungduc1410 ok a. E cảm ơn a ạ.
Anh ơi, em cứ lỗi
thì anh có thể chỉ em cách fix được ko em dùng wsl ubuntu 18.04 ạ!
err.png
@cristiano khả năng cao là lúc mount volume có lỗi j đó, kiểu bên windows có lỗi khi dùng local volume với MongoDB á, dùng Docker volume (như e đang làm) thì sẽ fix đc.
@cristiano Server báo lỗi error là gì e? Logs của phía nodejs báo thế nào, e chạy
docker-compose logs app
xem nhécho em hỏi là có cần thêm network trong docker-compose vào không ạ để connect các container lại với nhau ạ
@maitrungduc1410 Kakaka em cũng đọc xong hết series này rồi anh, rất dễ hiểu, mong anh làm thêm về series kubernes để connect với những phần ở đây thì tuyệt
@sangmeo nhiều bạn cũng comment hỏi a về series kubernetes, a cũng đau đáu lắm mà chưa có tgian

Mình cài trên Windows 10 và có vấn đề trong việc cài đặt node_modules Câu lệnh như trong hướng dẫn docker run --rm -v "/$(pwd)":/app -w //app node:13-alpine npm install báo lỗi, không thực hiện được trên windows 10 (máy mình). Ban đầu mình cài Node lên windows, nhưng thấy có vẻ không ổn, vì như vậy 2 phiên bản có thể khác nhau
Mình đã thực hiện theo hướng dẫn tại https://stackoverflow.com/questions/51097652/install-node-modules-inside-docker-container-and-synchronize-them-with-host
Nội dung các file thay đổi như sau
Dockfile
Tập tin docker-compose.yml
Cuối cùng là thêm file entrypoint.sh
Sau khi cài đặt xong, đã truy cập được trang web như hình
tuyệt vời bạn ơi, cám ơn bạn đã tìm ra vấn đề và giải pháp cho việc này, mong là giúp được các bạn đọc sau
Chào bạn mình làm theo cách của bạn ở trên Win11 nhưng khi chạy docker-compose up thì nó báo lỗi /usr/local/bin/docker-entrypoint.sh: exec: line 11: /app/entrypoint.sh: not found Không biết bạn đã từng gặp vấn đề này chưa ? bạn có thể cho mình xin cách giải quyết được không ?
Em chào a, em đang ngâm cứu serial học docker của a. Mong a có thời gian thông não giúp em bài toán này với ạ
Hiện tại em đang muốn implement 1 model S2T sử dụng flask + docker thành 1 web api
Em đang dự định thiết kế các service như sau: nginx, app(chứa flask), db(chứa mysql hoặc mongo)
Nhưng em có 1 vấn đề ở đây là e có dùng tensorflow (và 1 số thư viện khác), thì em có cần thiết tạo 1 service mới (service S2T chẳng hạn) để đảm nhiệm việc xử lý và train model ko ạ ? Khi đó cách kết nối service app với service S2T là ntn ạ
@maitrungduc1410 A cho em hỏi thêm chút ạ
Src code của e được để chung ở 1 chỗ duy nhất ấy ạ
Khi các service được định nghĩa như trên, service app của em chỉ chứa flask (không có các method của tensorflow kiểu như load_weight hay predict) thì em làm sao có thể sử dụng được ạ ?
Kiểu em chưa hiểu rõ là src code viết chung nhưng môi trường lại chia rẽ ra thì làm tn để chạy được ấy ạ
Mong a giải đáp giúp em ạ
@thangnk e viết cho chúng mỗi cái 1 dockerfile riêng thì mỗi cái sẽ có 1 môi trường riêng, trong dockerfile thì e chỉ copy các file cần thiết cho service e cần thôi nhé
Chào bạn, cho mình hỏi chút. Mình push image production lên docker hub, sau đó tạo 1 folder để test deploy. Sau đó chạy
docker-compose up
thì cứ bị lỗi này.Trong khi đó ở image app, mình dùng image của bạn lại ko bị lỗi. Bạn có thể cho mình biết mình thiếu ở bước nào không nhỉ?
Đây là repo docker hub của mình: https://hub.docker.com/repository/docker/thanhnx9368/docker-node-mongodb-redis
@maitrungduc1410 À thì ra lifecycle của nó:
Cảm ơn bạn nhé, giải thích chi tiết và dễ hiểu quá. 😊
@thanhnx9368 okie bạn nhé 😁
Cảm ơn tuto rất hay của bạn về docker và các project thực hiện với docker. Trong bài này, mình có access (root) đến server, nhưng chỉ là shell, ko phải graphic access. Mình cấu hình mãi ko được cái ssh tunneling để có thể truy cập đến page được served bởi container app. Bình thường vụ port forwarding này rất đơn giản: VD mình chạy jupyter notebook trên server, port 8888 trên host chẳng hạn, sau đó fwd đến localhost:8000 là có thể dùng browser thao tác với các notebook trên server.
Trường hợp này bạn có thể thêm phần hướng dẫn đó được ko?
khi bị lỗi ko access được vào app của bạn thì bạn cứ debug từng bước 1 nhé:
curl localhost:8000
có nhận đc response hay ko?curl ip_server:8000
-> nếu bước này ko được thì có 2 khả năng: 1 là bạn chưa mở cho traffic đi vào từ internet qua cổng 8000 (thường cấu hình trên cloud provider). Hoặc trường hợp 2 là app của bạn ko cho phép traffic bên ngoài gọi tới, mà chỉ cho phép gọilocalhost
(tức là gọi cùng server), khá giống với Python Flask, mặc định chỉ cho phép gọi từ localhost, xem thêm ở đây nhéTổng kết lại, mình ko nghĩ là việc ko gọi được vào app của bạn nó lại liên quan tới việc access từ account của bạn vào server
Bài hay lắm nha chủ thớt, mình góp ý xíu chỗ docker tag thiếu docker push á bác
cám ơn bạn đã góp ý
sao mình thay đổi code mà trong container vẫn k tự động thay đổi bạn ơi
bạn kiểm tra lại docker-compose.yml xem đã mount code từ ngoài vào trong chưa?
Quan trọng hơn chú ý trong bài mình dùng command "npm start" để chạy project, và mặc định nó không "watch" khi code thay đổi:
Bạn đổi thành như sau và build lại image nhé:
@maitrungduc1410 Anh cho em hỏi bài trước khi Dockerize Laravel em thấy có dùng nginx sao bên này lại không dùng vậy ạ.
vì app PHP thì nó cần có 1 webserver để nhận request từ bên ngoài sau đó pass vào php-fpm.
còn ở bài này thì mình dùng nodejs bản thân nó đã là 1 webserver sẵn rồi. Nếu e thích e có thể đặt 1 con nginx phía trước nodejs giống bài Laravel cũng đc, nhưng ko cần thiết
a cho e hỏi là đến bước test-deploy rồi, đã tạo docker-compose.yml và .env file. nhưng khi compose up thì nó lại báo là node_module missing. Vậy khi deploy thì server có cần chạy 1 Node container để install node_modules trước khi compose up không ?
thực tế, cách a hay làm nhé: đó là không dùng container node tạm thời để install node_modules, mà viết trực tiếp vào Dockerfile (npm install).
Như vậy thì khi nhìn vào Dockerfile ta có được cái nhìn tổng quát, tất cả mọi thứ cần để build image, deploy và chạy được
Hi bạn,
Bài viết rất hay và đầy đủ, mình có làm theo hướng dẫn thì ở khúc deploy thì thay vì phải tạo một folder trên máy local thì mình thuê hẳn một con server để test, mọi chuyện đang rất ổn cho đến khi mình bắt đầu chạy câu
docker-compose up
thì nó báo lỗi ở trong container app:exec /usr/local/bin/docker-entrypoint.sh: exec format error
Mình đã thử nhiều cách như là thêm quyền exec cho file cũng như là thử restart nhưng đều không được, hiện tại trên máy mình vẫn hoạt động bình thường chỉ có trên server thật thì mới ko chạy được thôi.
Hy vọng bạn có thể giúp mình,
Thanks.
Một vài thông tin thêm:
là sao bạn nhỉ?
vậy là bạn bị lỗi khi
docker compose up ...
hay là lỗi khi exec vào containerdocker compose exec app sh
?lỗi là khi mình chạy
docker compose up ...
nha, do mình không có lệnh -d nên nó hiện thị log bên trong container app luôn@tict khả năng là do image được build với architecture khác với trên server của bạn rồi, lỗi này giống như hội dùng MacBook M1: https://stackoverflow.com/questions/73285601/docker-exec-usr-bin-sh-exec-format-error
Ở
docker-compose.yml
, serviceapp
, bạn setplatform: linux/amd64
xem nhéCho e hỏi là tên của service mình khai báo trong file docker-compose thì đặt là gì cũng được phải ko ạ? E muốn đổi tên service db thành mongo thì phải sửa ở đâu nữa ko a, vì e run thì lỗi kết nối với db. E kp dev node nên ko rõ lắm.
@maitrungduc1410 cho e hỏi là cái db_name thì sao mà mongo nó hiểu để tạo ra ạ. Và nếu mình muốn đổi port của mongo và redis thì phải làm sao ạ? Em cảm ơn
@WRBKOR23 mongo + redis thì bên trong container nó đã luôn chạy port 27017 và 6379 rồi, e ko đổi được (Trừ khi e tự build lại image của nó, mà chả ai rảnh mà làm vậy
)
ở đây e chỉ có thể đổi port mà e map ra môi trường ngoài thôi, ví dụ:
Còn về việc DB_NAME, thì Mongo khác với các loại SQL DB đó là ta ko cần tạo trước db (collection), mà khi ta insert vào mongo sẽ check nếu collection (db) chưa được tạo thì nó sẽ tạo cho.
Nếu e muốn tạo sẵn collection ngay lúc chạy container mongo thì e truyền thêm biến môi trường
MONGO_INITDB_DATABASE
là được. Xem thêm ở đây nhé: https://hub.docker.com/_/mongochào bạn mình đang bị lỗi connect ECONNREFUSED 127.0.0.1:6379 redis , nhờ bạn xem và giúp mình với :
bạn đang ở trong service
app
thì làm sao bạn lại connect tới redis ở127.0.0.1
đượcmà nó phải là
redis:6379
nhé. "redis" là tên của serviceredis
, dòng số 42 như trong filedocker-compose.yml
của bạn.Bạn cần phải sửa lại
REDIS_URL
@maitrungduc1410 tks bạn hôm qua mình search và fix đc rồi , mongo cũng tương tự
@duyanh4788 oke bạn nhé
Hello anh, em xin phép hỏi một câu ạ 😅.
Em thấy trong file .dockerignore của anh có ingnore node_modules và anh có chú thích rằng là do mình có chạy
npm install
trong Dockerfile Em thấy nó đang hơi mâu thuẫn với việc mình phải tạo container tạm thời để chạynpm install
khi dev ạ. Anh có thể giải thích cho em rõ hơn chút được k ạ.Em đang nghĩ là có thể do
npm install -g
chăng 🤔Em cảm ơn ạ.
có vẻ là e đang bị hiểu lầm các trường hợp đó với nhau
node
để chạynpm install
, cái node_modules đc sinh ra là từ container mà ra, sẽ tương thích, phù hợp để dev trực tiếp trong container@maitrungduc1410 Câu trả lời rất ngắn gọn xúc tích và dễ hiểu ạ. Cảm ơn a 🤟
@Croud141 oke e nhe
A ngâm cứu làm thêm series về Redis đi a, e thấy phần này cũng khá nhiều vấn đề để nói đó ạ
oke e nhé,
Series hay lắm, hóng một bài dockerize về springboot của bạn
@maitrungduc1410 Dạ vâng e thấy lỗi register 500 r ạ, nhưng nó k hiện error detail ạ
@datnx ở 2 chỗ sau e log lỗi ra xem nhé:
controllers/UserController.js
Thêm vào bên trên dòng này:
app.js
Thêm vào bên trên dòng này:Sau đó build lại project và test lại sẽ thấy detail lỗi nhé
e k bik e có sai gì không nhưng khi nhấn thêm sản phẩm nó xoay vòng :>
học mới 6 bài mà não xoay mù vì cố nén hết kiến thức vô đầu xong cái nhớ cái quên 😅nên chắc 1 project phải triển khai thực tế hơn chục lần mới ok a nhỉ :.>
@maitrungduc1410 à ok r a chỗ số lượng sản phẩm e nhập chữ thay vì số 🤣
cảm ơn a
@neko nhớ lần sau gặp lỗi thì phải check logs đầu tiên e nhé
chào anh trong bài này thì em chỉ thấy chỉ thay đổi được giao diện, còn phải xử lí bên trong thì không thay đổi, em đã thay đổi bên trong UserController ở phần register ở line 49 thay vì 'Server error' như anh đã code thì em thay bằng mess mới nhưng thấy nó thay đổi, em phải down và up lần nữa mới thay đổi ạ
ý e là sao nhỉ?
e sửa code thì build lại project nhé, ở
docker-compose.yml
để ý dùng đúng tên image + tag e vừa buildSau khi cài xong
Em chạy với lệnh docker compose up thì gặp lỗi này, liên tục exit with code 0 và exit with code 14, lặp lại liên tục, em thử exec app cũng không được vì nó báo lỗi container is restarting. Mặc dù em đã check thấy có thư mục node_modules bên ngoài máy rồi.
@maitrungduc1410 Em đã fix được rồi, ban đầu em để docker folder ở /mnt/data1/, sau khi em chuyển về folder /home/duong/ thì ko gặp lỗi gì, em nghĩ có thể do permission gì đó nên folder được mount không chạy services mongodb được

@Koruvika theo a nhớ
/mnt
thường mặc định là củaroot
nhưng fix đc là may rồi e
👍️
a cho e hỏi là :
1/ trong file docker-compose.yml REDIS_PORT=${REDIS_PORT}
tên bên trái với bên phải mình để sao cũng được miễn là nó giống với file .env đúng không ạ và chỉ cần để port trong biến môi trường vậy là nó tự động kết nối đến service khác như mongo redis bên ngoài hay sao a
3/ tại sao trong file .env có DB_PORT mà trong file docker-compose.yml lại không ghi DB_PORT vô phần environment mà e chạy dự án vẫn được vậy ạ
3.1/cái biến
DB_HOST=${DB_HOST}
để giúp kết nối đến service mongo mà không cần
port trong file.yml mà
REDIS_HOST=${REDIS_HOST} lại phải cần để port trong yml vậy ạ
DB_HOST=${DB_HOST}
, cái bên trái là cái app bên trong container cần, phải chính xác, vế bên phải thì như nào cũng được miễn là khớp với.env
app
nó kết nối tới DB để lưu data, kết nối tới redis để lưu session đăng nhậpDB_PORT nếu ko có thì nó lấy mặc định
27017
, e check ở code nhé@maitrungduc1410 trời cảm ơn a trả lời sớm dữ , e đang ngồi ngẫm sửa lại cmt cho ra câu hỏi chỉnh chu thì a rep lun r :>
@neko okie e 🤣🤣🤣