DEV Community

Artem Kharlamov
Artem Kharlamov

Posted on

1

Configuring Laravel Reverb with Docker + Nginx in local and production environment.

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Configure nginx for local or production environment

To set up a container nginx you will need to open two ports:

  1. Port for listening laravel app.
  2. 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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/;
        }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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/;
    }

}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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'],
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope this article will help you if you encounter a similar problem. Good luck to everyone and bye!

Sentry image

Make it make sense

Make sense of fixing your code with straight-forward application monitoring.

Start debugging →

Top comments (2)

Collapse
 
__f87cb1a profile image
Дмитрий Желованов

Thank you very much! I had a similar problem and this article helped me solve it

Collapse
 
artem_kharlamov73_458eea profile image
Artem Kharlamov

🙏🙏🙏

DevCycle image

Ship Faster, Stay Flexible.

DevCycle is the first feature flag platform with OpenFeature built-in to every open source SDK, designed to help developers ship faster while avoiding vendor-lock in.

Start shipping

👋 Kindness is contagious

Discover fresh viewpoints in this insightful post, supported by our vibrant DEV Community. Every developer’s experience matters—add your thoughts and help us grow together.

A simple “thank you” can uplift the author and spark new discussions—leave yours below!

On DEV, knowledge-sharing connects us and drives innovation. Found this useful? A quick note of appreciation makes a real impact.

Okay