feat(realtime): replace Soketi image with bundled Reverb

Run Laravel Reverb and the terminal server inside the Coolify container instead of shipping a separate coolify-realtime image. Update compose files, proxy routing, cleanup behavior, and packaging tests for the embedded realtime services.
This commit is contained in:
Andras Bacsai
2026-06-03 15:43:20 +02:00
parent bc165f1976
commit 3a49baad19
64 changed files with 1336 additions and 645 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ yarn-error.log
/.ssh
.ignition.json
.env.dusk.local
docker/coolify-realtime/node_modules
docker/coolify-terminal/node_modules
/storage/*.key
/storage/app/backups
+2
View File
@@ -10,6 +10,8 @@ REDIS_PASSWORD=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_PORT=6001
PUSHER_BACKEND_PORT=6001
ROOT_USERNAME=
ROOT_USER_EMAIL=
+2
View File
@@ -11,3 +11,5 @@ REDIS_PASSWORD=coolify
PUSHER_APP_ID=coolify
PUSHER_APP_KEY=coolify
PUSHER_APP_SECRET=coolify
PUSHER_PORT=6001
PUSHER_BACKEND_PORT=6001
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
package: ['coolify', 'coolify-helper', 'coolify-realtime', 'coolify-testing-host']
package: ['coolify', 'coolify-helper', 'coolify-testing-host']
steps:
- name: Delete untagged ${{ matrix.package }} images
uses: actions/delete-package-versions@v5
@@ -6,11 +6,8 @@ on:
paths-ignore:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- .github/workflows/coolify-realtime.yml
- .github/workflows/coolify-realtime-next.yml
- .github/workflows/pr-quality.yaml
- docker/coolify-helper/Dockerfile
- docker/coolify-realtime/Dockerfile
- docker/testing-host/Dockerfile
- templates/**
- CHANGELOG.md
-120
View File
@@ -1,120 +0,0 @@
name: Coolify Realtime Development
on:
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-realtime-next.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/package-lock.json
- docker/coolify-realtime/soketi-entrypoint.sh
permissions:
contents: read
packages: write
env:
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
build-push:
strategy:
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: aarch64
platform: linux/aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build and Push Image (${{ matrix.arch }})
uses: docker/build-push-action@v6
with:
context: .
file: docker/coolify-realtime/Dockerfile
platforms: ${{ matrix.platform }}
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-${{ matrix.arch }}
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-${{ matrix.arch }}
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-24.04
needs: build-push
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create \
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-amd64 \
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-amd64 \
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
-120
View File
@@ -1,120 +0,0 @@
name: Coolify Realtime
on:
push:
branches: [ "v4.x" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/package-lock.json
- docker/coolify-realtime/soketi-entrypoint.sh
permissions:
contents: read
packages: write
env:
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
build-push:
strategy:
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: aarch64
platform: linux/aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build and Push Image (${{ matrix.arch }})
uses: docker/build-push-action@v6
with:
context: .
file: docker/coolify-realtime/Dockerfile
platforms: ${{ matrix.platform }}
push: true
tags: |
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-${{ matrix.arch }}
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-${{ matrix.arch }}
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-24.04
needs: build-push
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
docker buildx imagetools create \
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-amd64 \
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
run: |
docker buildx imagetools create \
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-amd64 \
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
@@ -9,11 +9,8 @@ on:
paths-ignore:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- .github/workflows/coolify-realtime.yml
- .github/workflows/coolify-realtime-next.yml
- .github/workflows/pr-quality.yaml
- docker/coolify-helper/Dockerfile
- docker/coolify-realtime/Dockerfile
- docker/testing-host/Dockerfile
- templates/**
- CHANGELOG.md
-2
View File
@@ -6,8 +6,6 @@ on:
paths-ignore:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- .github/workflows/coolify-realtime.yml
- .github/workflows/coolify-realtime-next.yml
- .github/workflows/pr-quality.yaml
workflow_dispatch:
+1 -1
View File
@@ -34,7 +34,7 @@ _ide_helper_models.php
scripts/load-test/*
.ignition.json
.env.dusk.local
docker/coolify-realtime/node_modules
docker/coolify-terminal/node_modules
.DS_Store
CHANGELOG.md
/.workspaces
+1 -1
View File
@@ -248,7 +248,7 @@ If you encounter issues or break your database or something else, follow these s
2. Remove all Coolify containers:
```bash
docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
docker rm coolify coolify-db coolify-redis coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
```
3. Remove Coolify volumes (it is possible that the volumes have no `coolify` prefix on your machine, in that case remove the prefix from the command):
+6 -17
View File
@@ -13,12 +13,6 @@ class CleanupDocker
public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false)
{
$realtimeImage = config('constants.coolify.realtime_image');
$realtimeImageVersion = config('constants.coolify.realtime_version');
$realtimeImageWithVersion = "$realtimeImage:$realtimeImageVersion";
$realtimeImageWithoutPrefix = 'coollabsio/coolify-realtime';
$realtimeImageWithoutPrefixVersion = "coollabsio/coolify-realtime:$realtimeImageVersion";
$helperImageVersion = getHelperVersion();
$helperImage = config('constants.coolify.helper_image');
$helperImageWithVersion = "$helperImage:$helperImageVersion";
@@ -38,13 +32,12 @@ class CleanupDocker
$cleanupLog = array_merge($cleanupLog, $applicationCleanupLog);
// Build image prune command that excludes application images and current Coolify infrastructure images
// This ensures we clean up non-Coolify images while preserving rollback images and current helper/realtime images
// This ensures we clean up non-Coolify images while preserving rollback images and current helper image
// Note: Only the current version is protected; old versions will be cleaned up by explicit commands below
// We pass the version strings so all registry variants are protected (ghcr.io, docker.io, no prefix)
$imagePruneCmd = $this->buildImagePruneCommand(
$applicationImageRepos,
$helperImageVersion,
$realtimeImageVersion
$helperImageVersion
);
$commands = [
@@ -53,9 +46,7 @@ class CleanupDocker
'docker builder prune -af',
"docker run --rm -v \$HOME/.docker/buildx:/root/.docker/buildx -v /var/run/docker.sock:/var/run/docker.sock {$helperImageWithVersion} docker buildx prune --builder coolify-railpack -af 2>/dev/null || true",
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
"docker images --filter before=$realtimeImageWithVersion --filter reference=$realtimeImage | grep $realtimeImage | awk '{print $3}' | xargs -r docker rmi -f",
"docker images --filter before=$helperImageWithoutPrefixVersion --filter reference=$helperImageWithoutPrefix | grep $helperImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f",
"docker images --filter before=$realtimeImageWithoutPrefixVersion --filter reference=$realtimeImageWithoutPrefix | grep $realtimeImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f",
];
if ($deleteUnusedVolumes) {
@@ -87,8 +78,7 @@ class CleanupDocker
*/
private function buildImagePruneCommand(
$applicationImageRepos,
string $helperImageVersion,
string $realtimeImageVersion
string $helperImageVersion
): string {
// Step 1: Always prune dangling images (untagged)
$commands = ['docker image prune -f'];
@@ -105,14 +95,13 @@ class CleanupDocker
// - ghcr.io/coollabsio/coolify-helper:1.0.12
// - docker.io/coollabsio/coolify-helper:1.0.12
// - coollabsio/coolify-helper:1.0.12
// Pattern: (^|/)coollabsio/coolify-(helper|realtime):VERSION$
// Pattern: (^|/)coollabsio/coolify-helper:VERSION$
$escapedHelperVersion = preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $helperImageVersion);
$escapedRealtimeVersion = preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $realtimeImageVersion);
$infraExcludePattern = "(^|/)coollabsio/coolify-helper:{$escapedHelperVersion}$|(^|/)coollabsio/coolify-realtime:{$escapedRealtimeVersion}$";
$infraExcludePattern = "(^|/)coollabsio/coolify-helper:{$escapedHelperVersion}$";
// Delete unused images that:
// - Are not application images (don't match app repos)
// - Are not current Coolify infrastructure images (any registry)
// - Are not current Coolify helper image (any registry)
// - Don't have coolify.managed=true label
// Images in use by containers will fail silently with docker rmi
// Pattern matches both uuid:tag and uuid_servicename:tag (Docker Compose with build)
+29 -9
View File
@@ -481,13 +481,20 @@ class Server extends BaseModel
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
],
'coolify-realtime-ws' => [
'coolify-reverb-ws' => [
'entryPoints' => [
0 => 'http',
],
'service' => 'coolify-realtime',
'service' => 'coolify-reverb',
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
],
'coolify-reverb-api' => [
'entryPoints' => [
0 => 'http',
],
'service' => 'coolify-reverb',
'rule' => "Host(`{$host}`) && PathPrefix(`/apps`)",
],
'coolify-terminal-ws' => [
'entryPoints' => [
0 => 'http',
@@ -506,11 +513,11 @@ class Server extends BaseModel
],
],
],
'coolify-realtime' => [
'coolify-reverb' => [
'loadBalancer' => [
'servers' => [
0 => [
'url' => 'http://coolify-realtime:6001',
'url' => 'http://coolify:6001',
],
],
],
@@ -519,7 +526,7 @@ class Server extends BaseModel
'loadBalancer' => [
'servers' => [
0 => [
'url' => 'http://coolify-realtime:6002',
'url' => 'http://coolify:6002',
],
],
],
@@ -543,16 +550,26 @@ class Server extends BaseModel
'certresolver' => 'letsencrypt',
],
];
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
$traefik_dynamic_conf['http']['routers']['coolify-reverb-wss'] = [
'entryPoints' => [
0 => 'https',
],
'service' => 'coolify-realtime',
'service' => 'coolify-reverb',
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
'tls' => [
'certresolver' => 'letsencrypt',
],
];
$traefik_dynamic_conf['http']['routers']['coolify-reverb-api-https'] = [
'entryPoints' => [
0 => 'https',
],
'service' => 'coolify-reverb',
'rule' => "Host(`{$host}`) && PathPrefix(`/apps`)",
'tls' => [
'certresolver' => 'letsencrypt',
],
];
$traefik_dynamic_conf['http']['routers']['coolify-terminal-wss'] = [
'entryPoints' => [
0 => 'https',
@@ -590,10 +607,13 @@ class Server extends BaseModel
$caddy_file = "
$schema://$host {
handle /app/* {
reverse_proxy coolify-realtime:6001
reverse_proxy coolify:6001
}
handle /apps* {
reverse_proxy coolify:6001
}
handle /terminal/ws {
reverse_proxy coolify-realtime:6002
reverse_proxy coolify:6002
}
reverse_proxy coolify:8080
}";
-10
View File
@@ -1,10 +0,0 @@
<?php
// To prevent github actions from failing
function env()
{
return null;
}
$version = include 'config/constants.php';
echo $version['coolify']['realtime_version'] ?: 'unknown';
+1
View File
@@ -22,6 +22,7 @@
"laravel/nightwatch": "^1.24",
"laravel/pail": "^1.2.4",
"laravel/prompts": "^0.3.11|^0.3.11|^0.3.11",
"laravel/reverb": "^1.10",
"laravel/sanctum": "^4.3.0",
"laravel/socialite": "^5.24.2",
"laravel/tinker": "^2.11.0",
Generated
+846 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "64b77285a7140ce68e83db2659e9a21d",
"content-hash": "31bd5f70ceaa27f7153302600b5cd453",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -341,6 +341,136 @@
],
"time": "2024-02-09T16:56:22+00:00"
},
{
"name": "clue/redis-protocol",
"version": "v0.3.2",
"source": {
"type": "git",
"url": "https://github.com/clue/redis-protocol.git",
"reference": "6f565332f5531b7722d1e9c445314b91862f6d6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/clue/redis-protocol/zipball/6f565332f5531b7722d1e9c445314b91862f6d6c",
"reference": "6f565332f5531b7722d1e9c445314b91862f6d6c",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"psr-4": {
"Clue\\Redis\\Protocol\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@lueck.tv"
}
],
"description": "A streaming Redis protocol (RESP) parser and serializer written in pure PHP.",
"homepage": "https://github.com/clue/redis-protocol",
"keywords": [
"parser",
"protocol",
"redis",
"resp",
"serializer",
"streaming"
],
"support": {
"issues": "https://github.com/clue/redis-protocol/issues",
"source": "https://github.com/clue/redis-protocol/tree/v0.3.2"
},
"funding": [
{
"url": "https://clue.engineering/support",
"type": "custom"
},
{
"url": "https://github.com/clue",
"type": "github"
}
],
"time": "2024-08-07T11:06:28+00:00"
},
{
"name": "clue/redis-react",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/clue/reactphp-redis.git",
"reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/clue/reactphp-redis/zipball/84569198dfd5564977d2ae6a32de4beb5a24bdca",
"reference": "84569198dfd5564977d2ae6a32de4beb5a24bdca",
"shasum": ""
},
"require": {
"clue/redis-protocol": "^0.3.2",
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"php": ">=5.3",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.0 || ^1.1",
"react/promise-timer": "^1.11",
"react/socket": "^1.16"
},
"require-dev": {
"clue/block-react": "^1.5",
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"psr-4": {
"Clue\\React\\Redis\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering"
}
],
"description": "Async Redis client implementation, built on top of ReactPHP.",
"homepage": "https://github.com/clue/reactphp-redis",
"keywords": [
"async",
"client",
"database",
"reactphp",
"redis"
],
"support": {
"issues": "https://github.com/clue/reactphp-redis/issues",
"source": "https://github.com/clue/reactphp-redis/tree/v2.8.0"
},
"funding": [
{
"url": "https://clue.engineering/support",
"type": "custom"
},
{
"url": "https://github.com/clue",
"type": "github"
}
],
"time": "2025-01-03T16:18:33+00:00"
},
{
"name": "danharrin/livewire-rate-limiting",
"version": "v2.2.0",
@@ -972,6 +1102,53 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "evenement/evenement",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/igorw/evenement.git",
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^9 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Evenement\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
}
],
"description": "Événement is a very simple event dispatching library for PHP",
"keywords": [
"event-dispatcher",
"event-emitter"
],
"support": {
"issues": "https://github.com/igorw/evenement/issues",
"source": "https://github.com/igorw/evenement/tree/v3.0.2"
},
"time": "2023-08-08T05:53:35+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.19.0",
@@ -2443,6 +2620,85 @@
},
"time": "2026-05-19T00:47:18+00:00"
},
{
"name": "laravel/reverb",
"version": "v1.10.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/reverb.git",
"reference": "43a5c0a99b1aaba33dc32f97fcf51f182dd8c8ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/reverb/zipball/43a5c0a99b1aaba33dc32f97fcf51f182dd8c8ac",
"reference": "43a5c0a99b1aaba33dc32f97fcf51f182dd8c8ac",
"shasum": ""
},
"require": {
"clue/redis-react": "^2.6",
"guzzlehttp/psr7": "^2.6",
"illuminate/console": "^10.47|^11.0|^12.0|^13.0",
"illuminate/contracts": "^10.47|^11.0|^12.0|^13.0",
"illuminate/http": "^10.47|^11.0|^12.0|^13.0",
"illuminate/support": "^10.47|^11.0|^12.0|^13.0",
"laravel/prompts": "^0.1.15|^0.2.0|^0.3.0",
"php": "^8.2",
"pusher/pusher-php-server": "^7.2",
"ratchet/rfc6455": "^0.4",
"react/promise-timer": "^1.10",
"react/socket": "^1.14",
"symfony/console": "^6.0|^7.0|^8.0",
"symfony/http-foundation": "^6.3|^7.0|^8.0"
},
"require-dev": {
"orchestra/testbench": "^8.36|^9.15|^10.8|^11.0",
"pestphp/pest": "^2.0|^3.0|^4.0",
"phpstan/phpstan": "^1.10",
"ratchet/pawl": "^0.4.1",
"react/async": "^4.2",
"react/http": "^1.9"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Reverb\\ApplicationManagerServiceProvider",
"Laravel\\Reverb\\ReverbServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Reverb\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Joe Dixon",
"email": "joe@laravel.com"
}
],
"description": "Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.",
"keywords": [
"WebSockets",
"laravel",
"real-time",
"websocket"
],
"support": {
"issues": "https://github.com/laravel/reverb/issues",
"source": "https://github.com/laravel/reverb/tree/v1.10.2"
},
"time": "2026-05-10T15:47:52+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.3.2",
@@ -6293,6 +6549,595 @@
},
"time": "2025-12-14T04:43:48+00:00"
},
{
"name": "ratchet/rfc6455",
"version": "v0.4.0",
"source": {
"type": "git",
"url": "https://github.com/ratchetphp/RFC6455.git",
"reference": "859d95f85dda0912c6d5b936d036d044e3af47ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/859d95f85dda0912c6d5b936d036d044e3af47ef",
"reference": "859d95f85dda0912c6d5b936d036d044e3af47ef",
"shasum": ""
},
"require": {
"php": ">=7.4",
"psr/http-factory-implementation": "^1.0",
"symfony/polyfill-php80": "^1.15"
},
"require-dev": {
"guzzlehttp/psr7": "^2.7",
"phpunit/phpunit": "^9.5",
"react/socket": "^1.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Ratchet\\RFC6455\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"role": "Developer"
},
{
"name": "Matt Bonneau",
"role": "Developer"
}
],
"description": "RFC6455 WebSocket protocol handler",
"homepage": "http://socketo.me",
"keywords": [
"WebSockets",
"rfc6455",
"websocket"
],
"support": {
"chat": "https://gitter.im/reactphp/reactphp",
"issues": "https://github.com/ratchetphp/RFC6455/issues",
"source": "https://github.com/ratchetphp/RFC6455/tree/v0.4.0"
},
"time": "2025-02-24T01:18:22+00:00"
},
{
"name": "react/cache",
"version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/cache.git",
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"react/promise": "^3.0 || ^2.0 || ^1.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Async, Promise-based cache interface for ReactPHP",
"keywords": [
"cache",
"caching",
"promise",
"reactphp"
],
"support": {
"issues": "https://github.com/reactphp/cache/issues",
"source": "https://github.com/reactphp/cache/tree/v1.2.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2022-11-30T15:59:55+00:00"
},
{
"name": "react/dns",
"version": "v1.14.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/dns.git",
"reference": "7562c05391f42701c1fccf189c8225fece1cd7c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3",
"reference": "7562c05391f42701c1fccf189c8225fece1cd7c3",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"react/cache": "^1.0 || ^0.6 || ^0.5",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4.3 || ^3 || ^2",
"react/promise-timer": "^1.11"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Dns\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Async DNS resolver for ReactPHP",
"keywords": [
"async",
"dns",
"dns-resolver",
"reactphp"
],
"support": {
"issues": "https://github.com/reactphp/dns/issues",
"source": "https://github.com/reactphp/dns/tree/v1.14.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2025-11-18T19:34:28+00:00"
},
{
"name": "react/event-loop",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/event-loop.git",
"reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
"reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"suggest": {
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\EventLoop\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
"keywords": [
"asynchronous",
"event-loop"
],
"support": {
"issues": "https://github.com/reactphp/event-loop/issues",
"source": "https://github.com/reactphp/event-loop/tree/v1.6.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2025-11-17T20:46:25+00:00"
},
{
"name": "react/promise",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
"shasum": ""
},
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.12.28 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2025-08-19T18:57:03+00:00"
},
{
"name": "react/promise-timer",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise-timer.git",
"reference": "4f70306ed66b8b44768941ca7f142092600fafc1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4f70306ed66b8b44768941ca7f142092600fafc1",
"reference": "4f70306ed66b8b44768941ca7f142092600fafc1",
"shasum": ""
},
"require": {
"php": ">=5.3",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.7.0 || ^1.2.1"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\Timer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
"homepage": "https://github.com/reactphp/promise-timer",
"keywords": [
"async",
"event-loop",
"promise",
"reactphp",
"timeout",
"timer"
],
"support": {
"issues": "https://github.com/reactphp/promise-timer/issues",
"source": "https://github.com/reactphp/promise-timer/tree/v1.11.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2024-06-04T14:27:45+00:00"
},
{
"name": "react/socket",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/socket.git",
"reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08",
"reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08",
"shasum": ""
},
"require": {
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"php": ">=5.3.0",
"react/dns": "^1.13",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
"react/stream": "^1.4"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4.3 || ^3.3 || ^2",
"react/promise-stream": "^1.4",
"react/promise-timer": "^1.11"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Socket\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
"keywords": [
"Connection",
"Socket",
"async",
"reactphp",
"stream"
],
"support": {
"issues": "https://github.com/reactphp/socket/issues",
"source": "https://github.com/reactphp/socket/tree/v1.17.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2025-11-19T20:47:34+00:00"
},
{
"name": "react/stream",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/stream.git",
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
"shasum": ""
},
"require": {
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"php": ">=5.3.8",
"react/event-loop": "^1.2"
},
"require-dev": {
"clue/stream-filter": "~1.2",
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Stream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
"keywords": [
"event-driven",
"io",
"non-blocking",
"pipe",
"reactphp",
"readable",
"stream",
"writable"
],
"support": {
"issues": "https://github.com/reactphp/stream/issues",
"source": "https://github.com/reactphp/stream/tree/v1.4.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2024-06-11T12:45:25+00:00"
},
{
"name": "resend/resend-laravel",
"version": "v0.20.0",
+23 -5
View File
@@ -11,11 +11,11 @@ return [
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "ably", "redis", "log", "null"
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'pusher'),
'default' => env('BROADCAST_CONNECTION', env('BROADCAST_DRIVER', 'reverb')),
/*
|--------------------------------------------------------------------------
@@ -30,15 +30,33 @@ return [
'connections' => [
'reverb' => [
'driver' => 'reverb',
'key' => env('PUSHER_APP_KEY', 'coolify'),
'secret' => env('PUSHER_APP_SECRET', 'coolify'),
'app_id' => env('PUSHER_APP_ID', 'coolify'),
'options' => [
'host' => env('PUSHER_HOST', 'coolify'),
'port' => env('PUSHER_BACKEND_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'http'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'http') === 'https',
'path' => '',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY', 'coolify'),
'secret' => env('PUSHER_APP_SECRET', 'coolify'),
'app_id' => env('PUSHER_APP_ID', 'coolify'),
'options' => [
'host' => env('PUSHER_BACKEND_HOST', 'coolify-realtime'),
'port' => env('PUSHER_BACKEND_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'http'),
'host' => env('PUSHER_HOST'),
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
-2
View File
@@ -4,14 +4,12 @@ return [
'coolify' => [
'version' => '4.1.2',
'helper_version' => '1.0.14',
'realtime_version' => '1.0.16',
'railpack_version' => '0.23.0',
'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
'registry_url' => env('REGISTRY_URL', 'ghcr.io'),
'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-helper'),
'realtime_image' => env('REALTIME_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-realtime'),
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
'cdn_url' => env('CDN_URL', 'https://cdn.coollabs.io'),
'versions_url' => env('VERSIONS_URL', env('CDN_URL', 'https://cdn.coollabs.io').'/coolify/versions.json'),
+102
View File
@@ -0,0 +1,102 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Reverb Server
|--------------------------------------------------------------------------
|
| This option controls the default server used by Reverb to handle
| incoming messages as well as broadcasting message to all your
| connected clients. At this time only "reverb" is supported.
|
*/
'default' => 'reverb',
/*
|--------------------------------------------------------------------------
| Reverb Servers
|--------------------------------------------------------------------------
|
| Here you may define details for each of the supported Reverb servers.
| Each server has its own configuration options that are defined in
| the array below. You should ensure all the options are present.
|
*/
'servers' => [
'reverb' => [
'host' => '0.0.0.0',
'port' => env('PUSHER_BACKEND_PORT', 6001),
'path' => '',
'hostname' => env('PUSHER_HOST'),
'options' => [
'tls' => [],
],
'max_request_size' => 10_000,
'scaling' => [
'enabled' => false,
'channel' => 'reverb',
'server' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', '6379'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DB', '0'),
'timeout' => env('REDIS_TIMEOUT', 60),
],
],
'pulse_ingest_interval' => 15,
'telescope_ingest_interval' => 15,
],
],
/*
|--------------------------------------------------------------------------
| Reverb Applications
|--------------------------------------------------------------------------
|
| Here you may define how Reverb applications are managed. If you choose
| to use the "config" provider, you may define an array of apps which
| your server will support, including their connection credentials.
|
*/
'apps' => [
'provider' => 'config',
'apps' => [
[
'key' => env('PUSHER_APP_KEY', 'coolify'),
'secret' => env('PUSHER_APP_SECRET', 'coolify'),
'app_id' => env('PUSHER_APP_ID', 'coolify'),
'options' => [
'host' => env('PUSHER_HOST', 'coolify'),
'port' => env('PUSHER_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'http'),
'useTLS' => env('PUSHER_SCHEME', 'http') === 'https',
],
'allowed_origins' => ['*'],
'ping_interval' => 60,
'activity_timeout' => 30,
'max_connections' => null,
'max_message_size' => 10_000,
'accept_client_events_from' => 'members',
'rate_limiting' => [
'enabled' => false,
'max_attempts' => 60,
'decay_seconds' => 60,
'terminate_on_limit' => false,
],
],
],
],
];
+8 -29
View File
@@ -10,10 +10,13 @@ services:
- GROUP_ID=${GROUPID:-1000}
ports:
- "${APP_PORT:-8000}:8080"
- "${FORWARD_PUSHER_PORT:-6001}:6001"
- "6002:6002"
environment:
AUTORUN_ENABLED: false
PUSHER_HOST: "${PUSHER_HOST}"
PUSHER_PORT: "${PUSHER_PORT}"
PUSHER_HOST: "${PUSHER_HOST:-coolify}"
PUSHER_PORT: "${PUSHER_PORT:-6001}"
PUSHER_BACKEND_PORT: "${PUSHER_BACKEND_PORT:-6001}"
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
@@ -26,6 +29,8 @@ services:
volumes:
- .:/var/www/html/:cached
- dev_backups_data:/var/www/html/storage/app/backups
- ./docker/coolify-terminal/terminal-server.js:/terminal/terminal-server.js
- ./docker/coolify-terminal/terminal-utils.js:/terminal/terminal-utils.js
networks:
- coolify
postgres:
@@ -59,33 +64,7 @@ services:
timeout: 2s
volumes:
- dev_redis_data:/data
soketi:
image: coolify-realtime:dev
pull_policy: never
build:
context: .
dockerfile: ./docker/coolify-realtime/Dockerfile
env_file:
- .env
ports:
- "${FORWARD_SOKETI_PORT:-6001}:6001"
- "6002:6002"
volumes:
- ./storage:/var/www/html/storage
- ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js
- ./docker/coolify-realtime/terminal-utils.js:/terminal/terminal-utils.js
environment:
SOKETI_DEBUG: "false"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
SOKETI_HOST: "${SOKETI_HOST:-0.0.0.0}"
healthcheck:
test: [ "CMD-SHELL", "curl -fsS http://127.0.0.1:6001/ready && curl -fsS http://127.0.0.1:6002/ready || exit 1" ]
interval: 5s
retries: 10
timeout: 2s
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
vite:
image: node:24-alpine
pull_policy: always
+8 -29
View File
@@ -10,10 +10,13 @@ services:
- GROUP_ID=${GROUPID:-1000}
ports:
- "${APP_PORT:-8000}:8080"
- "${FORWARD_PUSHER_PORT:-6001}:6001"
- "6002:6002"
environment:
AUTORUN_ENABLED: false
PUSHER_HOST: "${PUSHER_HOST}"
PUSHER_PORT: "${PUSHER_PORT}"
PUSHER_HOST: "${PUSHER_HOST:-coolify}"
PUSHER_PORT: "${PUSHER_PORT:-6001}"
PUSHER_BACKEND_PORT: "${PUSHER_BACKEND_PORT:-6001}"
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
@@ -26,6 +29,8 @@ services:
volumes:
- .:/var/www/html/:cached
- dev_backups_data:/var/www/html/storage/app/backups
- ./docker/coolify-terminal/terminal-server.js:/terminal/terminal-server.js
- ./docker/coolify-terminal/terminal-utils.js:/terminal/terminal-utils.js
networks:
- coolify
postgres:
@@ -59,33 +64,7 @@ services:
timeout: 2s
volumes:
- dev_redis_data:/data
soketi:
image: coolify-realtime:dev
pull_policy: never
build:
context: .
dockerfile: ./docker/coolify-realtime/Dockerfile
env_file:
- .env
ports:
- "${FORWARD_SOKETI_PORT:-6001}:6001"
- "6002:6002"
volumes:
- ./storage:/var/www/html/storage
- ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js
- ./docker/coolify-realtime/terminal-utils.js:/terminal/terminal-utils.js
environment:
SOKETI_DEBUG: "false"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
SOKETI_HOST: "${SOKETI_HOST:-0.0.0.0}"
healthcheck:
test: [ "CMD-SHELL", "curl -fsS http://127.0.0.1:6001/ready && curl -fsS http://127.0.0.1:6002/ready || exit 1" ]
interval: 5s
retries: 10
timeout: 2s
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
vite:
image: node:24-alpine
pull_policy: always
+6 -22
View File
@@ -18,12 +18,18 @@ services:
- PHP_FPM_PM_START_SERVERS=${PHP_FPM_PM_START_SERVERS:-1}
- PHP_FPM_PM_MIN_SPARE_SERVERS=${PHP_FPM_PM_MIN_SPARE_SERVERS:-1}
- PHP_FPM_PM_MAX_SPARE_SERVERS=${PHP_FPM_PM_MAX_SPARE_SERVERS:-10}
- PUSHER_PORT=${PUSHER_PORT:-6001}
- PUSHER_BACKEND_PORT=${PUSHER_BACKEND_PORT:-6001}
env_file:
- /data/coolify/source/.env
ports:
- "${APP_PORT:-8000}:8080"
- "${PUSHER_PORT:-6001}:6001"
- "${TERMINAL_PORT:-6002}:6002"
expose:
- "${APP_PORT:-8000}"
- "${PUSHER_PORT:-6001}"
- "${TERMINAL_PORT:-6002}"
healthcheck:
test: curl --fail http://127.0.0.1:8080/api/health || exit 1
interval: 5s
@@ -34,8 +40,6 @@ services:
condition: service_healthy
redis:
condition: service_healthy
soketi:
condition: service_healthy
postgres:
volumes:
- coolify-db:/var/lib/postgresql/data
@@ -59,26 +63,6 @@ services:
interval: 5s
retries: 10
timeout: 2s
soketi:
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.16'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
volumes:
- /data/coolify/ssh:/var/www/html/storage/app/ssh
environment:
APP_NAME: "${APP_NAME:-Coolify}"
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
SOKETI_HOST: "${SOKETI_HOST:-0.0.0.0}"
healthcheck:
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
interval: 5s
retries: 10
timeout: 2s
volumes:
coolify-db:
name: coolify-db
+7 -24
View File
@@ -42,14 +42,20 @@ services:
- PUSHER_APP_ID
- PUSHER_APP_KEY
- PUSHER_APP_SECRET
- PUSHER_PORT
- PUSHER_BACKEND_PORT
- AUTOUPDATE=true
- SELF_HOSTED=true
- SSH_MUX_ENABLED=false
- IS_WINDOWS_DOCKER_DESKTOP=true
ports:
- "${APP_PORT:-8000}:8080"
- "${PUSHER_PORT:-6001}:6001"
- "${TERMINAL_PORT:-6002}:6002"
expose:
- "${APP_PORT:-8000}"
- "${PUSHER_PORT:-6001}"
- "${TERMINAL_PORT:-6002}"
healthcheck:
test: curl --fail http://localhost:8080/api/health || exit 1
interval: 5s
@@ -95,30 +101,7 @@ services:
interval: 5s
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.16'
pull_policy: always
container_name: coolify-realtime
restart: always
env_file:
- .env
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
volumes:
- ./ssh:/var/www/html/storage/app/ssh
environment:
APP_NAME: "${APP_NAME:-Coolify}"
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
SOKETI_HOST: "${SOKETI_HOST:-0.0.0.0}"
healthcheck:
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
interval: 5s
retries: 10
timeout: 2s
volumes:
coolify-db:
+3 -8
View File
@@ -5,12 +5,14 @@ services:
working_dir: /var/www/html
extra_hosts:
- host.docker.internal:host-gateway
expose:
- "6001"
- "6002"
networks:
- coolify
depends_on:
- postgres
- redis
- soketi
postgres:
image: postgres:15-alpine
container_name: coolify-db
@@ -23,13 +25,6 @@ services:
restart: always
networks:
- coolify
soketi:
container_name: coolify-realtime
extra_hosts:
- host.docker.internal:host-gateway
restart: always
networks:
- coolify
networks:
coolify:
name: coolify
-30
View File
@@ -1,30 +0,0 @@
# Versions
# https://github.com/soketi/soketi/releases
ARG SOKETI_VERSION=1.6-16-alpine
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2025.7.0
FROM quay.io/soketi/soketi:${SOKETI_VERSION}
ARG TARGETPLATFORM
ARG CLOUDFLARED_VERSION
WORKDIR /terminal
RUN apk upgrade --no-cache && \
apk add --no-cache openssh-client make g++ python3 curl
COPY docker/coolify-realtime/package*.json ./
RUN npm ci
RUN npm rebuild node-pty --update-binary
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
COPY docker/coolify-realtime/terminal-utils.js /terminal/terminal-utils.js
# Install Cloudflared based on architecture
RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \
elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \
fi && \
chmod +x /usr/local/bin/cloudflared
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]
@@ -1,91 +0,0 @@
#!/bin/sh
if [ "$1" = "watch" ]; then
WATCH_MODE="--watch"
else
WATCH_MODE=""
fi
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [ENTRYPOINT] $*"
}
start_logger() {
prefix="$1"
fifo_path="$2"
while read -r line; do
echo "$(date '+%Y-%m-%d %H:%M:%S') [$prefix] $line"
done < "$fifo_path" &
}
cleanup() {
rm -f "$TERMINAL_LOG_FIFO" "$SOKETI_LOG_FIFO"
}
TERMINAL_LOG_FIFO="/tmp/coolify-terminal-log.$$"
SOKETI_LOG_FIFO="/tmp/coolify-soketi-log.$$"
rm -f "$TERMINAL_LOG_FIFO" "$SOKETI_LOG_FIFO"
mkfifo "$TERMINAL_LOG_FIFO" "$SOKETI_LOG_FIFO"
trap cleanup EXIT
log "Starting realtime container"
log "WATCH_MODE=${WATCH_MODE:-off}"
log "SOKETI_DEBUG=${SOKETI_DEBUG:-unset}"
log "NODE_OPTIONS=${NODE_OPTIONS:-unset}"
start_logger "TERMINAL" "$TERMINAL_LOG_FIFO"
TERMINAL_LOGGER_PID=$!
start_logger "SOKETI" "$SOKETI_LOG_FIFO"
SOKETI_LOGGER_PID=$!
node $WATCH_MODE /terminal/terminal-server.js > "$TERMINAL_LOG_FIFO" 2>&1 &
TERMINAL_PID=$!
log "Terminal server started pid=$TERMINAL_PID logger_pid=$TERMINAL_LOGGER_PID"
node /app/bin/server.js start > "$SOKETI_LOG_FIFO" 2>&1 &
SOKETI_PID=$!
log "Soketi started pid=$SOKETI_PID logger_pid=$SOKETI_LOGGER_PID"
forward_signal() {
log "Forwarding signal $1 to terminal=$TERMINAL_PID soketi=$SOKETI_PID"
kill -"$1" "$TERMINAL_PID" 2>/dev/null || true
kill -"$1" "$SOKETI_PID" 2>/dev/null || true
}
trap 'forward_signal TERM' TERM
trap 'forward_signal INT' INT
while true; do
if ! kill -0 "$TERMINAL_PID" 2>/dev/null; then
wait "$TERMINAL_PID"
EXIT_CODE=$?
log "Terminal server exited code=$EXIT_CODE; stopping soketi pid=$SOKETI_PID"
kill "$SOKETI_PID" 2>/dev/null || true
wait "$SOKETI_PID" 2>/dev/null || true
exit "$EXIT_CODE"
fi
if ! kill -0 "$SOKETI_PID" 2>/dev/null; then
wait "$SOKETI_PID"
EXIT_CODE=$?
log "Soketi exited code=$EXIT_CODE; stopping terminal pid=$TERMINAL_PID"
kill "$TERMINAL_PID" 2>/dev/null || true
wait "$TERMINAL_PID" 2>/dev/null || true
exit "$EXIT_CODE"
fi
sleep 1
done
@@ -1,5 +1,5 @@
{
"name": "coolify-realtime",
"name": "coolify-terminal",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -15,7 +15,7 @@ import {
async function postToCoolify(path, headers) {
return new Promise((resolve, reject) => {
const request = http.request({
hostname: 'coolify',
hostname: process.env.TERMINAL_AUTH_HOST || '127.0.0.1',
port: 8080,
path,
method: 'POST',
+13
View File
@@ -57,6 +57,11 @@ RUN apk upgrade --no-cache && \
# Install system dependencies
RUN apk add --no-cache \
postgresql${POSTGRES_VERSION}-client \
nodejs \
npm \
make \
g++ \
python3 \
openssh-client \
git \
git-lfs \
@@ -85,6 +90,14 @@ RUN mkdir -p /usr/local/bin && \
COPY docker/development/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini
ENV PHP_OPCACHE_ENABLE=0
# Install terminal WebSocket server dependencies
COPY docker/coolify-terminal/package*.json /terminal/
RUN npm ci --prefix /terminal && \
npm rebuild node-pty --prefix /terminal --update-binary
COPY docker/coolify-terminal/terminal-server.js /terminal/terminal-server.js
COPY docker/coolify-terminal/terminal-utils.js /terminal/terminal-utils.js
RUN chown -R www-data:www-data /terminal
# Configure Nginx and S6 overlay
COPY docker/development/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf
COPY docker/development/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
cd /var/www/html
if grep -qE '^PUSHER_ENABLED=false' .env 2>/dev/null; then
echo " INFO Reverb is disabled, sleeping."
exec sleep infinity
fi
echo " INFO Reverb is enabled, starting..."
exec php artisan reverb:start --host=0.0.0.0 --port=${PUSHER_BACKEND_PORT:-6001}
@@ -0,0 +1 @@
longrun
@@ -0,0 +1,6 @@
#!/bin/sh
cd /var/www/html
echo " INFO Terminal WebSocket server is starting..."
exec node /terminal/terminal-server.js
@@ -0,0 +1 @@
longrun
+13
View File
@@ -107,6 +107,11 @@ RUN --mount=type=cache,target=/var/cache/apk \
apk upgrade && \
apk add --no-cache \
postgresql${POSTGRES_VERSION}-client \
nodejs \
npm \
make \
g++ \
python3 \
openssh-client \
git \
git-lfs \
@@ -157,6 +162,14 @@ COPY --chown=www-data:www-data changelogs/ ./changelogs/
RUN composer dump-autoload
# Install terminal WebSocket server dependencies
COPY docker/coolify-terminal/package*.json /terminal/
RUN npm ci --prefix /terminal && \
npm rebuild node-pty --prefix /terminal --update-binary
COPY docker/coolify-terminal/terminal-server.js /terminal/terminal-server.js
COPY docker/coolify-terminal/terminal-utils.js /terminal/terminal-utils.js
RUN chown -R www-data:www-data /terminal
# Configure Nginx and S6 overlay
COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf
COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
cd /var/www/html
if grep -qE '^PUSHER_ENABLED=false' .env 2>/dev/null; then
echo " INFO Reverb is disabled, sleeping."
exec sleep infinity
fi
echo " INFO Reverb is enabled, starting..."
exec php artisan reverb:start --host=0.0.0.0 --port=${PUSHER_BACKEND_PORT:-6001}
@@ -0,0 +1 @@
longrun
@@ -0,0 +1,6 @@
#!/bin/sh
cd /var/www/html
echo " INFO Terminal WebSocket server is starting..."
exec node /terminal/terminal-server.js
@@ -0,0 +1 @@
longrun
+1 -1
View File
@@ -2,7 +2,7 @@
"scripts": {
"setup": "cp $JEAN_ROOT_PATH/.env . && mkdir -p .claude && cp $JEAN_ROOT_PATH/.claude/settings.local.json .claude/settings.local.json",
"teardown": null,
"run": "docker rm -f coolify coolify-minio-init coolify-realtime coolify-minio coolify-testing-host coolify-redis coolify-db coolify-mail coolify-vite; spin up; spin down"
"run": "docker rm -f coolify coolify-minio-init coolify-minio coolify-testing-host coolify-redis coolify-db coolify-mail coolify-vite; spin up; spin down"
},
"ports": [
{
+2
View File
@@ -10,6 +10,8 @@ REDIS_PASSWORD=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_PORT=6001
PUSHER_BACKEND_PORT=6001
ROOT_USERNAME=
ROOT_USER_EMAIL=
+6 -22
View File
@@ -18,12 +18,18 @@ services:
- PHP_FPM_PM_START_SERVERS=${PHP_FPM_PM_START_SERVERS:-1}
- PHP_FPM_PM_MIN_SPARE_SERVERS=${PHP_FPM_PM_MIN_SPARE_SERVERS:-1}
- PHP_FPM_PM_MAX_SPARE_SERVERS=${PHP_FPM_PM_MAX_SPARE_SERVERS:-10}
- PUSHER_PORT=${PUSHER_PORT:-6001}
- PUSHER_BACKEND_PORT=${PUSHER_BACKEND_PORT:-6001}
env_file:
- /data/coolify/source/.env
ports:
- "${APP_PORT:-8000}:8080"
- "${PUSHER_PORT:-6001}:6001"
- "${TERMINAL_PORT:-6002}:6002"
expose:
- "${APP_PORT:-8000}"
- "${PUSHER_PORT:-6001}"
- "${TERMINAL_PORT:-6002}"
healthcheck:
test: curl --fail http://127.0.0.1:8080/api/health || exit 1
interval: 5s
@@ -34,8 +40,6 @@ services:
condition: service_healthy
redis:
condition: service_healthy
soketi:
condition: service_healthy
postgres:
volumes:
- coolify-db:/var/lib/postgresql/data
@@ -59,26 +63,6 @@ services:
interval: 5s
retries: 10
timeout: 2s
soketi:
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.16'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
volumes:
- /data/coolify/ssh:/var/www/html/storage/app/ssh
environment:
APP_NAME: "${APP_NAME:-Coolify}"
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
SOKETI_HOST: "${SOKETI_HOST:-0.0.0.0}"
healthcheck:
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
interval: 5s
retries: 10
timeout: 2s
volumes:
coolify-db:
name: coolify-db
+7 -24
View File
@@ -42,14 +42,20 @@ services:
- PUSHER_APP_ID
- PUSHER_APP_KEY
- PUSHER_APP_SECRET
- PUSHER_PORT
- PUSHER_BACKEND_PORT
- AUTOUPDATE=true
- SELF_HOSTED=true
- SSH_MUX_ENABLED=false
- IS_WINDOWS_DOCKER_DESKTOP=true
ports:
- "${APP_PORT:-8000}:8080"
- "${PUSHER_PORT:-6001}:6001"
- "${TERMINAL_PORT:-6002}:6002"
expose:
- "${APP_PORT:-8000}"
- "${PUSHER_PORT:-6001}"
- "${TERMINAL_PORT:-6002}"
healthcheck:
test: curl --fail http://localhost:8080/api/health || exit 1
interval: 5s
@@ -95,30 +101,7 @@ services:
interval: 5s
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.16'
pull_policy: always
container_name: coolify-realtime
restart: always
env_file:
- .env
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
volumes:
- ./ssh:/var/www/html/storage/app/ssh
environment:
APP_NAME: "${APP_NAME:-Coolify}"
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
SOKETI_HOST: "${SOKETI_HOST:-0.0.0.0}"
healthcheck:
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
interval: 5s
retries: 10
timeout: 2s
volumes:
coolify-db:
+3 -8
View File
@@ -5,12 +5,14 @@ services:
working_dir: /var/www/html
extra_hosts:
- host.docker.internal:host-gateway
expose:
- "6001"
- "6002"
networks:
- coolify
depends_on:
- postgres
- redis
- soketi
postgres:
image: postgres:15-alpine
container_name: coolify-db
@@ -23,13 +25,6 @@ services:
restart: always
networks:
- coolify
soketi:
container_name: coolify-realtime
extra_hosts:
- host.docker.internal:host-gateway
restart: always
networks:
- coolify
networks:
coolify:
name: coolify
+10
View File
@@ -838,6 +838,13 @@ update_env_var() {
fi
}
normalize_pusher_port() {
if grep -q "^PUSHER_PORT=8080$" "$ENV_FILE"; then
sed -i "s|^PUSHER_PORT=8080$|PUSHER_PORT=6001|" "$ENV_FILE"
echo " - Updated PUSHER_PORT from app HTTP port 8080 to Reverb port 6001"
fi
}
update_env_var "APP_ID" "$(openssl rand -hex 16)"
update_env_var "APP_KEY" "base64:$(openssl rand -base64 32)"
# update_env_var "DB_USERNAME" "$(openssl rand -hex 16)" # Causes issues: database "random-user" does not exist
@@ -846,6 +853,9 @@ update_env_var "REDIS_PASSWORD" "$(openssl rand -base64 32)"
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
update_env_var "PUSHER_PORT" "6001"
normalize_pusher_port
update_env_var "PUSHER_BACKEND_PORT" "6001"
# Add default root user credentials from environment variables
if [ -n "$ROOT_USERNAME" ] && [ -n "$ROOT_USER_EMAIL" ] && [ -n "$ROOT_USER_PASSWORD" ]; then
+11 -1
View File
@@ -127,10 +127,20 @@ update_env_var() {
fi
}
normalize_pusher_port() {
if grep -q "^PUSHER_PORT=8080$" "$ENV_FILE"; then
sed -i "s|^PUSHER_PORT=8080$|PUSHER_PORT=6001|" "$ENV_FILE"
log "Updated PUSHER_PORT from app HTTP port 8080 to Reverb port 6001"
fi
}
log "Checking environment variables..."
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
update_env_var "PUSHER_PORT" "6001"
normalize_pusher_port
update_env_var "PUSHER_BACKEND_PORT" "6001"
log "Environment variables check complete"
echo " Done."
@@ -225,7 +235,7 @@ nohup bash -c "
}
# Stop and remove containers
for container in coolify coolify-db coolify-redis coolify-realtime; do
for container in coolify coolify-db coolify-redis; do
if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then
log \"Stopping container: \${container}\"
docker stop \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
-3
View File
@@ -9,9 +9,6 @@
"helper": {
"version": "1.0.14"
},
"realtime": {
"version": "1.0.16"
},
"sentinel": {
"version": "0.0.21"
}
+1 -2
View File
@@ -174,8 +174,7 @@
window.Pusher = Pusher;
const EchoConstructor = typeof Echo === 'function' ? Echo : Echo.default;
window.Echo = new EchoConstructor({
broadcaster: 'pusher',
cluster: "{{ config('constants.pusher.host') }}" || window.location.hostname,
broadcaster: 'reverb',
key: "{{ config('constants.pusher.app_key') }}" || 'coolify',
wsHost: "{{ config('constants.pusher.host') }}" || window.location.hostname,
wsPort: "{{ getRealtime() }}",
+10
View File
@@ -838,6 +838,13 @@ update_env_var() {
fi
}
normalize_pusher_port() {
if grep -q "^PUSHER_PORT=8080$" "$ENV_FILE"; then
sed -i "s|^PUSHER_PORT=8080$|PUSHER_PORT=6001|" "$ENV_FILE"
echo " - Updated PUSHER_PORT from app HTTP port 8080 to Reverb port 6001"
fi
}
update_env_var "APP_ID" "$(openssl rand -hex 16)"
update_env_var "APP_KEY" "base64:$(openssl rand -base64 32)"
# update_env_var "DB_USERNAME" "$(openssl rand -hex 16)" # Causes issues: database "random-user" does not exist
@@ -846,6 +853,9 @@ update_env_var "REDIS_PASSWORD" "$(openssl rand -base64 32)"
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
update_env_var "PUSHER_PORT" "6001"
normalize_pusher_port
update_env_var "PUSHER_BACKEND_PORT" "6001"
# Add default root user credentials from environment variables
if [ -n "$ROOT_USERNAME" ] && [ -n "$ROOT_USER_EMAIL" ] && [ -n "$ROOT_USER_PASSWORD" ]; then
+11 -1
View File
@@ -127,10 +127,20 @@ update_env_var() {
fi
}
normalize_pusher_port() {
if grep -q "^PUSHER_PORT=8080$" "$ENV_FILE"; then
sed -i "s|^PUSHER_PORT=8080$|PUSHER_PORT=6001|" "$ENV_FILE"
log "Updated PUSHER_PORT from app HTTP port 8080 to Reverb port 6001"
fi
}
log "Checking environment variables..."
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
update_env_var "PUSHER_PORT" "6001"
normalize_pusher_port
update_env_var "PUSHER_BACKEND_PORT" "6001"
log "Environment variables check complete"
echo " Done."
@@ -234,7 +244,7 @@ nohup bash -c "
}
# Stop and remove containers
for container in coolify coolify-db coolify-redis coolify-realtime; do
for container in coolify coolify-db coolify-redis; do
if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then
log \"Stopping container: \${container}\"
docker stop \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
@@ -1,15 +1,18 @@
<?php
it('copies the realtime terminal utilities into the container image', function () {
$dockerfile = file_get_contents(base_path('docker/coolify-realtime/Dockerfile'));
it('copies the terminal utilities into the Coolify container images', function (string $dockerfile) {
$dockerfile = file_get_contents(base_path($dockerfile));
expect($dockerfile)->toContain('COPY docker/coolify-realtime/terminal-utils.js /terminal/terminal-utils.js');
});
expect($dockerfile)->toContain('COPY docker/coolify-terminal/terminal-utils.js /terminal/terminal-utils.js');
})->with([
'production image' => 'docker/production/Dockerfile',
'development image' => 'docker/development/Dockerfile',
]);
it('mounts the realtime terminal utilities in local development compose files', function (string $composeFile) {
$composeContents = file_get_contents(base_path($composeFile));
expect($composeContents)->toContain('./docker/coolify-realtime/terminal-utils.js:/terminal/terminal-utils.js');
expect($composeContents)->toContain('./docker/coolify-terminal/terminal-utils.js:/terminal/terminal-utils.js');
})->with([
'default dev compose' => 'docker-compose.dev.yml',
'maxio dev compose' => 'docker-compose-maxio.dev.yml',
@@ -25,7 +28,7 @@ it('keeps terminal browser logging restricted to Vite development mode', functio
});
it('keeps realtime terminal server logging behind the explicit debug flag', function () {
$terminalServer = file_get_contents(base_path('docker/coolify-realtime/terminal-server.js'));
$terminalServer = file_get_contents(base_path('docker/coolify-terminal/terminal-server.js'));
expect($terminalServer)
->toContain('const debugOverride = String(process.env.TERMINAL_DEBUG')
@@ -35,7 +38,7 @@ it('keeps realtime terminal server logging behind the explicit debug flag', func
});
it('configures a server-initiated WebSocket heartbeat to survive proxy idle timeouts', function () {
$terminalServer = file_get_contents(base_path('docker/coolify-realtime/terminal-server.js'));
$terminalServer = file_get_contents(base_path('docker/coolify-terminal/terminal-server.js'));
expect($terminalServer)
->toContain('ws.isAlive = true;')
@@ -60,7 +63,7 @@ it('uses a fast probe timeout when the tab regains visibility', function () {
});
it('does not hard close terminal sessions after 30 minutes on the server', function () {
$terminalServer = file_get_contents(base_path('docker/coolify-realtime/terminal-server.js'));
$terminalServer = file_get_contents(base_path('docker/coolify-terminal/terminal-server.js'));
expect($terminalServer)
->not->toContain('IDLE_TIMEOUT_MS = 30 * 60 * 1000')
@@ -110,7 +113,7 @@ it('replays the last command on reconnect so the PTY respawns automatically', fu
});
it('buffers messages received before the realtime server finishes auth so the replay is not lost', function () {
$terminalServer = file_get_contents(base_path('docker/coolify-realtime/terminal-server.js'));
$terminalServer = file_get_contents(base_path('docker/coolify-terminal/terminal-server.js'));
expect($terminalServer)
->toContain('authReady: false')
@@ -0,0 +1,141 @@
<?php
it('uses Reverb as the first-party broadcast server', function () {
expect(file_get_contents(base_path('composer.json')))
->toContain('"laravel/reverb"')
->and(file_get_contents(config_path('broadcasting.php')))
->toContain("'default' => env('BROADCAST_CONNECTION', env('BROADCAST_DRIVER', 'reverb'))")
->toContain("'reverb' => [")
->toContain("'key' => env('PUSHER_APP_KEY', 'coolify')")
->toContain("'secret' => env('PUSHER_APP_SECRET', 'coolify')")
->toContain("'app_id' => env('PUSHER_APP_ID', 'coolify')")
->toContain("'host' => env('PUSHER_HOST', 'coolify')")
->toContain("'port' => env('PUSHER_BACKEND_PORT', 6001)")
->and(file_exists(config_path('reverb.php')))->toBeTrue();
});
it('runs Reverb and terminal websocket services inside the Coolify containers', function (string $dockerfile, string $dependencyService) {
$dockerfileContents = file_get_contents(base_path($dockerfile));
expect($dockerfileContents)
->toContain('nodejs')
->toContain('npm')
->toContain('COPY docker/coolify-terminal/package*.json /terminal/')
->toContain('COPY docker/coolify-terminal/terminal-server.js /terminal/terminal-server.js')
->toContain('COPY docker/coolify-terminal/terminal-utils.js /terminal/terminal-utils.js')
->toContain('npm ci --prefix /terminal')
->and(file_get_contents(base_path(dirname($dockerfile).'/etc/s6-overlay/s6-rc.d/reverb/run')))
->toContain('exec php artisan reverb:start --host=0.0.0.0 --port=${PUSHER_BACKEND_PORT:-6001}')
->and(file_get_contents(base_path(dirname($dockerfile).'/etc/s6-overlay/s6-rc.d/terminal-server/run')))
->toContain('exec node /terminal/terminal-server.js')
->and(file_exists(base_path(dirname($dockerfile)."/etc/s6-overlay/s6-rc.d/reverb/dependencies.d/{$dependencyService}")))->toBeTrue()
->and(file_exists(base_path(dirname($dockerfile)."/etc/s6-overlay/s6-rc.d/terminal-server/dependencies.d/{$dependencyService}")))->toBeTrue()
->and(file_exists(base_path(dirname($dockerfile).'/etc/s6-overlay/s6-rc.d/user/contents.d/reverb')))->toBeTrue()
->and(file_exists(base_path(dirname($dockerfile).'/etc/s6-overlay/s6-rc.d/user/contents.d/terminal-server')))->toBeTrue();
})->with([
'production image' => ['docker/production/Dockerfile', 'init-script'],
'development image' => ['docker/development/Dockerfile', 'init-setup'],
]);
it('removes the dedicated realtime service from bundled compose files', function (string $composeFile, bool $hasRuntimeEnvironment) {
$composeContents = file_get_contents(base_path($composeFile));
expect($composeContents)
->not->toContain('coolify-realtime')
->not->toContain('soketi:')
->not->toContain('SOKETI_DEFAULT_APP_ID')
->toContain('6001')
->toContain('6002')
->not->toContain('REVERB_');
if ($hasRuntimeEnvironment) {
expect($composeContents)->toContain('PUSHER_BACKEND_PORT');
}
})->with([
'base compose' => ['docker-compose.yml', false],
'production compose' => ['docker-compose.prod.yml', true],
'development compose' => ['docker-compose.dev.yml', true],
'maxio development compose' => ['docker-compose-maxio.dev.yml', true],
'windows compose' => ['docker-compose.windows.yml', true],
]);
it('keeps the internal Reverb listen port separate from the public Pusher port', function () {
expect(file_get_contents(config_path('reverb.php')))
->toContain("'port' => env('PUSHER_BACKEND_PORT', 6001)")
->toContain("'port' => env('PUSHER_PORT', 6001)")
->and(file_get_contents(base_path('docker/production/etc/s6-overlay/s6-rc.d/reverb/run')))
->toContain('exec php artisan reverb:start --host=0.0.0.0 --port=${PUSHER_BACKEND_PORT:-6001}')
->and(file_get_contents(base_path('docker/development/etc/s6-overlay/s6-rc.d/reverb/run')))
->toContain('exec php artisan reverb:start --host=0.0.0.0 --port=${PUSHER_BACKEND_PORT:-6001}');
});
it('proxies Reverb and terminal websocket traffic to the Coolify app container', function () {
$serverModel = file_get_contents(app_path('Models/Server.php'));
expect($serverModel)
->toContain("'rule' => \"Host(`{\$host}`) && PathPrefix(`/app`)\"")
->toContain("'rule' => \"Host(`{\$host}`) && PathPrefix(`/apps`)\"")
->toContain("'rule' => \"Host(`{\$host}`) && PathPrefix(`/terminal/ws`)\"")
->toContain("'url' => 'http://coolify:6001'")
->toContain("'url' => 'http://coolify:6002'")
->toContain('reverse_proxy coolify:6001')
->toContain('reverse_proxy coolify:6002')
->not->toContain('http://coolify-realtime:6001')
->not->toContain('http://coolify-realtime:6002')
->not->toContain('reverse_proxy coolify-realtime');
});
it('uses Pusher environment keys for self-hosted Reverb compatibility', function () {
$files = [
'.env.production',
'.env.windows-docker-desktop.example',
'docker-compose.prod.yml',
'docker-compose.dev.yml',
'docker-compose-maxio.dev.yml',
'docker-compose.windows.yml',
'scripts/install.sh',
'scripts/upgrade.sh',
'other/nightly/.env.production',
'other/nightly/docker-compose.prod.yml',
'other/nightly/docker-compose.windows.yml',
'other/nightly/install.sh',
'other/nightly/upgrade.sh',
];
foreach ($files as $file) {
expect(file_get_contents(base_path($file)))
->toContain('PUSHER_')
->not->toContain('REVERB_');
}
});
it('defaults the public Pusher websocket port to Reverb instead of the HTTP app port', function () {
expect(file_get_contents(base_path('.env.production')))
->toContain('PUSHER_PORT=6001')
->toContain('PUSHER_BACKEND_PORT=6001')
->and(file_get_contents(base_path('.env.windows-docker-desktop.example')))
->toContain('PUSHER_PORT=6001')
->toContain('PUSHER_BACKEND_PORT=6001')
->and(file_get_contents(base_path('scripts/install.sh')))
->toContain('update_env_var "PUSHER_PORT" "6001"')
->toContain('update_env_var "PUSHER_BACKEND_PORT" "6001"')
->toContain('normalize_pusher_port')
->and(file_get_contents(base_path('scripts/upgrade.sh')))
->toContain('update_env_var "PUSHER_PORT" "6001"')
->toContain('update_env_var "PUSHER_BACKEND_PORT" "6001"')
->toContain('normalize_pusher_port');
});
it('stops publishing or preserving the obsolete realtime image', function () {
expect(file_get_contents(config_path('constants.php')))
->not->toContain('realtime_version')
->not->toContain('realtime_image')
->and(file_get_contents(app_path('Actions/Server/CleanupDocker.php')))
->not->toContain('coolify-realtime')
->not->toContain('realtimeImage')
->and(file_get_contents(base_path('versions.json')))
->not->toContain('"realtime"')
->and(is_dir(base_path('docker/coolify-realtime')))->toBeFalse()
->and(file_exists(base_path('.github/workflows/coolify-realtime.yml')))->toBeFalse()
->and(file_exists(base_path('.github/workflows/coolify-realtime-next.yml')))->toBeFalse();
});
@@ -255,54 +255,34 @@ it('correctly excludes Docker Compose images from general prune', function () {
}
});
it('excludes current version of Coolify infrastructure images from any registry', function () {
// Test the regex pattern used to protect the current version of infrastructure images
// regardless of which registry they come from (ghcr.io, docker.io, or no prefix)
it('excludes current version of the Coolify helper image from any registry', function () {
$helperVersion = '1.0.12';
$realtimeVersion = '1.0.10';
// Build the exclusion pattern the same way CleanupDocker does
// Pattern: (^|/)coollabsio/coolify-helper:VERSION$|(^|/)coollabsio/coolify-realtime:VERSION$
$escapedHelperVersion = preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $helperVersion);
$escapedRealtimeVersion = preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $realtimeVersion);
$escapedHelperVersion = preg_quote($helperVersion, '/');
$infraPattern = "(^|/)coollabsio/coolify-helper:{$escapedHelperVersion}$";
$pattern = "#{$infraPattern}#";
// For PHP preg_match, escape forward slashes
$infraPattern = "(^|\\/)coollabsio\\/coolify-helper:{$escapedHelperVersion}$|(^|\\/)coollabsio\\/coolify-realtime:{$escapedRealtimeVersion}$";
$pattern = "/{$infraPattern}/";
// Current versioned infrastructure images from ANY registry should be PROTECTED
$protectedImages = [
// ghcr.io registry
"ghcr.io/coollabsio/coolify-helper:{$helperVersion}",
"ghcr.io/coollabsio/coolify-realtime:{$realtimeVersion}",
// docker.io registry (explicit)
"docker.io/coollabsio/coolify-helper:{$helperVersion}",
"docker.io/coollabsio/coolify-realtime:{$realtimeVersion}",
// No registry prefix (Docker Hub implicit)
"coollabsio/coolify-helper:{$helperVersion}",
"coollabsio/coolify-realtime:{$realtimeVersion}",
];
// Verify current infrastructure images ARE protected from any registry
foreach ($protectedImages as $image) {
expect(preg_match($pattern, $image))->toBe(1, "Current infrastructure image {$image} should be protected");
}
// Verify OLD versions of infrastructure images are NOT protected (can be deleted)
$oldVersionImages = [
'ghcr.io/coollabsio/coolify-helper:1.0.11',
'docker.io/coollabsio/coolify-helper:1.0.10',
'coollabsio/coolify-helper:1.0.9',
'ghcr.io/coollabsio/coolify-realtime:1.0.9',
'ghcr.io/coollabsio/coolify-helper:latest',
'coollabsio/coolify-realtime:latest',
];
foreach ($oldVersionImages as $image) {
expect(preg_match($pattern, $image))->toBe(0, "Old infrastructure image {$image} should NOT be protected");
}
// Verify other images are NOT protected (can be deleted)
$deletableImages = [
'nginx:alpine',
'postgres:15',
@@ -316,31 +296,19 @@ it('excludes current version of Coolify infrastructure images from any registry'
}
});
it('protects current infrastructure images from any registry even when no applications exist', function () {
// When there are no applications, current versioned infrastructure images should still be protected
// regardless of which registry they come from
it('protects current helper image from any registry even when no applications exist', function () {
$helperVersion = '1.0.12';
$realtimeVersion = '1.0.10';
// Build the pattern the same way CleanupDocker does
$escapedHelperVersion = preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $helperVersion);
$escapedRealtimeVersion = preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $realtimeVersion);
$escapedHelperVersion = preg_quote($helperVersion, '/');
$infraPattern = "(^|/)coollabsio/coolify-helper:{$escapedHelperVersion}$";
$pattern = "#{$infraPattern}#";
// For PHP preg_match, escape forward slashes
$infraPattern = "(^|\\/)coollabsio\\/coolify-helper:{$escapedHelperVersion}$|(^|\\/)coollabsio\\/coolify-realtime:{$escapedRealtimeVersion}$";
$pattern = "/{$infraPattern}/";
// Verify current infrastructure images from any registry are protected
expect(preg_match($pattern, "ghcr.io/coollabsio/coolify-helper:{$helperVersion}"))->toBe(1);
expect(preg_match($pattern, "docker.io/coollabsio/coolify-helper:{$helperVersion}"))->toBe(1);
expect(preg_match($pattern, "coollabsio/coolify-helper:{$helperVersion}"))->toBe(1);
expect(preg_match($pattern, "ghcr.io/coollabsio/coolify-realtime:{$realtimeVersion}"))->toBe(1);
// Old versions should NOT be protected
expect(preg_match($pattern, 'ghcr.io/coollabsio/coolify-helper:1.0.11'))->toBe(0);
expect(preg_match($pattern, 'docker.io/coollabsio/coolify-helper:1.0.11'))->toBe(0);
// Other images should not be protected
expect(preg_match($pattern, 'nginx:alpine'))->toBe(0);
});
-3
View File
@@ -9,9 +9,6 @@
"helper": {
"version": "1.0.14"
},
"realtime": {
"version": "1.0.16"
},
"sentinel": {
"version": "0.0.21"
}