Dockerize ứng dụng chat realtime với Laravel, Nginx, VueJS, Laravel Echo, Laravel Reverb
Cập nhật gần nhất: 05/12/2024
Xin chào các bạn đã quay trở lại với series Học Docker và CICD của mình.
Chúc mọi người một mùa giáng sinh an lành ấm áp 😍😍
Mấy ngày vừa rồi tinh thần viết blog đang lên cao thì đôi mắt mình lại đình công sau 1 khoảng thời gian ngồi máy tính quá nhiều, hôm nay "cửa sổ tâm hồn" có vẻ tốt trở lại thì mình lại chầm chậm viết blog tiếp cho mọi người 😉
Dù làm gì cũng phải nhớ giữ sức khoẻ nhé mọi người, Tết nhất đến nơi rồi phải có sức khoẻ để còn đi làm lấy tiền đưa về biếu thầy bu 🤣 🤣
Ở bài trước mình đã hướng dẫn các bạn các Dockerize ứng dụng NodeJS với mysql, redis,... cùng với đó là các setup cho môi trường dev và production.
Ở bài này chúng ta sẽ chơi "hardcore" hơn bằng cách Dockerize ứng dụng Chat realtime với Laravel, VueJS, Laravel Reverb cùng với đó là setup Laravel Pulse/Telescope và Laravel Schedule Task nhé.
Sorry các bạn vì tên bài này mình để hơi dài, mục đích để khi đọc qua tiêu đề các bạn sẽ rõ hơn đồng thời cho những anh em search từ google thì cũng sẽ dễ tìm hơn nhé.
Setup
Các bạn clone source code ở đây nhé (nhánh master)
Ở bài này chúng ta sẽ chỉ quan tâm tới folder docker-laravel-realtime-chat-app nhé.
Tổng quan ứng dụng
Ứng dụng này đã được mình làm và deploy ở đây các bạn có thể vô vọc vạch: https://realtime-chat.jamesisme.com
Tổng quan:
- Ứng dụng có các phòng chat để user có thể join vào
- Mỗi phòng chat sẽ có 1 hộp (box) chat chung, tất cả user trong phòng có thể nhắn tin vào đây
- Cùng với đó là danh sách user có ở trong phòng chat, click chọn vào 1 user bất kì để nhắn tin riêng vơi người đó
- Cứ mỗi 1 phút sẽ tự động xuất hiện tin nhắn chào mừng của Bot (dùng Laravel Schedule Task)
- Detect user đang gõ, hay đã xem tin nhắn,...
- Các bạn có thể chọn biểu cảm tin nhắn như Facebook Messager: Love, Haha, Wow,..... 💪
Đàm đạo về phương án triển khai
Hình dung ban đầu
Ta cùng nhau hình dung ra những thành phần mà ta sẽ cần phải có cho app này nhé:
Sương sương thì ta bóc tách ra thành 6 thứ như sau:
- Laravel App: main app xử lý các thể loại logic, views,...
- Workers: vì message sẽ được đẩy vào queue để xử lý bất đồng bộ, do vậy ta cần có workers làm nhiệm vụ lấy message ra và xử lý
- Reverb: Websocket server, chịu trách nhiệm nhận message và broadcast đi cho frontend
- Scheduler: giống như cronjob, chạy các tác vụ vào giờ giấc được cài đặt sẵn (lát nữa ta sẽ có con Bot cứ 1 phút sẽ gửi các tin nhắn dạng hướng dẫn vào phòng chat)
- MySQL: chắc chắn rồi ☺️, để lưu trữ dữ liệu
- phpMyAdmin: cái này để ta có thể thao tác với data trong MySQL trực tiếp trên Web UI, rất tiện 😘
Cái này là mình đã phác thảo sẵn ra cho các bạn ăn sẵn luôn rồi đó nha 🤣🤣
Deploy theo kiểu truyền thống
Nếu các bạn đã xem bài Deploy ứng dụng chat realtime Laravel, VueJS trên Ubuntu, ở bài đó ta cũng deploy app chat y hệt, nhưng dùng cách ngày xưa đó là setup trực tiếp mọi thứ vào VM (VPS) luôn, thì quy trình sẽ vô cùng vất vả:
Ta sẽ phải cài tất cả các thứ vào thẳng máy ảo (VM - virtual machine, ta cũng hay gọi là VPS), việc cài thẳng vào VM này sẽ gây rất bất tiện:
- tác động trực tiếp vào các file system của VM, có thể gây lỗi trong quá trình cài đặt và sử dụng, thậm chí có thể thay đổi vĩnh viễn một số thành phần
- rất khó upgrade khi phải chạy nhiều app với nhiều dependencies khác nhau, ví dụ cái thì PHP7, cái PHP8, cái node 20, cái node 14,...
- ...ti tỉ thứ khác
Vậy nên ta mới có bài này làm với Docker nè 🤣🤣
Deploy với Docker
Bài này mình viết từ khá lâu rồi, mình vẫn luôn review, update và xử lý các comment của các bạn, 😁, và vẫn gắng update sao cho phù hợp với hiện tại, tinh gọn, dễ hiểu và phù hợp với dự án thực tế nhất
Trước đây thì mình chọn kiến trúc deploy như sau:
Mỗi hình con cá 🐟 là 1 Docker container nha 😂
Kể ra thì nó chạy cũng oke trong 1 thời gian dài đấy, một phần mình bị ảnh hưởng bởi mô hình của công ty cũ ngày xưa, thấy chạy ổn thì follow theo.
Gần đây thì mình có review lại một số điểm bất tiện thì thấy như sau:
- Kiến trúc bên trên ta vẫn có sự "couple" (gắn kết) giữa các thành phần như Laravel App - Task scheduler - Workers
- việc dùng
supervisor
như cách deploy truyền thống bản chất là để quản lý process khi ta phải cài tất cả mọi thứ vào VM, còn đây là với Docker, chẳng phải mỗi 1 container cũng "gần như" một process rồi hay sao? Lại còn phải thêm Supervisor, rồi crontab, rồi gói mấy thằng đó chung vào 1 container làm gì? 🧐🧐
Khi làm thực tế chẳng phải ta thường sẽ deploy các thành phần thành các service riêng biệt, business riêng, dễ quản lý, lại dễ scale hơn hay sao?
Vậy nên mình đã refactor lại chút:
Ở trên các bạn thấy rằng mình đã tách hết ra mỗi thành phần là 1 container, trông kiến trúc rõ ràng hơn rất nhiều. Sau này chạy production cái nào nặng thì scale cái đó thôi, cũng tiện hơn rất nhiều😎
Do vậy nên là mình đã viết lại toàn bộ bài này, hiện tại là tháng 12/2024, các bạn đọc comment để ý nhé 😘
Ta chú ý rằng các service liên quan đến Laravel bao gồm: Main app, Reverb websocket server, worker, scheduler. Tất cả chúng có điểm chung là đều dùng PHP và cần dùng chung 1 source code 😉
Build Docker image
Đầu tiên, ở root folder project các bạn tạo cho mình Dockerfile nhé:
FROM php:8.3.9-fpm-alpine3.20 AS base
WORKDIR /app
# Add and Enable PHP-PDO Extenstions
RUN docker-php-ext-install pdo pdo_mysql pcntl
RUN docker-php-ext-enable pdo_mysql
FROM base AS reverb
CMD ["php", "artisan", "reverb:start"]
FROM base AS workers
CMD ["php", "artisan", "queue:work"]
Ở đây ta dùng multi-stage build nha, tức là chia Dockerfile thành nhiều stage, tí nữa với từng service ta sẽ target
vào từng stage cụ thể.
Giải thích chút nha:
- Ở trên ta có 1 stage
base
, ở đó ta cài extension để kết nối tới mysql từ PHP (Laravel) - tiếp theo ta có 2 stages là
reverb
vàworkers
, chúng sẽ FROM từbase
, lí do là vì chúng đều cần có đoạn setup extension
ủa vậy còn Main Laravel App và Scheduler đâu??? 🧐🧐
Thì 2 cái đó ta không cần custom CMD
gì cả, mà dùng mặc định CMD
của cái image php:8.3.9-fpm-alpine3.20
, nó là:
CMD ["php-fpm"]
Các bạn có thể xem thêm ở đây: https://github.com/docker-library/php/blob/master/8.3/alpine3.20/fpm/Dockerfile#L259
Tức là lát nữa ở docker compose
, với service app và scheduler ta cứ target thẳng vào base
mà không cần custom gì cả
Tiếp theo ta tạo file .dockerignore
với nội dung như sau:
node_modules
npm-debug.log
.env*
Dockerfile*
docker-compose*
.git*
.docker
vendor
app.conf
Tiếp đó ta tạo file docker-compose.yml
:
services:
webserver:
image: nginx:1.27.3-alpine
ports:
- "8000:80"
working_dir: /app
volumes:
- ./:/app
- ./nginx.app.conf:/etc/nginx/conf.d/default.conf
restart: always
app:
build:
context: .
target: base
volumes:
- ./:/app
restart: always
reverb:
build:
context: .
target: reverb
volumes:
- ./:/app
restart: always
workers:
build:
context: .
target: workers
volumes:
- ./:/app
restart: always
db:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MYSQL_DATABASE=${DB_DATABASE}
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
volumes:
- .docker/data/db:/var/lib/mysql
restart: always
scheduler:
build:
context: .
target: base
volumes:
- ./:/app
entrypoint: ["sh", "-c", "while true; do php artisan schedule:run; sleep 60; done"]
restart: always
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8001:80
environment:
- PMA_HOST=${DB_HOST}
- PMA_PORT=${DB_PORT}
Giải thích chút nha:
- ở trên có 1 thứ mới mà mình hôm nay mới giới thiệu tới các bạn đó là
target
, bằng việc dùngtarget
ởdocker-compose.yml
ta nói với Docker rằng: "ê ông, lúc build image thì ông build mỗi cái stage mà tôi đang target tới này thôi nhé, chứ đừng build cảDockerfile
nha" 😆 - với
nginx
ta có mount 1 filenginx.app.conf
vào container, tí nữa mình sẽ giải thích file đó là gì nhé - với service
scheduler
, mình cóentrypoint
, nó sẽ thay cho CMD và được chạy ngay lúc ta start container, ở đó các bạn thấy mình đơn giản là chạy task mỗi 60 giây - Với các service như
db
vàphpmyadmin
, chúng ta có khai báo vài biến môi trường, giá trị thực tế của chúng (ở trong dấu ngoặc${}
), sẽ được tham chiếu từ file.env
mà tí nữa ta sẽ tạo nhé - Chỉ có 2 service là
webserver
vàphpmyadmin
, được truy cập từ thế giới bên ngoài nên ta chỉ cần map port cho 2 cái đó thôi. Tí nữa mình sẽ giải thích về flow để ta rõ hơn nha
Tạo .env
Tiếp theo ta cần tạo file .env
và update 1 số cấu hình nhen. Ta đơn giản là copy từ file .env.example
:
cp .env.example .env
Giờ ta cần update 1 số thông tin ở file .env
như sau:
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=realtime_chatapp
DB_USERNAME=myuser
DB_PASSWORD=myuserpass
DB_ROOT_PASSWORD=myrootpass
REVERB_HOST="reverb"
VITE_REVERB_HOST="localhost"
Các biến về DB_
nhìn phát chắc là ta hiểu luôn ý nhờ, học từ đầu series rồi mừ 😁, có gì thì comment cho mình nhé
REVERB_HOST
là dùng để phía Laravel main app + worker connect tới Reverb websocket server, vì đây là connect trực tiếp từ container->container nên ta dùng luôn tên service chứ không cần map port ra localhost làm gì
Tiếp theo là VITE_REVERB_HOST
, cái này được dùng ở Frontend connect tới Reverb websocket server, mà ở Frontend (browser) nó sẽ không biết service docker gì đâu nên ta phải dùng localhost
Cấu hình Nginx
Ở root folder project ta tạo file nginx.app.conf
với nội dung như sau:
server {
listen 80;
index index.php index.html;
root /app/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_hide_header X-Powered-By;
}
location /reverb {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# trailing slash is important to remove /echo from the url
proxy_pass http://reverb:8080/;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Chú ý ở trên khi với code PHP thì ta sẽ redirect request vào cho service app để service app có PHP-FPM làm việc, còn với /reverb thì ta điều hướng công việc tới service http://reverb:8080 nha
Nói chung đoạn này cấu hình cũng khá cơ bản của Nginx thôi, ta chỉ cần chú ý là đây là môi trường Docker nên khi proxy pass request ta phải dùng service name 😘
Chạy ứng dụng
Âu cây setup thế thôi nhỉ, giờ ta chạy lên coi mặt mũi nó thế nào 🚀🚀
docker compose up -d --build
Chú ý rằng bình thường ta hay build image trước với
docker build...
nhưng ở bài này ta cấu hình phần build image trực tiếp ởdocker-compose.yml
vớibuild > context
Âu câyyy, giờ ta mở trình duyệt ở địa chỉ http://localhost:8000
vàaaaaa PÒMMMM:
Ủa cái gì zị anh em??????
Ầu thì ra ta không có folder vendor
, cái này là nơi lưu trữ các dependencies cho app Laravel (PHP, như kiểu node_modules của JS ấy)
Cài dependencies (composer install, npm install,...)
Như ở bài Dockerize VueJS mình có nhắc tới dùng container tạm thời để cài dependencies thay vì cài trực tiếp vào image.
Việc này sẽ giúp giảm size của image xuống, vì việc cài dependencies này không xảy ra thường xuyên mà ta thường chỉ làm 1 lần lúc đầu khi setup project.
Ở đây ta sẽ vừa setup dependencies cho PHP vừa cho Frontend (VueJS) luôn đó
Các bạn chạy lần lượt các command sau nhé (tuỳ vào hệ điều hành của các bạn là gì mà chọn cho đúng nha ☺️):
# MacOS + Linux
docker run --rm -v $(pwd):/app -w /app composer:2.8.3 install
docker run --rm -v $(pwd):/app -w /app node:22-alpine npm install
docker run --rm -v $(pwd):/app -w /app node:22-alpine npm run build
# If Windows see below:
# Git bash
docker run --rm -v "/$(pwd)":/app -w //app composer:2.8.3 install
docker run --rm -v "/$(pwd)":/app -w //app node:22-alpine npm install
docker run --rm -v "/$(pwd)":/app -w //app node:22-alpine npm run build
# PowerShell
docker run --rm -v "$(pwd):/app" -w /app composer:2.8.3 install
docker run --rm -v "$(pwd):/app" -w /app node:22-alpine npm install
docker run --rm -v "$(pwd):/app" -w /app node:22-alpine npm run build
# Command Prompt
docker run --rm -v "%cd%:/app" -w /app composer:2.8.3 install
docker run --rm -v "%cd%:/app" -w /app node:22-alpine npm install
docker run --rm -v "%cd%:/app" -w /app node:22-alpine npm run build
Giải thích:
- Đầu tiên ta tạo 1 container tạm thời từ image compose (có cài sẵn composer) và ta chạy composer install
- Tiếp đó ta tạo 1 container từ image node (22) và chạy npm install
- Cuối cùng là ta build phần code VueJS với npm run build
Note cho bạn nào dùng Windows:
- nếu bạn chạy
docker run ... npm install
và gặp lỗiENOSYS: Function not implemented
thì bạn khởi động lại Docker, nếu vẫn không được thì bạn cần khởi động lại máy nhé. Đây là 1 issue của Docker liên quan tới file system trên Windows được mô tả ở đây - nếu sau khi khởi động lại mà vẫn xảy ra các lỗi liên quan tới npm (
docker run ...npm install
,npm run...
), thì các bạn chạy trực tiếp các commandnpm
ở môi trường gốc nhé (tất nhiên điều này yêu cầu máy gốc các bạn cóNodeJS
)
Giây phút của sự thật
Sau khi hoàn thành ta quay lại trình duyệt và F5:
Ầu, quên chưa generate key (APP_KEY
ở .env
vẫn đang trống), ahihi 😁😁
Trước khi chạy command tạo key mình xin lưu ý: mọi command php artisan ... từ giờ ta sẽ luôn chạy dưới dạng docker compose exec app php artisan.... (để ý phần đầu nhé). Vì sao? Vì khi chạy bên ngoài thì Laravel sẽ dựa vào bối cảnh là môi trường ngoài để chạy, nên những command như migrate sẽ bị lỗi. Nên để đồng bộ và cho các bạn quen thì ta luôn chạy với docker compose exec... nhé
Chúng ta chạy command sau để tạo key đồng thời tạo DB và seed luôn nhé
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate --seed
Sau đó ta load lại trình duyệt và...... BÙM
Tạo 2 tài khoản 2 mở 2 tab zô nghịch tí coi nàooooooooo 💪💪
Ủa tin nhắn gửi đi thì có vẻ lưu vào DB rồi, F5 thấy ok, mà chả có tí realtime nào zị nhỉ???? 🧐🧐 Danh sách user đang online cũng trống rỗng luôn?????
Vọc vạch
Giờ ta check xem các container có oke không nhé:
docker compose ps
>>>
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
docker-laravel-realtime-chat-app-app-1 docker-laravel-realtime-chat-app-app "docker-php-entrypoi…" app 19 minutes ago Up 19 minutes 9000/tcp
docker-laravel-realtime-chat-app-db-1 mysql:8 "docker-entrypoint.s…" db 19 minutes ago Up 19 minutes 3306/tcp, 33060/tcp
docker-laravel-realtime-chat-app-phpmyadmin-1 phpmyadmin "/docker-entrypoint.…" phpmyadmin 19 minutes ago Up 19 minutes 0.0.0.0:8001->80/tcp
docker-laravel-realtime-chat-app-reverb-1 docker-laravel-realtime-chat-app-reverb "docker-php-entrypoi…" reverb 19 minutes ago Up 13 minutes 9000/tcp
docker-laravel-realtime-chat-app-scheduler-1 docker-laravel-realtime-chat-app-scheduler "sh -c 'while true; …" scheduler 19 minutes ago Up 19 minutes 9000/tcp
docker-laravel-realtime-chat-app-webserver-1 nginx:1.27.3-alpine "/docker-entrypoint.…" webserver 19 minutes ago Up 19 minutes 0.0.0.0:8000->80/tcp
docker-laravel-realtime-chat-app-workers-1 docker-laravel-realtime-chat-app-workers "docker-php-entrypoi…" workers 19 minutes ago Up 5 minutes 9000/tcp
Có vẻ mọi thứ đều oke mà ta?? Các service đều Up hết
Ê........ Sao 2 cái là reverb
và workers
lại có thời gian Up
ít hơn những cái còn lại nhờ???? 🧐🧐
Ta thử check logs workers:
docker compose logs workers
Ở trên ta để ý rằng, các log mới nhất của Workers thì có vẻ oke, nhưng kéo lên trên thì có lỗi không tìm thấy table trong database. Cái này ta đoán là lúc mới start lên thì worker nó không connect tới DB được, sau đó nó bị Docker restart và sau khi ta migrate
database thì nó chạy ngon
Tiếp theo ta check tới Reverb nhé:
docker compose logs reverb
Ầu, log toàn lỗi, khả năng là thanh niên này không được Docker restart rồi 🧐🧐
Thực tế là bởi vì cách vận hành của Reverb, nó sẽ tiếp tục chạy kể cả khi có lỗi, miễn là nó start lên là được php artisan reverb:start
, còn sau đó thì nó vẫn throw lỗi bình thường.
Vậy giờ cái ta cần làm là restart lại Reverb để nó connect đến DB cho đúng. Vì giờ DB của ta có data rồi mà
À ngoài vấn đề này ra thì ví dụ DB của ta sau này to đùng, mỗi lần up
mấy một lúc thì nếu đoạn đó các service khác kết nối tới sẽ bị báo Connection refused
nữa á 😪
Vậy nên để đảm bảo vấn đề này không xảy ra ta setup chút HEALTHCHECK
, và start các service theo thứ tự phù hợp sao cho cả kiến trúc trơn tru nha.
Trước khi làm thì ta tưởng tượng ra giao tiếp giữa các service của chúng ta tí nha:
- Ở trên ta có thể thấy rằng MySQL là trung tâm của cả kiến trúc
- Request đi từ trình duyệt vào đến Nginx, Nginx lại forward request vào Laravel Main App và Reverb Websocket server (dựa vào path mà khi nãy ta khai báo ở
nginx.app.conf
- Khi có 1 message gửi lên
Laravel App
thì nó sẽ lưu vào DB, Workers sẽ liên tụcpoll
data từ database về (ở bảngjobs
), và thực hiện, mỗi job xử lý xong thì nó sẽ bắn sang bên Reverb Websocket server để Reverb bắn ngược lại cho phía trình duyệt - Task Scheduler, thì chạy như cronjob, mỗi phút lại bắn ra 1 message (là 1 event vào bảng
jobs
)
Oke rồi giờ ta sửa lại docker-compose.yml
như sau:
services:
webserver:
image: nginx:1.27.3-alpine
ports:
- "8000:80"
working_dir: /app
volumes:
- ./:/app
- ./nginx.app.conf:/etc/nginx/conf.d/default.conf
restart: always
depends_on:
- app
- reverb
app:
build:
context: .
target: base
volumes:
- ./:/app
restart: always
depends_on:
db:
condition: service_healthy # Wait for the db service to be healthy (using the healthcheck defined below)
restart: true # Restart the service if db stops
reverb:
build:
context: .
target: reverb
volumes:
- ./:/app
restart: always
depends_on:
db:
condition: service_healthy
restart: true
workers:
build:
context: .
target: workers
volumes:
- ./:/app
restart: always
depends_on:
reverb:
condition: service_started
restart: true # Restart the service if reverb stops
db:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MYSQL_DATABASE=${DB_DATABASE}
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
volumes:
- .docker/data/db:/var/lib/mysql
restart: always
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD # in docker compose to reference an environment variable, you need to use $$ instead of $
interval: 10s # Check every 10 seconds
timeout: 5s # Timeout for each check
retries: 3 # Consider unhealthy after 3 consecutive failures
start_period: 30s # Wait 30 seconds before starting health checks
scheduler:
build:
context: .
target: base
volumes:
- ./:/app
entrypoint: ["sh", "-c", "while true; do php artisan schedule:run; sleep 60; done"]
restart: always
depends_on:
db:
condition: service_healthy
restart: true
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8001:80
environment:
- PMA_HOST=${DB_HOST}
- PMA_PORT=${DB_PORT}
depends_on:
db:
condition: service_healthy
restart: true
Ở trên mình đã thêm vào HEALTHCHECK
cho db
, các service phụ thuộc vào db
thì phải chờ cho nó ở trạng thái service_healthy
mới start, với restart=true
thì nếu db
restart thì các service liên quan cũng restart theo để tạo connection mới
HEALTCHECK là gì thì hiểu đơn giản nó là 1 command ta chạy liên tục để check xem trạng thái của container có healthy không, cái này mình sẽ nói kĩ hơn ở các bài sau nha 😘
Với
workers
, vì nó phụ thuộc cả vàoreverb
vàdb
, màreverb
cũng phụ thuộc vàodb
, nên ta chỉ cần khai báo nó phụ thuộc vàoreverb
là đủ
Giờ ta restart lại project nhé:
docker compose down
docker compose up -d
Sau khi start ta sẽ thấy các service liên quan phải chờ cho db
healthy mới start 😎
Sau đó ta quay lại trình duyệt, F5, vàaaaaaa........ Vẫn không thấy realtime đâu 😂😂, check console thấy báo như trên
Ở đây ta có thấy 2 điều lạ:
- sao nó lại connect tới
localhost:8080
?? Cả kiến trúc của ta chỉ cówebserver
chạy ở cổng 8000, vàphpMyAdmin
ở8001
- Thứ nữa là sao cái path của nó không bắt đầu bằng
/reverb
?? 🧐
Ahihi mình quên, ở .env
ta cần phải sửa lại như sau:
VITE_REVERB_PORT=8000
VITE_REVERB_PATH="/reverb"
Ở trên ta cần update biến VITE_REVERB_PORT
, dùng cổng 8000, vì khi này là ta sẽ connect vào webserver
, rồi từ đó proxy qua reverb
.
Tiếp đó ta cần thêm vào biến VITE_REVERB_PATH
, để cái path nó được chính xác.
Oke rồi vì đây là những thay đổi phía frontend nên ta cần build lại:
# MacOS + Linux
docker run --rm -v $(pwd):/app -w /app node:22-alpine npm run build
# If Windows see below:
# Git bash
docker run --rm -v "/$(pwd)":/app -w //app node:22-alpine npm run build
# PowerShell
docker run --rm -v "$(pwd):/app" -w /app node:22-alpine npm run build
# Command Prompt
docker run --rm -v "%cd%:/app" -w /app node:22-alpine npm run build
Vì ta đã mount volume từ môi trường gốc vào trong container nên các bạn có thể sửa trực tiếp code PHP và sẽ thấy thay đổi nhé. Chú ý nếu sửa code VueJS thì nhớ là phải chạy
npm run build
như khi nãy nhen 😉
Giờ ta restart lại project nha:
docker compose down
docker compose up -d
Âu cây, giờ ta quay lại trình duyệt F5, mở 2 tab login 2 account và chat với nhau sẽ thấy mọi thứ ngon lành nha, các bạn tự thẩm nhé 😎😎
Ta để ý xem cứ mỗi phút thì Bot có gửi message không nha
Ta có thể xem Laravel Telescope để monitor phần request khi click vào Telescope
:
Click vào Pulse
sẽ show thông tin kết nối websocket các thứ:
phpMyAdmin
được truy cập từ địa chỉ http://localhost:8001
, ta login với DB_USERNAME
+ DB_PASSWORD
khai báo ở .env
:
Kết bài
Phùuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu........ Bài cũng dài ý chứ bộ 😁😁, hàng tỉ kiến thức mới, mắt mờ, đầu óc quay cuồng. Nhưng nếu so với phiên bản trước đây của bài blog này thì mình đã thu gọn đi rất nhiều rồi á: https://gist.github.com/maitrungduc1410/d6adc5345c70dbbcebb9788ab16b1229
Ê mà đến cuối bài kể ra chúng ta mới sửa có vài files từ source ban đầu của mình á 😂😂
Có Docker phát tiện liền, triển khai phát một 😎😎
Trong bài có rất nhiều kiến thức liên quan đến Linux, mình không thể giải thích từng tí từng tí một cho các bạn được, nhưng các bạn có thể tự copy paste và search google là sẽ có hết thông tin nhé.
Qua bài này hi vọng các bạn đã hiểu được cách Dockerize một project Laravel full options 😘, đầy đủ các thứ như DB, Websocket, Queue Job, Cronjob, ... trong sẽ như thế nào nhé. Từ đó vận dụng và áp dụng vào thực tế cho phù hợp nhé. Đồng thời ta cũng để ý là dù cho ta dùng nhiều thứ MySQL, Nginx, phpMyAdmin, PHP, Composer NodeJS... thế nhưng toàn bộ đều được chạy trong container Docker, môi trường gốc của ta vẫn "trinh nguyên" nhé 😎😎
Nếu có vấn đề gì thì các bạn cứ để lại comment cho mình nha.
Cám ơn các bạn đã theo dõi. Hẹn gặp lại các bạn vào những bài sau 👋👋
All rights reserved