+22

Deploy ứng dụng Docker Laravel Realtime Chat

Cập nhật gần nhất: 05/12/2024

Chào mừng các bạn đã quay trở lại với series học Docker và CICD của mình. 👋👋

Ở bài trước chúng ta đã cùng nhau deploy ứng dụng NodeJS với Docker, setup domain name, HTTPS cùng với đó là hoài niệm so sánh với cách deploy truyền thống ngày xưa để thấy được sự đơn giản và nhanh gọn hơn rất nhiều.

Bài này chúng ta sẽ tiếp tục cùng nhau thử deploy project Laravel Realtime Chat App bằng Docker xem thế nào nhé

Bắt đầu thôi nào 🚀🚀

Tổng quan

Ở bài này ta sẽ deploy 1 app chat y hệt như của mình:https://realtime-chat.jamesisme.com. Dùng chính source code mà mình đang deploy luôn 😎

Screenshot 2024-12-05 at 11.44.36 AM.jpg

Mình khuyến khích các bạn xem lại bài mình viết về deploy app chat này nhưng là phiên bản không có Docker (làm theo kiểu truyền thống). Từ đó ta có cái nhìn rõ nét nhất về sự tuyệt vời mà Docker mang lại 😘

App chat này có khá đầy đủ các component như bất kì 1 app Laravel nào mà thường đi làm các bạn sẽ gặp:

  • Laravel, PHP (đương nhiên 😌)
  • Worker, Telescope, Pulse
  • Laravel Echo
  • Laravel Reverb
  • Task Scheduling
  • MySQL + phpMyAdmin (trình quản trị CSDL trên web)
  • Nginx + HTTPS

Và tất cả các container trong bài này sẽ đều được chạy bằng non-root user nhé 💪💪

Điều kiện tiên quyết

Vì bài này ta demo deploy trên server nên đương nhiên các bạn sẽ cần có server của riêng mình (VPS), nhớ là VPS chứ không phải Hosting thông thường nhé. (nên mua của các nhà cung cấp lớn như Google, AWS, Azure, Digital Ocean)

Cùng với đó ta sẽ setup HTTPS nên ta sẽ cần 1 tên miền (domain name), các bạn lên Godaddy mua 1 tên miền cùi cùi về để học tập nhé (~ 100K VND là cùng 🤪)

Setup

Clone source

Đầu tiên các bạn clone source code của mình ở đây: https://github.com/maitrungduc1410/realtime-chatapp-laravelecho-socketio. Nhánh master nha

Tổng quan

Và như thường lệ trước khi deploy lên server ta cần test trước để đảm bảo là code của chúng ta hoạt động ổn nhé 😎

Cùng điểm qua file docker-compose.non-root.yml xem ứng dụng của ta có những thành phần nào nha:

  • service webserver: ở đây ta có 1 container Nginx, nhận request từ bên ngoài và proxy vào service App, cùng với đó là phần kết nối realtime thì sẽ chuyển vào service Laravel Reverb (bên dưới). Cấu hình của service này nằm ở nginx.app.conf. Ta cũng cần custom cấu hình tổng của nginx ở file nginx.conf một chút để nó chạy oke với non-root user
  • service app: ở đây ta sẽ cài PHP, chạy PHP-FPM, chạy Laravel Telescope + Pulse
  • service reverb: Websocket server, nhận event từ Laravel và bắn realtime về trình duyệt
  • service workers: xử lý message ở trong queue job, sau đó bắn sang reverb
  • service db: chạy MySQL
  • service scheduler: chạy cronjob, mỗi phút Bot sẽ tự động gửi tin nhắn vào phòng chat
  • service phpmyadmin: trình quản trị CSDL trên web

Xem qua Dockerfile chút nhé:

FROM php:8.3.9-fpm-alpine3.20 AS base
WORKDIR /app
COPY . .
# 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"]

FROM base AS nonroot
RUN addgroup -g 1000 myusergroup
RUN adduser -D -u 1000 myuser -G myusergroup
RUN chown -R myuser:myusergroup .
USER myuser

FROM nonroot AS reverb-nonroot
CMD ["php", "artisan", "reverb:start"]

FROM nonroot AS workers-nonroot
CMD ["php", "artisan", "queue:work"]

Như các bạn thấy ở đây mình có những setup rất gọn gàng thôi:

  • Ta có 1 stage base, ở đó ta setup dependency để connect tới MySQL cho PHP
  • Tiếp đó ta có các stage khác như reverb, workers, và có stage nonroot dành cho việc chạy container với non-root user. Các stage reverb-nonrootworkers-nonroot thì ta FROM từ nonroot
  • docker-compose.non-root.yml, với service appscheduler thì vì ta không cần custom CMD lúc start container, nên ta sẽ đơn giản là target trực tiếp nonroot thôi

Test ở local

Tiếp theo ta sẽ tiến hành build image và test ở local xem nhen 💪

Ở bài này ta sẽ chạy tất cả các container với non-root user. Ta mặc định với nhau là dùng user 1000:1000 nhé. Code mình đã setup sẵn tất cả mọi thứ cho user 1000:1000 rồi các bạn không cần làm gì thêm cả 😘

Các bạn không biết chọn ID nào thì lấy 1000:1000 nhé không có lại loạn 😊

Bước này chỉ dành cho Linux, bạn nào đang dùng Docker trên Windows + Mac thì có thể bỏ qua

Nếu các bạn muốn chạy với user khác thì sửa lại Dockerfile của laravel-echo-server đi nhé.

Ơ, vậy còn service phpMyAdmin thì sao? 🧐

Với service phpMyAdmin này thì hơi đặc biệt chút, vì hiện tại image phpmyadmin chưa chính thức support non-root user, nên nếu ta cứ dùng nonroot thì lúc start sẽ gặp lỗi, đúng là lý thuyết ta vẫn có thể làm được, nhưng setup hơi dài dòng, và lan man, nên với cái này ta đặc cách vẫn dùng root nha 😁

Tiếp theo ta tạo file .env (ta đơn giản là copy từ file .env.docker, mình setup hết tên Host các service, password các thứ, bạn muốn đổi thì thoải mái update nhé):

cp .env.docker .env

Oke, tiếp thì ở root folder project ta tạo các folder .docker/data/db để lưu data cho MySQl

Sau đó, thì ta đổi lại permission của toàn bộ folder về 1000:1000 nhé:

# chỉ dành cho Linux thôi. Windows + Mac có thể bỏ qua
sudo chown -R 1000:1000 .

Ở một số bản Linux, nếu các bạn là root sẵn rồi mà dùng sudo, nó có thể báo Name or service not known, thì ta bỏ sudo đi là được

Tiếp đó ta sẽ install dependencies sau đó build cho PHP/Laravel và cả phần frontend VueJS nhé:

# MacOS + Linux
  docker run --rm -u 1000:1000 -v $(pwd):/app -w /app composer:2.8.3 install

  docker run --rm -u 1000:1000 -v $(pwd):/app -w /app node:22-alpine npm install

  docker run --rm -u 1000:1000 -v $(pwd):/app -w /app node:22-alpine npm run build


  # If Windows see below:

  # Git bash
  docker run --rm -u 1000:1000 -v "/$(pwd)":/app -w //app composer:2.8.3 install

  docker run --rm -u 1000:1000 -v "/$(pwd)":/app -w //app node:22-alpine npm install

  docker run --rm -u 1000:1000 -v "/$(pwd)":/app -w //app node:22-alpine npm run build

  # PowerShell
  docker run --rm -u 1000:1000 -v "$(pwd):/app" -w /app composer:2.8.3 install

  docker run --rm -u 1000:1000 -v "$(pwd):/app" -w /app node:22-alpine npm install

  docker run --rm -u 1000:1000 -v "$(pwd):/app" -w /app node:22-alpine npm run build

  # Command Prompt
  docker run --rm -u 1000:1000 -v "%cd%:/app" -w /app composer:2.8.3 install

  docker run --rm -u 1000:1000 -v "%cd%:/app" -w /app node:22-alpine npm install

  docker run --rm -u 1000:1000 -v "%cd%:/app" -w /app node:22-alpine npm run build

Ổn rồi đó, ta tiến hành khởi động build và khởi động project luôn nhé:

docker compose -f docker-compose.non-root.yml up -d --build

chú ý rằng ở bài này tất cả các command docker compose ta phải thêm vào -f docker-compose.non-root.yml, bởi vì ở bài này ta có 2 file docker-compose đó, 1 file dành cho chạy với root user và 1 file chạy với non-root

Sau đó ta tạo app key

docker compose -f docker-compose.non-root.yml exec app php artisan key:generate

Tiếp đó là ta tạo các bảng database và tạo ít data sẵn nhé:

docker compose -f docker-compose.non-root.yml exec app php artisan migrate --seed

Cuối cùng ta mở trình duyệt, truy cập ở địa chỉ localhost:8000 tạo 2 account và thử chat xem nhé. Phần này mình để các bạn tự sướng 😘

Screenshot 2024-12-05 at 12.13.15 PM.jpg

Khi chạy lên mà không thấy realtime, ví dụ không hiển thị danh sách user, hoặc nhắn tin không realtime, thì khả năng là các bạn cần down đi rồi up project lại, vì đoạn đầu ta chưa có bảng database thì reverb + worker có thể có lỗi

Khi chạy lên nếu các bạn gặp lỗi Access Denied for user... thì 96.69% là volume của db có lỗi, các bạn docker compose -f docker-compose.non-root.yml down, sau đó xoá folder .docker/non-root/data/db và tạo lại folder đó, cuối cùng là docker compose -f docker-compose.non-root.yml up -d là được nhé

Deploy

Sau khi ta đã đảm bảo là source code của mình chạy ổn định, thì ta tiến hành deploy trên server nhé.

Đầu tiên các bạn cần SSH vào server của các bạn trước.

Sau đó ta sẽ chuyển tới một folder nào đó để làm việc nhé, bất kì chỗ nào cũng đc 😁 Sau đó ta tiến hành clone code về:

git clone https://github.com/maitrungduc1410/realtime-chatapp-laravelecho-socketio

cd realtime-chatapp-laravelecho-socketio

Sau khi clone về thì ta tiến hành setup, các bước setup thì y hệt như lúc test ở local, các bạn tự làm lại theo các bước đó nha. Chú ý đoạn tạo folder .docker/data/dbchown đó nhé, vì VPS giờ là Linux rồi đó 😎😎

À thêm nữa là vì giờ ta deploy trên server thật thì ở .env, biến VITE_REVERB_HOST ta phải set thành IP của server của các bạn nha:

VITE_REVERB_HOST="1.2.3.4"

Đổi xong nhớ "npm run build" lại nhé

Ta vẫn start project như bình thường:

docker compose -f docker-compose.non-root.yml up -d --build

# Tạo key
docker compose -f docker-compose.non-root.yml exec app php artisan key:generate

# Migrate database
docker compose -f docker-compose.non-root.yml exec app php artisan migrate --seed

Và nếu bây giờ các bạn quay lại trình duyệt truy cập ở địa chỉ server_ip:8000 thì với một số cloud provider sẽ chưa được đâu vì như các bài deploy trước mình đã nói là VPS của ta mặc định ban đầu không cho phép traffic từ bên ngoài vào bất kì port nào mà ta phải mở bằng tay, chỉ định cụ thể port nào muốn mở. Cái này ta cũng có thể gặp phải khi ta có Firewall bọc ngoài VPS, ví dụ như của mình dùng Digital Ocean:

Screenshot 2024-12-05 at 12.26.01 PM.jpg

Ở trên các bạn thấy, firewall của mình ban đầu chỉ cho traffic đi vào 1 số cổng như 22,80,443, và mình phải tạo thêm 2 rules cho cổng 8000 và 8001 ở bài này

Ngay sau đó ta quay lại trình duyệt, truy cập ở địa chỉ server_ip:8000 sẽ thấy kết quả ngay nha:

Screenshot 2024-12-05 at 12.43.23 PM.jpg

Các bạn thử tạo 2 account và có thể thấy rằng đến đây ta đã có thể chat realtime ngon nghẻ rồi nha:

HTTPS

Phần này thì làm y hệt như phần lấy HTTPS ở bài deploy NodeJS, các bạn mở lại bài đó xem và làm theo là được nhé. Ý tưởng vẫn là chạy riêng 1 con Nginx ở môi trường ngoài đóng vai trò như 1 Security layer, HTTPS sẽ được lấy ở đây, cho tất cả các project phía sau (giả sử sau này chúng ta có nhiều project Docker chẳng hạn).

Các bạn đừng nhầm con Nginx ở môi trường ngoài này với Nginx trong ở service webserver nha 😂. Với kiểu này thì bên ngoài đoạn xử lý HTTPS nó chỉ proxy request vào trong thôi chứ nó chả cần quan tâm là bên trong lại dùng tiếp 1 con nginx hay gì... kiểu này thì sẽ dễ để bê cả architecture của chúng ta đi 1 nơi khác, ví dụ VPS khác, hoặc lên Kubernetes

Note: Sau khi các bạn lấy HTTPS thành công thì ta cần phải phải update chút cấu hình để phần realtime chạy ngon. Ta update các biến sau ở .env

APP_FORCE_HTTPS=true

VITE_REVERB_HOST="example.com"
VITE_REVERB_PORT="443"
VITE_REVERB_SCHEME="https"

VITE_REVERB_HOST sửa thành domain của các bạn nhé

Sau đó ta build lại Frontend:

# MacOS + Linux
docker run --rm -u 1000:1000 -v $(pwd):/app -w /app node:22-alpine npm run build


# If Windows see below:

# Git bash
docker run --rm -u 1000:1000 -v "/$(pwd)":/app -w //app node:22-alpine npm run build

# PowerShell
docker run --rm -u 1000:1000 -v "$(pwd):/app" -w /app node:22-alpine npm run build

# Command Prompt
docker run --rm -u 1000:1000 -v "%cd%:/app" -w /app node:22-alpine npm run build

Chú ý rằng bài này ta có 2 service là webserverphpmyadmin là được expose ra internet, ta có thể lấy HTTPS cho 2 chúng nó 😘

So sánh với cách deploy truyền thống

Đến đây ta đã hoàn thành việc deploy ứng dụng Laravel chat realtime 🤟🤟

Và nếu nhìn lại bài Deploy app chat realtime trên Ubuntu, ở bài đó ta cũng deploy app y hệt như thế này, chỉ là làm theo kiểu truyền thống, không có Docker. Ta sẽ thấy rằng ở cách cũ ta phải cài mệt nghỉ, setup vỡ mặt thớt 😂😂

Còn với Docker, ta chỉ cần Dockerize 1 lần, local chạy ngon thì ra server làm nhoắng 1 cái là lên luôn. Không cần phải setup đi setup lại nữa

Kết bài

Qua bài này ta đã deploy thành công một project Laravel với đầy đủ các thành phần gần giống như khi đi làm thường gặp.

Ta thấy được rằng deploy với Docker đã giúp ta rút ngắt được rất rất nhiều thời gian, cùng với đó an toàn hơn nhiều so với cách truyền thống, vì với project Laravel này nếu làm theo cách truyền thống ta phải cài 1 lố vào trong môi trường gốc, và đây chính là cội nguồn của vô vàn vấn đề 😂😂

Cũng có nhiều người có hỏi mình là "nghe bảo Laravel + Docker" chạy chậm ở production. Nếu ai nói như vậy thì các bạn có thể show kết quả các bạn làm trong bài này cho họ, hoặc demo của mình cho họ thấy nhé 😉

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí