Real-time Notifications: Laravel Echo Server with Docker and Traefik

Jeff Turcotte, Director of Engineering
Posted on Mar 29, 2018

One of my favorite projects in the Laravel ecosystem is Echo. Echo enables real-time web applications through the use of WebSockets and hooks directly into Laravel's event broadcasting features. This means developers can use a familiar PHP API to send real-time data. A very common use-case for this type of functionality would be a notification or chat system.

So let's do it! Here's how to build a simple notification system, using only web standards and open source tools.

Start with an open source WebSocket server and proxy

The Laravel documentation tends to point developers towards integrating with Pusher, a commercial product which provides the WebSocket connection to your users, but there is an open source, self-hosted alternative based around Socket.IO: Laravel Echo Server. If you have strict project parameters or need a free alternative, Echo server is for you.

Let's configure Laravel and Echo Server to run on Docker behind a Traefik proxy. Traefik is a load balancer that auto-configures itself based on container labels. In my opionion, the best part about Traefik is that you can offload all TLS termination to it. With a little information from each running container, it automatically takes care of all certificate management with Let's Encrypt. That means hands-off, free, and auto-renewing HTTPS, and in the case of Echo, a secure WebSocket connection over HTTPS.

Instead of doing a step by step walkthrough, here is a reference repository ( with installation instructions below. We'll then dive a little deeper into a few of the pieces that make this work.

If you're looking for a peek into every piece of the setup, browse the commit log.

How To Install And Run

# clone repo
git clone
cd laravel-echo-example

# install php dependencies
composer install
composer run-script install-tasks

# install npm dependencies and build
npm install
npm run prod

# start docker services docker-compose up

# Advanced/Optional: to deploy to a real public domain with Traefik's TLS management enabled, do this: docker-compose -f docker-compose.yml up

Open up in your browser to see it in action.

Docker Compose Configuration

Below is the docker-compose.yml configuration. We are doing a few things here:

  • Using container labels to configure Traefik. You can use these to configure hostnames, ports, and even things like basic auth. The labels tell Traefik to proxy those containers. We then can limit the open ports to the Traefik container.
  • Echo is being built from a local Dockerfile and not an existing image due to the lack of community supported images for Echo Server.
  • Due to Docker's internal DNS, we can reference all our services with simple hostnames such as 'redis' or 'mysql' from any other service. This comes in very handy for doing authentication between Echo Server and Laravel, where Echo Server can reference Laravel as 'www' directly in its configuration.
version: '2'

 image: traefik
 command: --docker.domain=${DOMAIN} --logLevel=DEBUG
 - "80:80"
 - "443:443"
 - /var/run/docker.sock:/var/run/docker.sock
 - ./traefik/traefik.toml:/etc/traefik/traefik.toml
 - ./traefik/acme.json:/etc/traefik/acme/acme.json

 image: imarcagency/php-apache:2
 - "APACHE_ROOT=/var/www/public"
 - "traefik.enable=true"
 - "traefik.frontend.rule=Host:www.${DOMAIN}"
 - "traefik.port=80"
 - "./:/var/www"
 - "./"
 - "app_storage:/var/www/storage"

 image: "mariadb:10.3"
 - "MYSQL_USER=app"
 - "mysql_data:/var/lib/mysql"

 image: "redis:3.2"
 command: "redis-server --appendonly yes"
 - "redis_data:/data"

 build: ./echo
 - "traefik.enable=true"
 - "traefik.frontend.rule=Host:echo.${DOMAIN}"
 - "traefik.port=80"
 working_dir: "/usr/src/app"
 - "./:/usr/src/app"


Echo Configuration

Because everything is behind Traefik, we don't have to worry about setting up HTTPS or certs for production deployments. Traefik will terminate TLS so Echo can run on port 80 and be easily made available under its own separate hostname.

Within our welcome.blade.php file, we'll pass in some of our .env vars to a global JS variable and ensure the client side Echo library can configure itself properly.

# excerpt from welcome.blade.php

 window.echoConfig = {
 host: {!! json_encode(env('ECHO_HOST')) !!},
 port: {!! json_encode(env('ECHO_PORT')) !!}

<!-- Scripts -->
<script src="//{{ env('ECHO_HOST') }}/"></script>
<script src="{{ asset('js/app.js') }}"></script>

And in bootstrap.js:

# excerpt from bootstrap.js

import Echo from 'laravel-echo'

window.Echo = new Echo({
 broadcaster: '',
 host: `${}:${window.echoConfig.port}`

Triggering & Listening for Events

I created a very simple Laravel Test Event to play with. Laravel Event objects broadcast all public properties to the specified channels. It's important that all Echo events implement the ShouldBroadcast interface. I also made a quick HTTP handler to trigger the event:

# excerpt from routes/web.php

Route::get('/broadcast-test', function() {
 event(new TestEvent('The server time is now ' . date('H:i:s')));

This handler will trigger the event with a message that displays the server time. The event is listened for by a Vue component that displays a list of the returned messages. The component also container a trigger to hit the HTTP handler.

const app = new Vue({
 el: '#app',

 data() {
 return {
 messages: []

 mounted() {'global')
 .listen('TestEvent', (e) => {

 methods: {
 broadcast() {


Here it is. Nothing flashy, just a simple PoC to trigger and receive events. If you open it up in two browsers at once, you get to see the full power and real-time capability of Laravel Echo. Click the button in one window, and the messages will simultaneously update across all windows.

Now go impress your friends and clients!

More Thoughts