From e800bc97bc99dfd6b78341e186be54f99f4aa971 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 15 Dec 2025 19:20:03 +0100 Subject: [PATCH 01/12] chore(config): remove unused queue connections --- config/queue.php | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/config/queue.php b/config/queue.php index 3c651ec5b..a188ff536 100644 --- a/config/queue.php +++ b/config/queue.php @@ -35,35 +35,6 @@ return [ 'driver' => 'sync', ], - 'database' => [ - 'driver' => 'database', - 'connection' => env('DB_QUEUE_CONNECTION'), - 'table' => env('DB_QUEUE_TABLE', 'jobs'), - 'queue' => env('DB_QUEUE', 'default'), - 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), - 'after_commit' => false, - ], - - 'beanstalkd' => [ - 'driver' => 'beanstalkd', - 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), - 'queue' => env('BEANSTALKD_QUEUE', 'default'), - 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), - 'block_for' => 0, - 'after_commit' => false, - ], - - 'sqs' => [ - 'driver' => 'sqs', - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), - 'queue' => env('SQS_QUEUE', 'default'), - 'suffix' => env('SQS_SUFFIX'), - 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), - 'after_commit' => false, - ], - 'redis' => [ 'driver' => 'redis', 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), @@ -72,22 +43,6 @@ return [ 'block_for' => null, 'after_commit' => false, ], - - 'deferred' => [ - 'driver' => 'deferred', - ], - - 'background' => [ - 'driver' => 'background', - ], - - 'failover' => [ - 'driver' => 'failover', - 'connections' => [ - 'database', - 'deferred', - ], - ], ], /* From d2cae69d991234eef4df47f8f55a79153cf983de Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:41:23 +0100 Subject: [PATCH 02/12] chore(config): configure laravel queues - use redis as the default queue connection - use the redis "jobs" connection for all queued jobs - set after_commit=true to ensure jobs wait for all DB transactions to finish before being dispatched - use pgsql database for job batches - remove logging of failed jobs into a DB table as we use horizon for that --- config/queue.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/config/queue.php b/config/queue.php index a188ff536..b390f2483 100644 --- a/config/queue.php +++ b/config/queue.php @@ -14,7 +14,7 @@ return [ | */ - 'default' => env('QUEUE_CONNECTION', 'database'), + 'default' => env('QUEUE_CONNECTION', 'redis'), /* |-------------------------------------------------------------------------- @@ -37,11 +37,11 @@ return [ 'redis' => [ 'driver' => 'redis', - 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'connection' => env('REDIS_QUEUE_CONNECTION', 'jobs'), 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 'block_for' => null, - 'after_commit' => false, + 'after_commit' => true, ], ], @@ -57,7 +57,7 @@ return [ */ 'batching' => [ - 'database' => env('DB_CONNECTION', 'sqlite'), + 'database' => env('DB_CONNECTION', 'pgsql'), 'table' => 'job_batches', ], @@ -75,8 +75,6 @@ return [ */ 'failed' => [ - 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), - 'database' => env('DB_CONNECTION', 'sqlite'), - 'table' => 'failed_jobs', + 'driver' => env('QUEUE_FAILED_DRIVER', 'null'), ], ]; From e67624afa827f91629a8e22a289ec66cfc8152ee Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 10 Dec 2025 21:29:01 +0100 Subject: [PATCH 03/12] feat(jobs): create job_batches table --- ..._12_10_154900_create_job_batches_table.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 database/migrations/2025_12_10_154900_create_job_batches_table.php diff --git a/database/migrations/2025_12_10_154900_create_job_batches_table.php b/database/migrations/2025_12_10_154900_create_job_batches_table.php new file mode 100644 index 000000000..5f2ec85a6 --- /dev/null +++ b/database/migrations/2025_12_10_154900_create_job_batches_table.php @@ -0,0 +1,29 @@ +string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + } +}; From 124957fe94c5ddae0e79d018a26418bc15bc9399 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:37:11 +0100 Subject: [PATCH 04/12] chore(deps): install laravel horizon and all horizon assets --- app/Providers/HorizonServiceProvider.php | 29 +++ bootstrap/providers.php | 1 + composer.json | 1 + composer.lock | 81 +++++++- config/horizon.php | 231 +++++++++++++++++++++++ 5 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 app/Providers/HorizonServiceProvider.php create mode 100644 config/horizon.php diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php new file mode 100644 index 000000000..042c4fef1 --- /dev/null +++ b/app/Providers/HorizonServiceProvider.php @@ -0,0 +1,29 @@ + false); + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 84c7d4de9..e1b8d938f 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -4,4 +4,5 @@ declare(strict_types=1); return [ App\Providers\AppServiceProvider::class, + App\Providers\HorizonServiceProvider::class, ]; diff --git a/composer.json b/composer.json index d80599cb8..d3d42c74b 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "require": { "php": "^8.5", "laravel/framework": "^12.40.2", + "laravel/horizon": "^5.40", "laravel/tinker": "^2.10.2" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 511bd861b..56cd5a004 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "6045e019dfef5b1628b4762e35986357", + "content-hash": "273a008ce9e4578d53a4922fb59ff548", "packages": [ { "name": "brick/math", @@ -1271,6 +1271,85 @@ }, "time": "2025-11-26T19:24:25+00:00" }, + { + "name": "laravel/horizon", + "version": "v5.40.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/horizon.git", + "reference": "005e5638478db9e25f7ae5cfb30c56846fbad793" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/horizon/zipball/005e5638478db9e25f7ae5cfb30c56846fbad793", + "reference": "005e5638478db9e25f7ae5cfb30c56846fbad793", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcntl": "*", + "ext-posix": "*", + "illuminate/contracts": "^9.21|^10.0|^11.0|^12.0", + "illuminate/queue": "^9.21|^10.0|^11.0|^12.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.17|^3.0", + "php": "^8.0", + "ramsey/uuid": "^4.0", + "symfony/console": "^6.0|^7.0", + "symfony/error-handler": "^6.0|^7.0", + "symfony/polyfill-php83": "^1.28", + "symfony/process": "^6.0|^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^7.55|^8.36|^9.15|^10.8", + "phpstan/phpstan": "^1.10|^2.0", + "predis/predis": "^1.1|^2.0|^3.0" + }, + "suggest": { + "ext-redis": "Required to use the Redis PHP driver.", + "predis/predis": "Required when not using the Redis PHP driver (^1.1|^2.0|^3.0)." + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Horizon": "Laravel\\Horizon\\Horizon" + }, + "providers": [ + "Laravel\\Horizon\\HorizonServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Horizon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Dashboard and code-driven configuration for Laravel queues.", + "keywords": [ + "laravel", + "queue" + ], + "support": { + "issues": "https://github.com/laravel/horizon/issues", + "source": "https://github.com/laravel/horizon/tree/v5.40.2" + }, + "time": "2025-11-28T20:12:12+00:00" + }, { "name": "laravel/prompts", "version": "v0.3.8", diff --git a/config/horizon.php b/config/horizon.php new file mode 100644 index 000000000..641ee274f --- /dev/null +++ b/config/horizon.php @@ -0,0 +1,231 @@ + env('HORIZON_NAME'), + + /* + |-------------------------------------------------------------------------- + | Horizon Domain + |-------------------------------------------------------------------------- + | + | This is the subdomain where Horizon will be accessible from. If this + | setting is null, Horizon will reside under the same domain as the + | application. Otherwise, this value will serve as the subdomain. + | + */ + + 'domain' => env('HORIZON_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Horizon Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Horizon will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('HORIZON_PATH', 'horizon'), + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Connection + |-------------------------------------------------------------------------- + | + | This is the name of the Redis connection where Horizon will store the + | meta information required for it to function. It includes the list + | of supervisors, failed jobs, job metrics, and other information. + | + */ + + 'use' => 'default', + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Prefix + |-------------------------------------------------------------------------- + | + | This prefix will be used when storing all Horizon data in Redis. You + | may modify the prefix when you are running multiple installations + | of Horizon on the same server so that they don't have problems. + | + */ + + 'prefix' => env( + 'HORIZON_PREFIX', + Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:', + ), + + /* + |-------------------------------------------------------------------------- + | Horizon Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will get attached onto each Horizon route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Queue Wait Time Thresholds + |-------------------------------------------------------------------------- + | + | This option allows you to configure when the LongWaitDetected event + | will be fired. Every connection / queue combination may have its + | own, unique threshold (in seconds) before this event is fired. + | + */ + + 'waits' => [ + 'redis:default' => 60, + ], + + /* + |-------------------------------------------------------------------------- + | Job Trimming Times + |-------------------------------------------------------------------------- + | + | Here you can configure for how long (in minutes) you desire Horizon to + | persist the recent and failed jobs. Typically, recent jobs are kept + | for one hour while all failed jobs are stored for an entire week. + | + */ + + 'trim' => [ + 'recent' => 60, + 'pending' => 60, + 'completed' => 60, + 'recent_failed' => 10080, + 'failed' => 10080, + 'monitored' => 10080, + ], + + /* + |-------------------------------------------------------------------------- + | Silenced Jobs + |-------------------------------------------------------------------------- + | + | Silencing a job will instruct Horizon to not place the job in the list + | of completed jobs within the Horizon dashboard. This setting may be + | used to fully remove any noisy jobs from the completed jobs list. + | + */ + + 'silenced' => [ + // App\Jobs\ExampleJob::class, + ], + + 'silenced_tags' => [ + // 'notifications', + ], + + /* + |-------------------------------------------------------------------------- + | Metrics + |-------------------------------------------------------------------------- + | + | Here you can configure how many snapshots should be kept to display in + | the metrics graph. This will get used in combination with Horizon's + | `horizon:snapshot` schedule to define how long to retain metrics. + | + */ + + 'metrics' => [ + 'trim_snapshots' => [ + 'job' => 24, + 'queue' => 24, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Fast Termination + |-------------------------------------------------------------------------- + | + | When this option is enabled, Horizon's "terminate" command will not + | wait on all of the workers to terminate unless the --wait option + | is provided. Fast termination can shorten deployment delay by + | allowing a new instance of Horizon to start while the last + | instance will continue to terminate each of its workers. + | + */ + + 'fast_termination' => false, + + /* + |-------------------------------------------------------------------------- + | Memory Limit (MB) + |-------------------------------------------------------------------------- + | + | This value describes the maximum amount of memory the Horizon master + | supervisor may consume before it is terminated and restarted. For + | configuring these limits on your workers, see the next section. + | + */ + + 'memory_limit' => 64, + + /* + |-------------------------------------------------------------------------- + | Queue Worker Configuration + |-------------------------------------------------------------------------- + | + | Here you may define the queue worker settings used by your application + | in all environments. These supervisors and settings handle all your + | queued jobs and will be provisioned by Horizon during deployment. + | + */ + + 'defaults' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default'], + 'balance' => 'auto', + 'autoScalingStrategy' => 'time', + 'maxProcesses' => 1, + 'maxTime' => 0, + 'maxJobs' => 0, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 60, + 'nice' => 0, + ], + ], + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, + ], + ], + ], +]; From 907be7d562af61d1a4742dde9903c887bb7e4c5c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:32:03 +0100 Subject: [PATCH 05/12] feat(jobs): add ProcessingQueue enum --- app/Enums/.gitkeep | 0 app/Enums/ProcessingQueue.php | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+) delete mode 100644 app/Enums/.gitkeep create mode 100644 app/Enums/ProcessingQueue.php diff --git a/app/Enums/.gitkeep b/app/Enums/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/Enums/ProcessingQueue.php b/app/Enums/ProcessingQueue.php new file mode 100644 index 000000000..1c9a66174 --- /dev/null +++ b/app/Enums/ProcessingQueue.php @@ -0,0 +1,18 @@ + Date: Wed, 10 Dec 2025 22:08:25 +0100 Subject: [PATCH 06/12] test: remove toImplementNothing() from enum ArchTest - PHP enums always implement one or more internal interfaces so this assertion will never pass --- tests/ArchTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ArchTest.php b/tests/ArchTest.php index 6b23cd499..e54ad749f 100644 --- a/tests/ArchTest.php +++ b/tests/ArchTest.php @@ -79,7 +79,6 @@ arch('Enums') ->toBeEnums() ->ignoring('App\Enums\Concerns') ->toExtendNothing() - ->toImplementNothing() ->toHaveLineCountLessThan(80); arch('Exceptions') From e5c54f94f42975de1712b1d8168353973660e6e2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:26:10 +0100 Subject: [PATCH 07/12] chore(tooling): ignore NewlineBetweenClassLikeStmtsRector rector rule - this rule adds newlines between enum cases which is incorrect and pint already formats all other cases covered by this rule via its class_attributes_separation rule --- rector.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rector.php b/rector.php index d345710b2..490c74465 100644 --- a/rector.php +++ b/rector.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Rector\Caching\ValueObject\Storage\FileCacheStorage; +use Rector\CodingStyle\Rector\ClassLike\NewlineBetweenClassLikeStmtsRector; use Rector\Config\RectorConfig; use Rector\Php80\Rector\NotIdentical\MbStrContainsRector; use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector; @@ -31,6 +32,7 @@ return RectorConfig::configure() ->withSkip([ __DIR__.'/bootstrap/cache', AddOverrideAttributeToOverriddenMethodsRector::class, + NewlineBetweenClassLikeStmtsRector::class, ]) ->withCache(__DIR__.'/storage/rector', FileCacheStorage::class) ->withPhpSets() From 2ce8184e68a83de43e7f5d32952339fcb97408bd Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:33:38 +0100 Subject: [PATCH 08/12] feat(config): configure horizon and add worker server base - normal jobs (backups, emails, etc.) and deployment jobs now use separate supervisor configurations and defaults - use different queues and supervisors depending on if the server is a worker server - horizon workers are now restarted after 500 (job workers) or 300 (deployment workers) jobs or after 1 hour to clean up stale memory and CPU usage - reduced default queue timeouts from 10h to 60s for jobs and 300s for deployments to prevent stale jobs - increased balanceCooldown from 1s to 2s for jobs to reduce CPU spikes - prepare LongWaitDetected waits for all queues - use the "jobs" redis connection for horizon - remove unused config option --- config/horizon.php | 136 +++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 41 deletions(-) diff --git a/config/horizon.php b/config/horizon.php index 641ee274f..0090d8ae1 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -18,19 +18,6 @@ return [ 'name' => env('HORIZON_NAME'), - /* - |-------------------------------------------------------------------------- - | Horizon Domain - |-------------------------------------------------------------------------- - | - | This is the subdomain where Horizon will be accessible from. If this - | setting is null, Horizon will reside under the same domain as the - | application. Otherwise, this value will serve as the subdomain. - | - */ - - 'domain' => env('HORIZON_DOMAIN'), - /* |-------------------------------------------------------------------------- | Horizon Path @@ -55,7 +42,7 @@ return [ | */ - 'use' => 'default', + 'use' => 'jobs', /* |-------------------------------------------------------------------------- @@ -70,7 +57,7 @@ return [ 'prefix' => env( 'HORIZON_PREFIX', - Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:', + Str::slug((string) env('APP_NAME', 'coolify'), '_').'_horizon:', ), /* @@ -98,7 +85,14 @@ return [ */ 'waits' => [ - 'redis:default' => 60, + 'redis:high' => 10, + 'redis:default' => 20, + 'redis:production-deployment' => 10, + 'redis:standard-deployment' => 20, + 'redis:worker-high' => 10, + 'redis:worker-default' => 20, + 'redis:worker-production-deployment' => 10, + 'redis:worker-standard-deployment' => 20, ], /* @@ -198,34 +192,94 @@ return [ */ 'defaults' => [ - 'supervisor-1' => [ - 'connection' => 'redis', - 'queue' => ['default'], - 'balance' => 'auto', - 'autoScalingStrategy' => 'time', - 'maxProcesses' => 1, - 'maxTime' => 0, - 'maxJobs' => 0, - 'memory' => 128, - 'tries' => 1, - 'timeout' => 60, - 'nice' => 0, - ], + ...((bool) env('IS_WORKER_SERVER', false) ? [] : [ + 'jobs' => [ + 'connection' => 'redis', + 'queue' => ['high', 'default'], + 'balance' => false, + 'autoScalingStrategy' => 'time', + 'maxTime' => 3600, + 'maxJobs' => 500, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 60, + 'sleep' => 3, + 'workers-name' => 'jobs', + ], + 'deployments' => [ + 'connection' => 'redis', + 'queue' => ['production-deployment', 'standard-deployment'], + 'balance' => false, + 'autoScalingStrategy' => 'time', + 'maxTime' => 3600, + 'maxJobs' => 300, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 300, + 'sleep' => 3, + 'workers-name' => 'deployments', + ], + ]), + ...((bool) env('IS_WORKER_SERVER', false) ? [ + 'worker-jobs' => [ + 'connection' => 'redis', + 'queue' => ['worker-high', 'worker-default'], + 'balance' => false, + 'autoScalingStrategy' => 'time', + 'maxTime' => 3600, + 'maxJobs' => 500, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 60, + 'sleep' => 3, + 'workers-name' => 'worker-jobs', + ], + 'worker-deployments' => [ + 'connection' => 'redis', + 'queue' => ['worker-production-deployment', 'worker-standard-deployment'], + 'balance' => false, + 'autoScalingStrategy' => 'time', + 'maxTime' => 3600, + 'maxJobs' => 300, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 300, + 'sleep' => 3, + 'workers-name' => 'worker-deployments', + ], + ] : []), ], 'environments' => [ - 'production' => [ - 'supervisor-1' => [ - 'maxProcesses' => 10, - 'balanceMaxShift' => 1, - 'balanceCooldown' => 3, - ], - ], - - 'local' => [ - 'supervisor-1' => [ - 'maxProcesses' => 3, - ], + '*' => [ + ...((bool) env('IS_WORKER_SERVER', false) ? [] : [ + 'jobs' => [ + 'minProcesses' => env('HORIZON_JOBS_MIN_PROCESSES', 1), + 'maxProcesses' => env('HORIZON_JOBS_MAX_PROCESSES', 4), + 'balanceMaxShift' => env('HORIZON_JOBS_BALANCE_MAX_SHIFT', 1), + 'balanceCooldown' => env('HORIZON_JOBS_BALANCE_COOLDOWN', 2), + ], + 'deployments' => [ + 'minProcesses' => env('HORIZON_DEPLOYMENTS_MIN_PROCESSES', 1), + 'maxProcesses' => env('HORIZON_DEPLOYMENTS_MAX_PROCESSES', 2), + 'balanceMaxShift' => env('HORIZON_DEPLOYMENTS_BALANCE_MAX_SHIFT', 1), + 'balanceCooldown' => env('HORIZON_DEPLOYMENTS_BALANCE_COOLDOWN', 2), + ], + ]), + ...((bool) env('IS_WORKER_SERVER', false) ? [ + 'worker-jobs' => [ + 'minProcesses' => env('HORIZON_JOBS_MIN_PROCESSES', 1), + 'maxProcesses' => env('HORIZON_JOBS_MAX_PROCESSES', 6), + 'balanceMaxShift' => env('HORIZON_JOBS_BALANCE_MAX_SHIFT', 2), + 'balanceCooldown' => env('HORIZON_JOBS_BALANCE_COOLDOWN', 1), + ], + 'worker-deployments' => [ + 'minProcesses' => env('HORIZON_DEPLOYMENTS_MIN_PROCESSES', 1), + 'maxProcesses' => env('HORIZON_DEPLOYMENTS_MAX_PROCESSES', 4), + 'balanceMaxShift' => env('HORIZON_DEPLOYMENTS_BALANCE_MAX_SHIFT', 2), + 'balanceCooldown' => env('HORIZON_DEPLOYMENTS_BALANCE_COOLDOWN', 1), + ], + ] : []), ], ], ]; From c52b927e9332c4a16d77d186eeb0a372fcfa56c1 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:19:00 +0100 Subject: [PATCH 09/12] chore(deps): bump laravel version to get withoutInterruptionPolling option --- composer.lock | 187 ++++++++++++++++++++++++-------------------------- 1 file changed, 91 insertions(+), 96 deletions(-) diff --git a/composer.lock b/composer.lock index 56cd5a004..a857782dc 100644 --- a/composer.lock +++ b/composer.lock @@ -510,31 +510,31 @@ }, { "name": "fruitcake/php-cors", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", - "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6|^7" + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" }, "require-dev": { - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^2", "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -565,7 +565,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" }, "funding": [ { @@ -577,7 +577,7 @@ "type": "github" } ], - "time": "2023-10-12T05:21:21+00:00" + "time": "2025-12-03T09:33:47+00:00" }, { "name": "graham-campbell/result-type", @@ -1054,16 +1054,16 @@ }, { "name": "laravel/framework", - "version": "v12.40.2", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1ccd99220b474500e672b373f32bd709ec38de50" + "reference": "509b33095564c5165366d81bbaa0afaac28abe75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1ccd99220b474500e672b373f32bd709ec38de50", - "reference": "1ccd99220b474500e672b373f32bd709ec38de50", + "url": "https://api.github.com/repos/laravel/framework/zipball/509b33095564c5165366d81bbaa0afaac28abe75", + "reference": "509b33095564c5165366d81bbaa0afaac28abe75", "shasum": "" }, "require": { @@ -1151,6 +1151,7 @@ "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", "illuminate/routing": "self.version", "illuminate/session": "self.version", "illuminate/support": "self.version", @@ -1175,7 +1176,7 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.8.0", + "orchestra/testbench-core": "^10.8.1", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -1237,6 +1238,7 @@ "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], @@ -1245,7 +1247,8 @@ "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", "src/Illuminate/Collections/", - "src/Illuminate/Conditionable/" + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" ] } }, @@ -1269,7 +1272,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-26T19:24:25+00:00" + "time": "2025-12-09T15:51:23+00:00" }, { "name": "laravel/horizon", @@ -1915,20 +1918,20 @@ }, { "name": "league/uri", - "version": "7.6.0", + "version": "7.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "f625804987a0a9112d954f9209d91fec52182344" + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", - "reference": "f625804987a0a9112d954f9209d91fec52182344", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.6", + "league/uri-interfaces": "^7.7", "php": "^8.1", "psr/http-factory": "^1" }, @@ -2001,7 +2004,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.6.0" + "source": "https://github.com/thephpleague/uri/tree/7.7.0" }, "funding": [ { @@ -2009,20 +2012,20 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2025-12-07T16:02:06+00:00" }, { "name": "league/uri-interfaces", - "version": "7.6.0", + "version": "7.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", "shasum": "" }, "require": { @@ -2085,7 +2088,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" }, "funding": [ { @@ -2093,7 +2096,7 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2025-12-07T16:03:21+00:00" }, { "name": "monolog/monolog", @@ -2200,16 +2203,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.3", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f" + "reference": "bdb375400dcd162624531666db4799b36b64e4a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", - "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1", "shasum": "" }, "require": { @@ -2217,9 +2220,9 @@ "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", - "symfony/clock": "^6.3.12 || ^7.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -2301,7 +2304,7 @@ "type": "tidelift" } ], - "time": "2025-09-06T13:39:36+00:00" + "time": "2025-12-02T21:04:28+00:00" }, { "name": "nette/schema", @@ -3368,22 +3371,21 @@ }, { "name": "symfony/clock", - "version": "v7.4.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/clock": "^1.0", - "symfony/polyfill-php83": "^1.28" + "php": ">=8.4", + "psr/clock": "^1.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -3422,7 +3424,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "source": "https://github.com/symfony/clock/tree/v8.0.0" }, "funding": [ { @@ -3442,20 +3444,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2025-11-12T15:46:48+00:00" }, { "name": "symfony/console", - "version": "v7.4.0", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" + "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "url": "https://api.github.com/repos/symfony/console/zipball/6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", "shasum": "" }, "require": { @@ -3520,7 +3522,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.0" + "source": "https://github.com/symfony/console/tree/v7.4.1" }, "funding": [ { @@ -3540,7 +3542,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-05T15:23:39+00:00" }, { "name": "symfony/css-selector", @@ -3991,16 +3993,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.0", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "769c1720b68e964b13b58529c17d4a385c62167b" + "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/769c1720b68e964b13b58529c17d4a385c62167b", - "reference": "769c1720b68e964b13b58529c17d4a385c62167b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bd1af1e425811d6f077db240c3a588bdb405cd27", + "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27", "shasum": "" }, "require": { @@ -4049,7 +4051,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.1" }, "funding": [ { @@ -4069,20 +4071,20 @@ "type": "tidelift" } ], - "time": "2025-11-13T08:49:24+00:00" + "time": "2025-12-07T11:13:10+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.0", + "version": "v7.4.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7348193cd384495a755554382e4526f27c456085" + "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7348193cd384495a755554382e4526f27c456085", - "reference": "7348193cd384495a755554382e4526f27c456085", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f6e6f0a5fa8763f75a504b930163785fb6dd055f", + "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f", "shasum": "" }, "require": { @@ -4168,7 +4170,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.2" }, "funding": [ { @@ -4188,7 +4190,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:38:24+00:00" + "time": "2025-12-08T07:43:37+00:00" }, { "name": "symfony/mailer", @@ -5431,16 +5433,16 @@ }, { "name": "symfony/string", - "version": "v8.0.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f" + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f929eccf09531078c243df72398560e32fa4cf4f", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", "shasum": "" }, "require": { @@ -5497,7 +5499,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.0" + "source": "https://github.com/symfony/string/tree/v8.0.1" }, "funding": [ { @@ -5517,38 +5519,31 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:37:55+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/translation", - "version": "v7.4.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" + "reference": "770e3b8b0ba8360958abedcabacd4203467333ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "url": "https://api.github.com/repos/symfony/translation/zipball/770e3b8b0ba8360958abedcabacd4203467333ca", + "reference": "770e3b8b0ba8360958abedcabacd4203467333ca", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5.3|^3.3" + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" }, "conflict": { "nikic/php-parser": "<5.0", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<6.4", - "symfony/yaml": "<6.4" + "symfony/service-contracts": "<2.5" }, "provide": { "symfony/translation-implementation": "2.3|3.0" @@ -5556,17 +5551,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5597,7 +5592,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.0" + "source": "https://github.com/symfony/translation/tree/v8.0.1" }, "funding": [ { @@ -5617,7 +5612,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/translation-contracts", From 7740dc738fa70f4e9d0b1d400b8eda87feaa45a3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:16:15 +0100 Subject: [PATCH 10/12] chore(jobs): disable queue interruption polling to improve performance --- app/Providers/AppServiceProvider.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e0f4444f7..22e8c5af5 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,6 +12,7 @@ use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\Vite; use Illuminate\Support\ServiceProvider; @@ -35,6 +36,7 @@ final class AppServiceProvider extends ServiceProvider $this->configureCommands(); $this->configureModels(); $this->configureDates(); + $this->configureQueues(); $this->configureTests(); $this->configureRequestExceptions(); $this->configureVite(); @@ -105,6 +107,14 @@ final class AppServiceProvider extends ServiceProvider Date::use(CarbonImmutable::class); } + /** + * Configure queues. + */ + private function configureQueues(): void + { + Queue::withoutInterruptionPolling(); + } + /** * Configure tests. */ From 98e37a5b3caebc4c95588dd104a8e7a8e78cb48e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:01:02 +0100 Subject: [PATCH 11/12] build: add empty compose.worker.yaml --- compose.worker.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 compose.worker.yaml diff --git a/compose.worker.yaml b/compose.worker.yaml new file mode 100644 index 000000000..e69de29bb From 694c87dc9ed4081483983b90757d746edeba0b08 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:51:59 +0100 Subject: [PATCH 12/12] docs: improve and update changelog - add new v5.x-chore/add-queue-system changes to changelog - improve wording for a few points --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 785c3cd3f..f60c62d07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,11 @@ All notable changes to this project will be documented in this file. - **v4 to v5 upgrade migration** - Added Coolify v4 database as `old_pgsql` connection - +- Worker Servers which replace build servers with servers that can also run jobs (horizon workers) in addition to building docker images ### Changed -- Upgraded all Composer and Node dependencies, most notably: PHP to 8.5 (previously 8.4), TailwindCSS to v4.0 (previously v3) and Laravel to v12 (previously v10) +- Upgraded all Composer and Node dependencies and adopted their latest syntax and features, most notably: PHP to 8.5 (previously 8.2), TailwindCSS to v4.0 (previously v3) and Laravel to v12 (previously v10) - **Docker:** - Upgraded all Docker dependencies, most notably: Postgres to v18 (previously v15) and Redis to v8 (previously v7) - @@ -25,6 +26,11 @@ All notable changes to this project will be documented in this file. - Use Redis for sessions and expire inactive sessions after 24h (previously 14 days) - Encrypt user sessions data - Expire password reset tokens after 10 minutes (previously 60 minutes) + - Jobs now wait for all DB transactions to be finished before being dispatched which prevents race conditions + - Normal jobs (backups, emails, etc.) and deployment jobs now use separate supervisor configurations and defaults + - Horizon workers are now restarted after 500 (job workers) or 300 (deployment workers) jobs or after 1 hour to clean up stale memory and CPU usage + - Reduced default queue timeouts from 10h to 60s for jobs and 300s for deployments to prevent stale jobs + - Increased `balanceCooldown` from 1s to 2s for jobs to reduce CPU spikes - Redirect Laravel logs to `stderr` so they can be viewed in docker logs - Configured production logging to rotate automatically and keep only the last 10 days of logs to reduce disk usage - Changed production log level from `debug` to `warning` to reduce disk usage and avoid logging sensitive information @@ -44,6 +50,8 @@ All notable changes to this project will be documented in this file. ### Fixed - `laravel.log` file growing indefinitely and consuming excessive disk space +- Removed logging of failed jobs into the database as we use Horizon for that and it can cause excessive disk usage in some cases +- On v4 when changing the maximum concurrent builds setting to more than 4 builds the setting is no longer respected because there is a maximum of 4 horizon workers available by default - ### Security @@ -70,6 +78,7 @@ All notable changes to this project will be documented in this file. - Automatically eager load all relationships to prevent N+1 queries - Configure models and enforce morph map for polymorphic relationships - Enforce immutable dates globally + - Disable queue interruption polling to improve performance - Fake sleeps and prevent stray HTTP requests in testing - Prevent exception truncation in development - Use aggressive Vite prefetching for better performance @@ -83,13 +92,14 @@ All notable changes to this project will be documented in this file. - Cancel in-progress action runs when a new run is triggered - Improved `SECURITY.md` formatting and wording and added the support policy for `v5.x` - Refactored the GitHub issue templates to use issue types and improved formatting and wording - - Moved `README.md` assets into `.github/assets/` to easier exclude them from the core repository code + - Moved `README.md` assets into `.github/assets/` to more easily exclude them from the core repository code - Removed the `chore-remove-labels-and-assignees-on-close.yml` workflow as labels and assignees are now kept when closing Issues and PRs ### Refactored - Completely refactored all database migrations for a cleaner, more consistent and stable database schema - Completely refactored all database models +- Queues are now accessed via an enum instead of hardcoded strings ## Issues