Hello everyone, recently I have a problem with setting up Laravel Reverb in Docker. It took me a lot of time, but I found a working solution and am ready to share it with you. I will not tell you about the full Docker setup, I will tell you about the aspects that are necessary for Reverb. Thus, there will be two containers: nginx and laravel application.
Setting up Laravel App container
How to install reverb to your project you can read in the documentation. An important point is that the reverb server will not work without the pcntl extension. To automatically start the reverb server, I will use supervisor, which also needs to be added to the container.
According to the documentation: before broadcasting events, you need to configure and run the queue worker. All broadcasting is done through tasks in queues, so events do not affect the application response time. The queue worker will also be launched by supervisor.
Example of installing packages and configuring supervisor in my Dockerfile:
FROM php:8.2-fpm
...
RUN apt-get update && \
...
apt-get install -y --no-install-recommends supervisor && \
docker-php-ext-install pcntl
...
COPY --from=composer /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
# Copy the Supervisor configuration file
COPY supervisord.conf /etc/supervisor/supervisord.conf
EXPOSE 9000
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]
Supervisor config file (supervisord.conf):
[supervisord]
logfile=/var/log/supervisord.log
pidfile=/var/log/supervisord.pid
nodaemon=true
user=root
[program:php-fpm]
command=/usr/local/sbin/php-fpm
numprocs=1
autostart=true
autorestart=true
stderr_logfile=/var/log/php-fpm_consumer.err.log
stdout_logfile=/var/log/php-fpm_consumer.out.log
priority=100
user=root
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=/usr/local/bin/php /var/www/artisan queue:work --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log
stopwaitsecs=300
[program:reverb-server]
process_name=%(program_name)s_%(process_num)02d
command=/usr/local/bin/php /var/www/artisan reverb:start
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/laravel-reverb.log
stopwaitsecs=300
Configure nginx for local or production environment
To set up a container nginx you will need to open two ports:
- Port for listening laravel app.
- Port for listening reverb server.
Setting up an nginx server for a PHP application is nothing special:
nginx/conf.d/app.conf
server {
listen 80;
server_name localhost;
root /var/www/public;
index index.php index.html index.htm;
client_max_body_size 16M;
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass your-php-container-name:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
The next step is to configure a reverse proxy for your reverb server in your nginx configuration. As you may have noticed, port 8070 is listening here, you can specify any other you want. I took the setting for the location /
from the documentation, as an IP address in proxy_pass
you need to use the name of your PHP docker container, since we work in a docker network.
Also here an additional location with the prefix /reverb/ is used. Why this is necessary I will tell a little later. I use double slash in location for strict matching. Another main thing to note is that at the end of the proxy_pass for the location /reverb/ I also added "/" - so that this prefix is ignored.
Also note that the port used in proxy_pass is 8080 - this is the default port for starting the reverb server. If you want to use other port, you can specify the REVERB_SERVER_PORT
setting in your .env.
nginx/conf.d/reverb.conf
server {
listen 8070;
server_name localhost;
root /var/www/public;
location / {
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";
proxy_pass http://your-php-container-name:8080;
}
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";
proxy_pass http://your-php-container-name:8080/;
}
}
The nginx.conf file is quite simple.
nginx/nginx.conf
user nginx;
worker_processes 4;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
#access_log /dev/stdout;
#error_log /dev/stderr;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
}
My docker-compose (please don't forget that you need to configure port forwarding for the nginx container):
php-fpm:
container_name: app
build:
context: ./php-fpm
depends_on:
- db
volumes:
- ./src/:/var/www
- ./php-fpm/php.ini:/usr/local/etc/php/php.ini
nginx:
container_name: test-nginx
image: nginx:alpine
volumes:
- ./src:/var/www
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
- ./logs/nginx:/var/log/nginx
depends_on:
- php-fpm
ports:
- "8050:80"
- "8090:8070"
Setting up in local and production environments
If you are developing the application locally, or your situation is different from mine, then there is no need to use an additional prefix when accessing the reverb server. Then in the reverb.conf setting it is enough to use location /
. But here I will try to explain why it is necessary to use the reverb prefix for me.
My production environment has a complex structure. Several servers are used that communicate with each other. On the first server, the application is also deployed in Docker. But there is also a second server with its own nginx, where the domain and SSL certificates are configured and it communicates with the dockerised nginx of the first server. In order for requests to be distributed and reach where needed, the /reverb prefix was entered, so that websocket requests go to the reverb server, and other requests go to the laravel application.
This approach allows me to make the configuration of my dockerised nginx universal and applicable to both local and production environments.
My production server nginx config:
server {
listen 443 ssl;
server_name my-domain.com;
ssl_sertificate /path/to/certificate
ssl_sertificate_key /path/to/certificate-key
client_max_body_size 50M;
location / {
proxy_pass http://my-server-ip:8050;
}
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";
proxy_pass http://my-server-ip:8090/;
}
}
So the request /reverb/app/...
will be passed as http://my-server-ip:8090/app/...
It is also worth noting that the .env setup will be different in the local environment and in production.
This pair of environment variables REVERB_HOST
and REVERB_PORT
specifies where the reverb server should send broadcast events. So here we need to specify the address of our nginx container inside the docker network.
This pair of environment variables VITE_REVERB_HOST
and VITE_REVERB_PORT
is needed to configure Echo and initialize the websocket connection on the client side. Here you need to specify the address in the external network.
Local .env:
BROADCAST_DRIVER=reverb
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="your-nginx-container-name"
REVERB_PORT=8070
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="localhost"
VITE_REVERB_PORT="8090"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
VITE_REVERB_PATH="/reverb"
Production .env:
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="your-nginx-container-name"
REVERB_PORT=8070
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="my-domain.com"
VITE_REVERB_PORT="443"
VITE_REVERB_SCHEME="https"
VITE_REVERB_PATH="/reverb"
Oh yeah, and I almost forgot, setting up pusher for our application:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT,
wssPort: import.meta.env.VITE_REVERB_PORT,
wsPath: import.meta.env.VITE_REVERB_PATH,
wssPath: import.meta.env.VITE_REVERB_PATH,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
Conclusion
I hope this article will help you if you encounter a similar problem. Good luck to everyone and bye!
Top comments (2)
Thank you very much! I had a similar problem and this article helped me solve it
🙏🙏🙏