dockerwebsocketddev

DDEV: Laravel + Vite + Websockets with Soketi - trouble with port configuration


I am trying to get a local ddev setup working with the stack:

I am confused on how to configure ports on ddev, and which host/port i should use on the frontend.

.ddev/config.yaml

name: laravel-vite-inertia
type: laravel
docroot: public
php_version: "8.1"
webserver_type: apache-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
  type: mysql
  version: "8.0"
nfs_mount_enabled: false
mutagen_enabled: false
use_dns_when_possible: true
composer_version: "2"
web_environment: []
nodejs_version: "16"

.ddev/docker-compose.vite.yaml

# Override the web container's standard HTTP_EXPOSE and HTTPS_EXPOSE services
# to expose port 3000 of DDEV's web container.
version: '3.6'
services:
  web:
    # ports are a list of exposed *container* ports
    ports:
      - "3000"
      - "6001:6001"
    environment:
      - HTTP_EXPOSE=${DDEV_ROUTER_HTTP_PORT}:80,${DDEV_MAILHOG_PORT}:8025,3001:3000
      - HTTPS_EXPOSE=${DDEV_ROUTER_HTTPS_PORT}:80,${DDEV_MAILHOG_HTTPS_PORT}:8025,3000:3000

/resources/js/app.js

import Echo from 'laravel-echo';
import pusher from 'pusher-js';
let laravelEcho = new Echo({
    broadcaster: 'pusher',
    key: 'app-key',
    wsHost: '127.0.0.1', // <- I assume this is the error?
    wsPort: 6001,
    wssPort: 6001,
    forceTLS: false,
    encrypted: true,
    disableStats: true,
    enabledTransports: ['ws', 'wss'],
});

laravelEcho.channel(`auctions`)
    .listen('AuctionIndexVisited', (e) => {
        console.log('AuctionIndexVisited', e);
    });

laravel .env

#...
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_APP_KEY="app-key"
PUSHER_APP_ID="app-id"
PUSHER_APP_SECRET="app-secret"

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_HOST="${PUSHER_HOST}"
MIX_PUSHER_PORT="${PUSHER_PORT}"

Laravel does broadcasts to soketi. enter image description here

The frontend is not able to connect to the websocket...

enter image description here


Solution

  • Fix: Do not use port 6000.

    As it turns out, port 6000 is considered "unsafe" because its the the default port for X11.
    So I switched to port 9000.

    Vite -> Port 3000
    Soketi -> Port 9000

    My working setup:

    .ddev/docker-compose.vite.yaml

    # Override the web container's standard HTTP_EXPOSE and HTTPS_EXPOSE services
    # to expose port 3000 of DDEV's web container.
    version: '3.6'
    services:
      web:
        # ports are a list of exposed *container* ports
        ports:
          - "3000"
          - "9000"
        environment:
          - HTTP_EXPOSE=${DDEV_ROUTER_HTTP_PORT}:80,${DDEV_MAILHOG_PORT}:8025,3001:3000,9001:9000
          - HTTPS_EXPOSE=${DDEV_ROUTER_HTTPS_PORT}:80,${DDEV_MAILHOG_HTTPS_PORT}:8025,3000:3000,9000:9000
    

    vite.config.js

    import path from "path";
    import vue from '@vitejs/plugin-vue';
    import vuetify from '@vuetify/vite-plugin';
    
    export default ({command}) => ({
        base: command === 'serve' ? '' : '/dist/',
        publicDir: path.resolve(__dirname, 'resources/copy-to-public'),
        server: {
            host: '0.0.0.0',
            port: 3000,
        },
        plugins: [
            vue(),
            // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
            vuetify({
                autoImport: true,
            }),
        ],
        optimizeDeps: {
            include: [
                "@inertiajs/inertia",
                "@inertiajs/inertia-vue3",
                "axios",
                "vue",
            ],
        },
        build: {
            manifest: true,
            outDir: path.resolve(__dirname, 'public/dist'),
            rollupOptions: {
                input: '/resources/js/app.js',
            }
        },
        resolve: {
            alias: {
                '@': path.resolve(__dirname, 'resources/js'),
            },
            extensions: [
                '.js',
                '.json',
                '.jsx',
                '.mjs',
                '.ts',
                '.tsx',
                '.vue',
            ]
        },
    });
    
    

    Add localhost:3000 to sanctum config.

    /config/sanctum.php

    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
        '%s%s',
        'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
        Sanctum::currentApplicationUrlWithPort()
    ))),    
    

    soketi.config.json
    -> Caveat: run soketi not inside your root dir! It will read the laravel .env and not work as expected.

    {
        "debug": true,
        "ip": "0.0.0.0",
        "address": "0.0.0.0",
        "host": "0.0.0.0",
        "port": 9000,
        "appManager.array.apps": [
            {
                "id": "app-id",
                "key": "app-key",
                "secret": "app-secret",
                "webhooks": [
                ]
            }
        ]
    }
    

    I am using soketi as pusher replacement. Part of my app.js:

    import Echo from 'laravel-echo';
    import pusher from 'pusher-js';
    window.laravelEcho = new Echo({
        broadcaster: 'pusher',
        key: "app-key",
        wsHost: "mysite.ddev.site",
        wsPort: 9000,
        wssPort: 9000,
        forceTLS: false,
        encrypted: true,
        disableStats: true,
        enabledTransports: ['ws', 'wss'],
    });
    

    /resources/views/app.blade.php

    <!doctype html>
    <html>
    <head>
        <title>Laravel, Vue, Inertia and Vite</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <meta charset="UTF-8" />
        @include('partials/favicon')
        <link rel="stylesheet" href="/dist/fonts/materialdesignicons.min.css" />
        @production
            @php
                $manifest = json_decode(file_get_contents(
                    public_path('dist/manifest.json')
                ), JSON_OBJECT_AS_ARRAY);
            @endphp
            <script type="module" src="/dist/{{ $manifest['resources/js/app.js']['file'] }}"></script>
            <link rel="stylesheet" href="/dist/{{ $manifest['resources/js/app.js']['css'][0] }}" />
        @else
            <script type="module" src="{{ env('APP_URL') }}:3000/@vite/client"></script>
            <script type="module" src="{{ env('APP_URL') }}:3000/resources/js/app.js"></script>
        @endproduction
    
        <meta name="csrf-token" content="{{ csrf_token() }}">
        @routes
    </head>
    <body>
    @inertia
    </body>
    </html>
    

    .env

    # Toggle between "production" and "local" for vite
    # "production" == vite build
    # "local" == vite dev
    APP_ENV=local
    APP_DEBUG=true
    
    # ....
    BROADCAST_DRIVER=pusher
    # ....
    
    # Pusher config
    PUSHER_HOST=127.0.0.1
    PUSHER_APP_HOST=127.0.0.1
    PUSHER_PORT=9000
    PUSHER_APP_KEY="app-key"
    PUSHER_APP_ID="app-id"
    PUSHER_APP_SECRET="app-secret"
    

    for vite dev to work.

    Part of my package.json

    "scripts": {
        "dev": "vite serve",
        "soketi": "cd .. && soketi start --config=html/soketi-config.json",
        "prod": "vite build",
        "build": "vite build"
    },