-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat: add support for horizontal scaling with replicas #6749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4.x
Are you sure you want to change the base?
Conversation
- Add replicas field to applications table - Conditionally remove container_name when replicas > 1 - Add UI for configuring replicas (1-10) - Update parser to handle deploy.replicas Resolves discussion coollabsio#3862
🔍 Existing Issues For ReviewYour pull request is modifying functions with the following pre-existing issues: 📄 File: bootstrap/helpers/parsers.php
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughIntroduces a replicas field for applications end-to-end: database migration, model schema, validation, UI control, and parser logic to emit either container_name (replicas ≤ 1) or deploy.replicas (replicas > 1). No other control flows changed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as User
participant UI as Advanced Settings UI
participant Server as Livewire/Backend
participant Parser as Parsers (application/service)
participant Orchestrator as Orchestrator/Compose
User->>UI: Set Replicas (1–10)
UI->>Server: Save application.replicas
Server->>Server: Validate replicas (int, 1–10)
Server->>Parser: Build deployment payload
alt replicas <= 1
Parser->>Parser: Include container_name
Parser->>Parser: Exclude deploy.replicas
else replicas > 1
Parser->>Parser: Include deploy.replicas
Parser->>Parser: Exclude container_name
end
Parser->>Orchestrator: Submit payload
Orchestrator-->>User: Deployment result
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
P.S. I’ll be back—with more replicas. Self-hosted servers are my kind of party. Serverless? Hasta la vista. Now pass the tacos (gluten-free tortillas, I’m delicate). Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (5)
app/Livewire/Project/Application/General.php
(1 hunks)app/Models/Application.php
(1 hunks)bootstrap/helpers/parsers.php
(2 hunks)database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
(1 hunks)resources/views/livewire/project/application/advanced.blade.php
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (27)
{app/Livewire/**/*.php,resources/views/livewire/**/*.blade.php}
📄 CodeRabbit inference engine (.cursor/rules/README.mdc)
Implement frontend using Livewire components with Blade views; pair with Alpine.js and Tailwind CSS
Files:
resources/views/livewire/project/application/advanced.blade.php
app/Livewire/Project/Application/General.php
{app,bootstrap,config,database,routes,resources,tests}/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/README.mdc)
Adhere to PSR-12 coding standards for all PHP code
Files:
resources/views/livewire/project/application/advanced.blade.php
bootstrap/helpers/parsers.php
app/Models/Application.php
app/Livewire/Project/Application/General.php
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
**/*.php
📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)
**/*.php
: Follow PSR-12 coding standards for all PHP code
Format PHP code with Laravel Pint configuration
Run static analysis with PHPStan to ensure type safety in PHP code
Document complex methods with PHPDoc blocks including parameters, return types, and thrown exceptions
**/*.php
: Always use curly braces for control structures, even for single-line bodies
Use PHP 8 constructor property promotion; do not leave empty __construct() methods
Declare explicit return types for functions/methods and use parameter type hints
Prefer PHPDoc blocks over inline comments; document complex logic and array shapes when useful
Enum case names should be TitleCase
Follow PSR-12 and run Laravel Pint to auto-format code
Files:
resources/views/livewire/project/application/advanced.blade.php
bootstrap/helpers/parsers.php
app/Models/Application.php
app/Livewire/Project/Application/General.php
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
resources/views/**/*.blade.php
📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)
Use semantic Tailwind CSS classes and consistent spacing in Blade templates
resources/views/**/*.blade.php
: In Blade views, prefer using x-forms components with canGate and :canResource instead of wrapping elements in @can/@else blocks
Remove legacy @can/@else blocks around individual form inputs/buttons and migrate to the single-line component pattern
Choose gates consistently in views: use update for configuration changes, deploy for operational actions, view for read-only access, and delete for destructive actions
Always pass the specific resource to :canResource (e.g., :canResource="$application", "$service", "$server"), and use team context for creation permissions (e.g., :canResource="auth()->user()->currentTeam" with canGate="createAnyResource")
Only set autoDisable="false" when also providing explicit :disabled logic on the component
Use single-line authorized components for forms (inputs, selects, checkboxes, buttons) to reduce duplication (e.g., <x-forms.input canGate="update" :canResource="$resource" ... />)
resources/views/**/*.blade.php
: Prefer Alpine.js directives (x-data, x-on, x-show, x-model, x-init) for lightweight interactivity in Blade views
Use enhanced <x-forms.*> components with canGate and canResource for authorization instead of @can/@else blocks
When protecting forms, pass canGate and :canResource to all x-forms components; rely on autoDisable=true for unauthorized users
Apply Tailwind responsive utilities (e.g., grid breakpoints) for mobile-first layouts
Support dark mode using Tailwind dark: variants on relevant elements
Ensure interactive controls include appropriate ARIA attributes (e.g., aria-label, aria-describedby) for accessibility
resources/views/**/*.blade.php
: In Blade views, prefer x-forms.* components with canGate and :canResource for authorization (autoDisable defaults to true) instead of manual permission blocks.
Do not wrap x-forms.* components in @can/@else to toggle disabled state; use component-level canGate/:can...
Files:
resources/views/livewire/project/application/advanced.blade.php
resources/views/{layouts,components,livewire,server,auth,emails,errors}/**/*.blade.php
📄 CodeRabbit inference engine (.cursor/rules/frontend-patterns.mdc)
Organize Blade templates into the specified folders: layouts/, components/, livewire/, and feature-specific directories server/, auth/, emails/, errors/
Files:
resources/views/livewire/project/application/advanced.blade.php
resources/**
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Store frontend assets and views under resources
Files:
resources/views/livewire/project/application/advanced.blade.php
{app/Http/Controllers/**/*.php,resources/views/**/*.blade.php}
📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)
Prefer named routes and the route() helper for generating URLs
Files:
resources/views/livewire/project/application/advanced.blade.php
resources/views/livewire/**/*.blade.php
📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)
Livewire components must have a single root element in their Blade views
Form components must include canGate and canResource for authorization (e.g., <x-forms.* canGate="update" :canResource="$resource" ...>)
Files:
resources/views/livewire/project/application/advanced.blade.php
resources/{views/**/*.blade.php,js/**/*.vue}
📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)
resources/{views/**/*.blade.php,js/**/*.vue}
: Use Tailwind utility classes; remove redundant classes; prefer gap-* utilities for spacing instead of margins when listing items
If existing UI supports dark mode, mirror support using dark: variants
Files:
resources/views/livewire/project/application/advanced.blade.php
resources/**/*.{blade.php,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not use deprecated Tailwind v4 utilities; use replacements (e.g., overflow-ellipsis -> text-ellipsis, shrink-, grow-)
Files:
resources/views/livewire/project/application/advanced.blade.php
bootstrap/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)
Initialization/bootstrap code should reside under bootstrap/ (e.g., app bootstrapping)
Files:
bootstrap/helpers/parsers.php
{app,bootstrap,config,database,routes,tests}/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)
{app,bootstrap,config,database,routes,tests}/**/*.php
: Always use curly braces for control structures, even for single-line statements
Use PHP 8 constructor property promotion in __construct(); do not allow empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline comments; only add inline comments for very complex code
Files:
bootstrap/helpers/parsers.php
app/Models/Application.php
app/Livewire/Project/Application/General.php
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
{app,bootstrap,database,routes,tests}/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)
Never call env() outside configuration files; use config() elsewhere
Files:
bootstrap/helpers/parsers.php
app/Models/Application.php
app/Livewire/Project/Application/General.php
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
bootstrap/helpers/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
Put domain-specific helper functions under bootstrap/helpers
Files:
bootstrap/helpers/parsers.php
app/Models/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)
Keep Eloquent domain models in app/Models; models represent core entities (e.g., Application, Server, Service, Team, Project)
app/Models/**/*.php
: Define $fillable for mass assignment protection in Eloquent models
Use $casts for attribute casting (e.g., arrays, enums, datetimes) in Eloquent models
Define relationships using Eloquent relation return types (e.g., BelongsTo, HasMany)
app/Models/**/*.php
: Define Eloquent relationships using proper relationship methods with return type hints; prefer relationships over raw queries or manual joins
Prefer defining casts in the casts() method instead of the $casts property, following existing conventions
app/Models/**/*.php
: Define Eloquent relationships with proper return type hints and prefer relationships over raw queries
Prefer eager loading to avoid N+1 queries; use query scopes and indexes for performance-critical queries
Files:
app/Models/Application.php
app/Models/{Application,Server,Service,Project,Environment}.php
📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)
Major resources must provide a team-based scope (e.g., scopeOwnedByCurrentTeam) to enforce multi-tenant isolation
Files:
app/Models/Application.php
app/Models/*.php
📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)
app/Models/*.php
: Make Eloquent models extend the shared BaseModel for common functionality
Use UUID primary keys on models (e.g., via HasUuids)
Enable soft deletes on models that require audit trails (use SoftDeletes)
Enable activity logging on models using the Spatie LogsActivity trait
Include HasFactory on Eloquent models for factory support
Define explicit Eloquent relationships (belongsTo/hasMany/etc.) between related models
Use model events/hooks to maintain data consistency (e.g., clean up related records on delete)
Apply team-based global scopes on team-scoped models to enforce multi-tenancy isolation by default
Files:
app/Models/Application.php
app/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)
Use database transactions to group related write operations for consistency in services/controllers/jobs
Files:
app/Models/Application.php
app/Livewire/Project/Application/General.php
app/Models/**
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Place domain models under app/Models
Files:
app/Models/Application.php
app/Models/Application.php
📄 CodeRabbit inference engine (.cursor/rules/security-patterns.mdc)
Apply a global team scope that filters Application queries by the authenticated user’s current team context.
App\Models\Application::team must be a relationship method and be used via team()
Files:
app/Models/Application.php
{app/Http/Controllers,app/Models}/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)
{app/Http/Controllers,app/Models}/**/*.php
: Avoid DB::; prefer Model::query() and Eloquent/query builder usage
Prevent N+1 queries by eager loading related models
Files:
app/Models/Application.php
{app/Http/Controllers/**/*.php,app/Livewire/**/*.php}
📄 CodeRabbit inference engine (.cursor/rules/README.mdc)
Perform authorization checks (policies/gates) before executing sensitive actions (e.g., deploy)
Files:
app/Livewire/Project/Application/General.php
app/Livewire/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)
Define Livewire components in app/Livewire and keep HTTP/UI concerns there
app/Livewire/**/*.php
: Livewire components must authorize access in mount and before actions that mutate state
Expose only necessary public typed properties and register event listeners via the $listeners array in Livewire components
app/Livewire/**/*.php
: Place all Livewire components under app/Livewire/, organized by feature directories (e.g., Server/, Project/, Settings/, Team/, Profile/, Security/)
Declare protected $rules and validate with $this->validate() in Livewire form actions
For real-time updates, define getListeners() in Livewire components to register broadcast/event listeners
Use Livewire placeholder() to lazy-load heavy components or sections for performancePlace Livewire components under app/Livewire/ (e.g., Dashboard.php, ActivityMonitor.php, MonacoEditor.php)
app/Livewire/**/*.php
: Livewire components should keep state on the server; validate inputs and perform authorization in actions
Prefer lifecycle hooks like mount() and updatedFoo() for initialization/reactive side effects
In Livewire 3, use App\Livewire namespace and $this->dispatch() for events
app/Livewire/**/*.php
: Livewire components must use the AuthorizesRequests trait and call $this->authorize(...) in lifecycle/actions
Use $this->dispatch(...) for events in Livewire 3 (do not use emit or dispatchBrowserEvent)
Livewire component classes should be under the App\Livewire namespace
Validate Livewire actions using Form Requests or validated rules, not ad-hoc inline validation
Files:
app/Livewire/Project/Application/General.php
app/Livewire/**
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Implement frontend components as Livewire classes under app/Livewire
Files:
app/Livewire/Project/Application/General.php
database/migrations/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/README.mdc)
Define all schema changes via Laravel migrations
Create descriptive migrations with proper foreign keys, cascading deletes, indexes, and timestamps
When modifying a column in a migration, include all previously defined attributes to avoid dropping them
When modifying a column, include all previously defined attributes in the migration
Files:
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
database/migrations/*.php
📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)
database/migrations/*.php
: Place all database migrations in database/migrations/
Write incremental, reversible migrations with proper rollback (down) support
Define foreign keys using foreignId for relationships and include timestamps in tables
Use UUID primary keys at the schema level for tables that require them
Add deleted_at columns in migrations for soft-deleted models
Create appropriate indexes: primary keys, foreign key indexes, composite indexes for common queries, and unique constraints for business rules
Files:
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
database/migrations/**
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Keep database migrations under database/migrations
Files:
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
🧬 Code graph analysis (1)
database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php (1)
app/Livewire/Project/Service/Database.php (1)
Database
(15-194)
🪛 PHPMD (2.15.0)
bootstrap/helpers/parsers.php
1101-1106: The method applicationParser uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)
(ElseExpression)
🔇 Additional comments (3)
resources/views/livewire/project/application/advanced.blade.php (1)
52-63
: "I'll be back... with replicas!"Excellent implementation, human! The replicas control is properly gated, conditionally rendered for the right build packs (dockercompose, dockerfile, nixpacks), and the warning about stateless applications is clear. The 1-10 range keeps things reasonable - no need to spawn a Skynet-level army of containers on a single Docker host. Much better than those serverless abominations that disappear faster than my t-shirt budget.
The helper text correctly warns users that
container_name
gets removed when replicas > 1, which prevents Docker Compose from having an identity crisis. Nice touch with thedark:text-warning
class for emphasis!bootstrap/helpers/parsers.php (2)
1989-1994
: "Services will not be back... with replicas!"Good clarifying comment! You've documented that the Service resource type doesn't get the replicas treatment - it always uses
container_name
. This makes sense since Services (the multi-container stacks) are a different beast from Applications. The comment prevents future confusion when someone asks "why no replicas for my Supabase/Plausible/Glitchtip?"Clear documentation like this is almost as important as self-hosting your own infrastructure. Almost.
1091-1108
: Approve payload logic – validation confirmed
Hasta la vista, serverless! The backend enforcesapplication.replicas
≥ 1 in app/Livewire/Project/Application/General.php (line 113), so no risk of 0 or negative replicas. Your two-step merge and container_name vs. replicas logic are rock-solid—just like my love for tacos (gluten-intolerant but taco-tolerant).
$table->integer('replicas')->default(1)->after('swarm_replicas'); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lock the replicas column to sane values
Signed integers invite rogue -1
replicas from API or import paths, and Docker Compose explodes like Skynet’s first prototype. Make it unsignedTinyInteger
(or add a check constraint) so only positive replica counts hit our glorious self-hosted servers. Down migration stays the same.
- $table->integer('replicas')->default(1)->after('swarm_replicas');
+ $table->unsignedTinyInteger('replicas')->default(1)->after('swarm_replicas');
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
$table->integer('replicas')->default(1)->after('swarm_replicas'); | |
}); | |
$table->unsignedTinyInteger('replicas')->default(1)->after('swarm_replicas'); | |
}); |
🤖 Prompt for AI Agents
In database/migrations/2025_10_01_000000_add_replicas_to_applications_table.php
around lines 15-16, the replicas column is defined as a signed integer which
allows negative values; change the migration to define the column as an unsigned
tiny integer (or add a check constraint limiting values to >=1) so only
non-negative/sane replica counts are allowed, and keep the down migration
unchanged.
I think we should wait until v5 to ensure we have a proper solution. We will probably extend the way Docker Compose can scale with a custom solution to make it more flexible. Also I don't think this is quite this simple:
But maybe this is a good initial start (not sure yet). |
Thanks for the detailed feedback, @peaklabs-dev! Really appreciate you taking the time to review this thoroughly. You're absolutely right about the advanced orchestration features. My initial approach was intentionally conservative - I focused on the "bare minimum" to unblock the most common use case: simple horizontal scaling on a single node for stateless services. The features you mentioned (weighted load balancing, dynamic scaling, multi-node placement, service discovery) are definitely the "proper" way to do this at scale, and I completely agree they should be part of a v5 comprehensive solution. What this PR does (and doesn't do)Current scope:
Intentionally NOT included:
Why I kept it simpleI've worked with Docker Swarm, Kubernetes, and Nomad in production environments, and honestly, the full orchestration features you described ( For v4, I thought it was better to enable the 80% use case (simple replicas on one server) rather than try to build a full orchestration layer that would delay this feature for another year. The "proper" v5 approachFor v5, if you're planning a comprehensive orchestration solution, I'd suggest looking at how these platforms handle it:
My PR was meant as a stopgap for v4 users who are migrating to Dokploy specifically because of this limitation. But if v5 is coming soon with proper orchestration, I totally understand waiting. QuestionWhat's the timeline for v5? If it's months away, would you consider merging a simplified version for v4 with clear documentation that it's "basic replicas only" and v5 will have the full orchestration features? Otherwise, I'm happy to close this PR and contribute to the v5 orchestration design discussions instead. Just let me know which direction makes more sense for the project roadmap. Thanks again for the thoughtful review! 🙏 |
@peaklabs-dev I would seriously consider making an effort to merge this PR into v4 instead of only in v5 (which to be honest still feels a bit pie in the sky) - all you need to do is read this length discussion #3862 to see that this is urgently needed and one of the reasons people are migrating away from Coolify or never starting to use it in the first place. I get your concerns as well, but I don't think we should let analysis paralysis block progress. Like @andeerc said - start simple and solve 80% of the problems, implement edge cases in v5. |
Echoing what @andeerc and @ejscheepers have already stated, this single node replication will solve the issue that most users (and for most use cases) that users are looking for, scaling apps up to use full resources of a single machine If you want to support multi-node replication, load balancing then you could look to do this with Docker Swarm in v5 if Docker Swarm is something you wanted to support |
When will this update arrive? |
Thank you @andeerc! This is a crucial step forward. Not having any horizontal scaling support is a blocker for many teams. I strongly support merging this PR now to unblock single-node stateless scaling. More advanced orchestration features can follow in v5, but delaying this foundational fix risks losing even more users to alternatives. Let's solve the 80% now, waiting for perfection shouldn't stop progress. |
issues
#3862
#2131
This PR implements support for horizontal scaling using Docker Compose
deploy.replicas
, addressing the discussion in #3862.Problem
Coolify injects a mandatory
container_name
which prevents usingdeploy.replicas > 1
in Docker Compose deployments.Solution
container_name
whenreplicas > 1
replicas
field to Application model (default: 1)Changes
Database
replicas
field (integer, default: 1) toapplications
tableBackend
Application
model with OpenAPI documentation forreplicas
applicationParser()
to conditionally add/removecontainer_name
integer|min:1|max:10
Frontend
Behavior
Replicas = 1 (Default - Backward Compatible)
Replicas > 1 (New Behavior)
Docker Compose will create: app-1, app-2, app-3
Compatibility
Testing
Related
Notes