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
- 🚀 Laravel Reverb with Docker: Production Guide
- 📋 Table of Contents
- 1. Introduction
- 2. Project Structure & Docker Compose
- 3. Reverb Installation & Laravel Setup
- 4. Environment Configuration (.env)
- 5. Nginx Proxy Configuration
- 6. Broadcasting Events
- 7. Frontend (Echo) Setup
- 8. Real-Time UI Update Example
- 9. Production Checklist & Troubleshooting
- 10. Conclusion
- 11. Production Security: Use HTTPS and WSS
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
- Install Reverb and Echo dependencies:
composer require laravel/reverb php artisan install:broadcasting npm install laravel-echo pusher-js
- Add Reverb service to Docker Compose (see above).
- 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
andphp 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
, andstorage/logs/laravel.log
- Use
ShouldBroadcastNow
for instant event delivery
Common issues:
cURL error 7: Failed to connect to 0.0.0.0
– setREVERB_HOST
to the Docker service name, not0.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:
- Obtain SSL certificates (e.g., from Let's Encrypt) and place them in your nginx container (e.g.,
docker/nginx/ssl/
). - 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";
}
# ...
}
- Map your SSL certificates in docker-compose:
nginx:
# ...
volumes:
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
- Update your .env for frontend:
VITE_REVERB_SCHEME=https
VITE_REVERB_PORT=443
- Update Echo config:
window.Echo = new Echo({
// ...
forceTLS: true, // or: import.meta.env.VITE_REVERB_SCHEME === 'https',
wsPort: 80,
wssPort: 443,
// ...
});
- 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
andforceTLS: true
in Echo config - Your frontend connects via
wss://yourdomain.com/app/...
Warning:
Browsers will block
ws://
(insecure WebSocket) connections from pages loaded overhttps://
. Always usewss://
in production!
For more Laravel & DevOps tips, follow Dominik Jasiński on LinkedIn