🚀 Laravel Reverb with Docker: Production Guide

This guide will show you how to set up a real-time Laravel application using Laravel Reverb (WebSockets) in a production Docker environment, based on a working, production-ready configuration.

📋 Table of Contents


1. Introduction

Laravel Reverb is the official WebSocket server for Laravel. With Docker, you can run Reverb as a separate service, proxy WebSocket traffic through Nginx, and use Laravel Echo on the frontend for real-time features.


2. Project Structure & Docker Compose

docker-compose.production.yml (relevant parts):

services:
  app:
    image: ...
    container_name: laravel_app
    # ...
    networks:
      - laravel_network
    depends_on:
      - redis

  reverb:
    image: ...
    container_name: laravel_reverb
    command: php artisan reverb:start --host=0.0.0.0 --port=8080 --debug
    volumes:
      - ./.env:/var/www/.env
    ports:
      - "6001:8080" # (optional, for direct access)
    depends_on:
      - redis
    networks:
      - laravel_network

  nginx:
    image: ...
    container_name: laravel_nginx
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - laravel_storage:/var/www/storage
    networks:
      - laravel_network
    depends_on:
      - app
      - reverb

  redis:
    image: redis:alpine
    container_name: laravel_redis
    # ...
    networks:
      - laravel_network

networks:
  laravel_network:
    driver: bridge

volumes:
  laravel_storage:
  redis_data:

3. Reverb Installation & Laravel Setup

  1. Install Reverb and Echo dependencies:
    composer require laravel/reverb
    php artisan install:broadcasting
    npm install laravel-echo pusher-js
    
  2. Add Reverb service to Docker Compose (see above).
  3. Configure broadcasting in Laravel: In config/broadcasting.php, set:
    'default' => env('BROADCAST_DRIVER', 'reverb'),
    ...
    'connections' => [
        'reverb' => [
            'driver' => 'reverb',
            'key' => env('REVERB_APP_KEY'),
            'secret' => env('REVERB_APP_SECRET'),
            'app_id' => env('REVERB_APP_ID'),
            'host' => env('REVERB_HOST', 'reverb'),
            'port' => env('REVERB_PORT', 8080),
            'scheme' => env('REVERB_SCHEME', 'http'),
        ],
        ...
    ]
    

4. Environment Configuration (.env)

Backend (.env):

BROADCAST_DRIVER=reverb
REVERB_APP_ID=your_app_id
REVERB_APP_KEY=your_app_key
REVERB_APP_SECRET=your_app_secret
REVERB_HOST=reverb
REVERB_PORT=8080
REVERB_SCHEME=http

# For frontend build (Vite):
VITE_REVERB_APP_KEY=your_app_key
VITE_REVERB_HOST=your_public_domain_or_ip
VITE_REVERB_PORT=80
VITE_REVERB_SCHEME=http
  • REVERB_HOST must match the service name in docker-compose (here: reverb).
  • VITE_REVERB_HOST must be the public domain or IP your frontend will use to connect.

5. Nginx Proxy Configuration

docker/nginx/conf.d/default.conf (relevant part):

server {
    listen 80;
    server_name _;
    # ...

    location /app {
        proxy_pass http://reverb:8080;
         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";
    }
    # ...
}
  • This proxies /app to the Reverb container on port 8080.
  • Echo will connect to /app on your public domain/IP.

6. Broadcasting Events

Example event:

// app/Events/ClickEvent.php
namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ClickEvent implements ShouldBroadcast
{
    use Dispatchable, SerializesModels;

    public function broadcastOn(): Channel
    {
        return new Channel('clicks');
    }

    public function broadcastAs(): string
    {
        return 'Clicked';
    }
}

Dispatching the event in a controller:

// app/Http/Controllers/ClickController.php
public function click(Request $request)
{
    Click::firstOrCreate()->increment('times');

    event(new ClickEvent());

    return redirect()->route('clicks.index');
}

7. Frontend (Echo) Setup

resources/js/echo.js

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 ?? window.location.hostname,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
    enabledTransports: ['ws'],
    disableStats: true,
    auth: {
        headers: {
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
        }
    }
});

8. Real-Time UI Update Example

React component (Click.tsx):

import { useState, useEffect } from 'react';

export default function Click({ click }) {
    const [times, setTimes] = useState(click?.times || 0);

    useEffect(() => {
        const channel = window.Echo.channel('clicks');
        channel.listen('.Clicked', () => {
            setTimes(prev => prev + 1); // Or fetch new value from backend if needed
        });
        return () => {
            window.Echo.leave('clicks');
        };
    }, []);

    // ...handlers for click/reset...
}
  • This way, the UI updates in real time for all users without a full reload.

9. Production Checklist & Troubleshooting

  • REVERB_HOST in .env is set to the Docker service name (reverb)
  • VITE_REVERB_HOST is set to your public domain/IP
  • Nginx proxies /app to the Reverb container
  • All containers are on the same Docker network
  • php artisan config:clear and php artisan cache:clear run after changing .env
  • WebSocket port is not exposed to the public (unless needed)
  • Use HTTPS in production for security (update VITE_REVERB_SCHEME and nginx config)
  • Check logs: docker logs laravel_reverb, docker logs laravel_nginx, and storage/logs/laravel.log
  • Use ShouldBroadcastNow for instant event delivery

Common issues:

  • cURL error 7: Failed to connect to 0.0.0.0 – set REVERB_HOST to the Docker service name, not 0.0.0.0.
  • WebSocket connects but no events: check event dispatch, broadcasting config, and logs.
  • WebSocket disconnects after 60s: increase proxy_read_timeout in nginx.

10. Conclusion

With this setup, you have a robust, production-ready real-time Laravel application using Reverb and Docker. You can now broadcast events and update your frontend instantly for all users.


11. Production Security: Use HTTPS and WSS

For production deployments, always use HTTPS for your application and WSS (WebSocket Secure) for WebSocket connections. This ensures that all data transmitted between your users and your server is encrypted and secure.

How to enable HTTPS/WSS:

  1. Obtain SSL certificates (e.g., from Let's Encrypt) and place them in your nginx container (e.g., docker/nginx/ssl/).
  2. Update your nginx config to listen on port 443 and use SSL:
server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    # ...
    location /app {
        proxy_pass http://reverb:8080;
        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";
    }
    # ...
}
  1. Map your SSL certificates in docker-compose:
nginx:
  # ...
  volumes:
    - ./docker/nginx/ssl:/etc/nginx/ssl:ro
  1. Update your .env for frontend:
VITE_REVERB_SCHEME=https
VITE_REVERB_PORT=443
  1. Update Echo config:
window.Echo = new Echo({
    // ...
    forceTLS: true, // or: import.meta.env.VITE_REVERB_SCHEME === 'https',
    wsPort: 80,
    wssPort: 443,
    // ...
});
  1. Redirect HTTP to HTTPS in nginx for best security.

Checklist:

  • SSL certificates are valid and mapped to nginx
  • Nginx listens on 443 and proxies /app as before
  • VITE_REVERB_SCHEME=https and forceTLS: true in Echo config
  • Your frontend connects via wss://yourdomain.com/app/...

Warning:

Browsers will block ws:// (insecure WebSocket) connections from pages loaded over https://. Always use wss:// in production!


For more Laravel & DevOps tips, follow Dominik Jasiński on LinkedIn

Comments (0)
Leave a comment

© 2025 All rights reserved.