Skip to content

Conversation

@Jackman3005
Copy link

@Jackman3005 Jackman3005 commented Nov 6, 2025

Description

This PR adds a configurable stop grace period for application containers, allowing users to specify how long Docker should wait for graceful shutdown before forcing containers to stop.

Previously, the stop timeout was hardcoded to 30 seconds, which was insufficient for applications that need more time for graceful shutdown (e.g., draining connections, finishing requests, cleanup operations). Users can now configure a custom timeout between 1 and 3600 seconds (1 hour) via the UI in Advanced settings.

The setting applies to:

  • Rolling updates (stopping old containers after new ones are healthy)
  • Manual application stops
  • Preview deployment stops
  • Application restarts

When not configured, the system defaults to 30 seconds, maintaining backwards compatibility.

Changes

  • Added stop_grace_period nullable integer field to application_settings table (1-3600 range)
  • Added stop_grace_period property to ApplicationSetting model with integer cast
  • Added UI input control in Advanced settings page with validation
  • Updated graceful_shutdown_container() in ApplicationDeploymentJob to use configured timeout
  • Updated StopApplication action to use configured timeout
  • Updated StopApplicationOneServer action to use configured timeout
  • Updated Previews component stopContainers() to use configured timeout
  • Added test coverage in ApplicationSettingStaticCastTest for model casting and null handling
  • Added DEFAULT_STOP_GRACE_PERIOD_SECONDS = 30 constant to bootstrap/helpers/constants.php. Maybe there is a more appropriate location for this? Please let me know if so. All locations that hardcoded 30s as the default now use this constant as well as the 2 new locations in the UI where it is shown (tooltip and placeholder).
  • Added database migration 2025_11_05_091558_add_stop_grace_period_to_application_settings.php

Issues

  • Addresses my critical need for configurable graceful shutdown timeouts during deployments as I need to wait a few minutes to ensure long-running message handlers have time to complete successfully before being forced to quit.
  • Related to [Bug]: Services are killed instead of gracefully stopped due to faulty process handling and not respecting stop_grace_period #5620 (docker-compose stop_grace_period discussion). Doesn't resolve exactly as using stop_grace_period from the docker-compose.yml had two problems.
    1. Identifying the service by the generated name is fragile and could fail if user specified a name override.
    2. Only have access to the latest docker-compose.yml, not the yaml used to deploy the currently active containers, meaning the latest yaml may no longer have that service, may have it under a new name, or may have changed the grace period. Likely we should store this information from the yaml file on start/deployment of the service and use the stored value when gracefully stopping the service.

Screenshots

Added to advanced options under new Operations section header
Screenshot 2025-11-06 at 5 27 14 pm

Empty is treated as null which falls back to default, so 30 is shown as placeholder since it is the default.
Screenshot 2025-11-06 at 5 27 34 pm

Informative tooltip
Screenshot 2025-11-06 at 5 27 21 pm

Confirmed that value set and saved is used when calling docker stop in a rolling update.
Screenshot 2025-11-06 at 5 26 42 pm

Summary by CodeRabbit

  • New Features

    • Added configurable Stop Grace Period setting in Advanced application settings, allowing control over how long containers are permitted to gracefully terminate before forced removal (valid range: 1-3600 seconds; defaults to 30 seconds).
  • Tests

    • Added unit tests for stop grace period configuration and type casting validation.

@Jackman3005
Copy link
Author

Jackman3005 commented Nov 6, 2025

Hi @andrasbacsai and @peaklabs-dev 👋

This is my first contribution here to Coolify :)

We've been using Coolify for a couple years now and are rather happy with it. We are moving some services from AWS to a Hetzner-hosted Coolify instance. While going through production-readiness we noticed our new way of handling messages is part of the application deployment flow on Coolify, which means that when a new deployment comes in, the hardcoded 30 seconds of timeout is provided to docker stop when the new container is healthy and takes on load.

Overall the rolling deployment process is great, but some of our messages have a timeout of 5 minutes as there can be quite a lot happening there, the 30s timeout opens up an issue if we initiate the action on an external service before stopping the container as we are then not able to wait the full time for the response, meaning the action will end up being performed twice after the message becomes visible on the queue again. This just isn't an acceptable outcome in our situation and there doesn't seem to be a great way to work around this in our architecture at the moment. It wasn't a problem on AWS as new deployments did not stop existing Lambdas triggered by SQS and they instead were allowed to complete with their normal timeouts while new lambdas picked up any new messages.

@andrasbacsai - It looks like you were considering doing something like this before (source):

I can add a dynamic settings to each app as a settings that you can control and set this timeout value. Wdyt?

I have no idea if this is in alignment with how you would have added the feature. I'm totally open to feedback and will happily fix it up to make it mergeable as it's quite important for us to have this functionality.

Let me know what you think :)

@Jackman3005 Jackman3005 marked this pull request as draft November 6, 2025 09:59
Allow users to configure the Docker stop timeout (1-3600 seconds) for
graceful container shutdown during rolling updates, manual stops, and
restarts. Defaults to 30 seconds when not configured.

  - Add `stop_grace_period` field to `ApplicationSettings` (nullable integer)
  - Add UI control in Advanced settings with validation
  - Apply timeout to all container stop operations (deployments, manual stops, previews)
  - Add test coverage for model casting and null handling
@Jackman3005 Jackman3005 force-pushed the feat/configurable-stop-grace-period branch from c68089f to 35d7f52 Compare November 6, 2025 10:29
 - It was hardcoded in numerous areas as well as in strings/placeholders
   shown to the user. In case it was ever changed it would be nice that
   all locations would appropriately be updated and not cause confusion.
@Jackman3005
Copy link
Author

Oh, I just realized I didn't consider adding any doc updates as part of this PR. The things of note are that this only applies to Applications, not Services and that docker stop has a default signal it sends to your running process, but you can specify a STOPSIGNAL SIGTERM (or SIGQUIT, etc.) in your Dockerfile if the code needs to be different for your process to detect a graceful shutdown request.

@Jackman3005
Copy link
Author

Another small detail is that --time appears to be deprecated and we should switch to --timeout. I didn't want to include that into this PR to make it that much bigger, but I'm happy to or to put it up in another PR if desired.

@ShadowArcanist
Copy link
Member

Another small detail is that --time appears to be deprecated and we should switch to --timeout. I didn't want to include that into this PR to make it that much bigger, but I'm happy to or to put it up in another PR if desired.

Hey, I have an PR for that! - #6807

@ShadowArcanist
Copy link
Member

@CodeRabbit review!!!!!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

@ShadowArcanist: I'll review the changes in this PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ShadowArcanist ShadowArcanist added the 🛠️ Feature Issues requesting a new feature. label Nov 6, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 6, 2025

Hi @Jackman3005! 👋

It appears to us that you are adding a new feature to Coolify.
We kindly ask you to also update the Coolify Documentation to include information about this new feature.
This will help ensure that our documentation remains accurate and up-to-date for all users.

Coolify Docs Repository: https://github.com/coollabsio/coolify-docs
How to Contribute to the Docs: https://coolify.io/docs/get-started/contribute/documentation

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

This pull request introduces a configurable stop grace period for application containers, replacing a hardcoded 30-second timeout. The feature includes a new database column, model casting, Livewire component with UI, and updates to container stop logic across multiple deployment and preview flows.

Changes

Cohort / File(s) Summary
Database & Model Setup
bootstrap/helpers/constants.php, database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php, app/Models/ApplicationSetting.php
Adds constant DEFAULT_STOP_GRACE_PERIOD_SECONDS = 30, migration to create nullable stop_grace_period column on application_settings table, and integer casting in ApplicationSetting model.
Livewire UI Component & View
app/Livewire/Project/Application/Advanced.php, resources/views/livewire/project/application/advanced.blade.php
Adds new nullable stopGracePeriod property with validation, new saveStopGracePeriod() method with input normalization and range validation (1-3600), and new "Operations" form section in view with numeric input and helper text.
Container Stop Actions
app/Actions/Application/StopApplication.php, app/Actions/Application/StopApplicationOneServer.php, app/Livewire/Project/Application/Previews.php
Replaces hardcoded 30-second timeout with dynamic timeout derived from application->settings->stop_grace_period, falling back to DEFAULT_STOP_GRACE_PERIOD_SECONDS.
Deployment Job
app/Jobs/ApplicationDeploymentJob.php
Updates graceful_shutdown_container timeout logic to use configurable stop_grace_period setting instead of hardcoded 30 seconds for non-dev environments.
Test Coverage
tests/Unit/ApplicationSettingStaticCastTest.php
Adds three unit tests verifying stop_grace_period is cast as integer, handles null values, and correctly casts string inputs.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Advanced View
    participant Livewire as Advanced Component
    participant Settings as ApplicationSetting
    
    User->>UI: Input stop grace period (1-3600 sec)
    User->>UI: Click Save
    UI->>Livewire: saveStopGracePeriod()
    Livewire->>Livewire: Normalize input (empty/null → null)
    Livewire->>Livewire: Validate range (1-3600)
    alt Validation passes
        Livewire->>Settings: Persist to stop_grace_period
        Settings->>Settings: Cast to integer
        Livewire->>UI: Success notification
    else Validation fails
        Livewire->>UI: Error notification
    end
Loading
sequenceDiagram
    participant Container as Container Stop Flow
    participant Setting as ApplicationSetting
    participant Docker as Docker
    
    Note over Container: Old: timeout = 30 sec (hardcoded)
    Note over Container: New: timeout = configurable
    
    Container->>Setting: Read stop_grace_period
    alt Setting exists
        Setting-->>Container: timeout = stop_grace_period
    else Setting not set
        Setting-->>Container: timeout = DEFAULT_STOP_GRACE_PERIOD_SECONDS (30)
    end
    Container->>Docker: docker stop --time={timeout} container
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas:
    • app/Livewire/Project/Application/Advanced.php — Verify input normalization logic (empty string/null handling) and validation range enforcement (1-3600 bounds)
    • Timeout logic consistency across StopApplication.php, StopApplicationOneServer.php, ApplicationDeploymentJob.php, and Previews.php — ensure fallback to constant is applied uniformly
    • Migration reversibility and schema consistency
    • Test coverage for edge cases (null, boundary values 1 and 3600)

Poem

🐰✨ A grace period blooms, configurable and true,
No more the frozen thirty—now containers bid adieu!
From job to stop action, the timeout flows with grace,
Settings dance with integers, each in their rightful place.
A quantum leap for control, the rabbit hops with cheer! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding a configurable stop grace period for application containers, which is the core feature introduced across all modified files.
Description check ✅ Passed The description is comprehensive and complete, covering changes, issues addressed, design decisions, backwards compatibility considerations, and screenshots. All required template sections are filled with relevant details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (3)
tests/Unit/ApplicationSettingStaticCastTest.php (1)

107-128: Good test coverage, consider adding edge case tests.

The tests properly verify the integer casting behavior and null handling. To ensure robustness, consider adding test cases for:

  • Zero value: $setting->stop_grace_period = 0 (should cast to int 0)
  • Negative value: $setting->stop_grace_period = -10 (should cast to int -10)

These edge cases would help document the expected behavior when invalid values are stored in the database.

Example tests to add:

it('casts stop_grace_period zero to integer', function () {
    $setting = new ApplicationSetting;
    $setting->stop_grace_period = 0;

    expect($setting->stop_grace_period)->toBe(0)
        ->and($setting->stop_grace_period)->toBeInt();
});

it('casts stop_grace_period negative value to integer', function () {
    $setting = new ApplicationSetting;
    $setting->stop_grace_period = -10;

    expect($setting->stop_grace_period)->toBe(-10)
        ->and($setting->stop_grace_period)->toBeInt();
});
app/Livewire/Project/Application/Previews.php (1)

278-278: Add defensive validation check for edge case: prevent zero or negative timeout values.

While validation exists in Advanced.php's update handler (lines 263-264), it only protects that save path. With no model-level validation or database constraints, a zero or negative value could bypass the check if set through direct database modification or a future code path. The ?? operator at usage points only catches null, not invalid values.

The suggested fix remains valid:

-        $timeout = $this->application->settings->stop_grace_period ?? DEFAULT_STOP_GRACE_PERIOD_SECONDS;
+        $timeout = $this->application->settings->stop_grace_period > 0 
+            ? $this->application->settings->stop_grace_period 
+            : DEFAULT_STOP_GRACE_PERIOD_SECONDS;

Alternatively, add model-level validation in ApplicationSetting to enforce the constraint once, preventing the issue across all save paths.

resources/views/livewire/project/application/advanced.blade.php (1)

46-60: Ensure stop grace period value binds to Livewire state
Confirm that <x-forms.input id="stopGracePeriod" … /> actually binds to the $stopGracePeriod property (e.g., via wire:model). Without a binding, the submitted value will remain empty and the new save handler will keep persisting null. If the component does not auto-bind by id, add the explicit wire:model.defer="stopGracePeriod" (or equivalent).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 395d225 and 99b6da5.

📒 Files selected for processing (10)
  • app/Actions/Application/StopApplication.php (1 hunks)
  • app/Actions/Application/StopApplicationOneServer.php (1 hunks)
  • app/Jobs/ApplicationDeploymentJob.php (1 hunks)
  • app/Livewire/Project/Application/Advanced.php (3 hunks)
  • app/Livewire/Project/Application/Previews.php (1 hunks)
  • app/Models/ApplicationSetting.php (1 hunks)
  • bootstrap/helpers/constants.php (1 hunks)
  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php (1 hunks)
  • resources/views/livewire/project/application/advanced.blade.php (1 hunks)
  • tests/Unit/ApplicationSettingStaticCastTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (26)
bootstrap/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Initialization/bootstrap code should reside under bootstrap/ (e.g., app bootstrapping)

Files:

  • bootstrap/helpers/constants.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: Follow PSR-12 PHP coding standards across the codebase
Prefer eager loading and query optimization to prevent N+1 issues in database interactions
Use Laravel best practices for structure, services, and policies

**/*.php: Always use curly braces for control structures in PHP, even for single-line bodies
Use PHP 8 constructor property promotion in __construct() and avoid empty constructors
Always declare explicit return types for functions and methods; add appropriate parameter type hints
Prefer PHPDoc blocks over inline comments; only add inline code comments for very complex logic
When documenting arrays, add useful array shape types in PHPDoc where appropriate
Enum case names should be TitleCase (e.g., FavoritePerson, Monthly)

Files:

  • bootstrap/helpers/constants.php
  • tests/Unit/ApplicationSettingStaticCastTest.php
  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
  • app/Livewire/Project/Application/Advanced.php
  • app/Livewire/Project/Application/Previews.php
  • app/Actions/Application/StopApplicationOneServer.php
  • app/Jobs/ApplicationDeploymentJob.php
  • app/Models/ApplicationSetting.php
  • resources/views/livewire/project/application/advanced.blade.php
  • app/Actions/Application/StopApplication.php
tests/Unit/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Place unit tests under tests/Unit and keep them focused on individual classes/methods

tests/Unit/**/*.php: Unit tests (tests/Unit) must not use database connections or model factories
Unit tests must use mocking for models and external dependencies

Files:

  • tests/Unit/ApplicationSettingStaticCastTest.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Write feature tests for API endpoints using Pest; mock external services and assert JSON responses and side effects

Add tests validating that components respect authorization (e.g., unauthorized users see disabled inputs; checkbox instantSave becomes false)

Include security tests covering SQL injection resistance, XSS validation, and enforcement of team isolation (403 on cross-team access).

Write tests using Pest (feature/unit) and include Laravel Dusk for browser scenarios as needed

Prefer Pest PHP as the primary testing framework for writing tests

Files:

  • tests/Unit/ApplicationSettingStaticCastTest.php
tests/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Keep automated tests (Pest/Dusk) under tests

Files:

  • tests/Unit/ApplicationSettingStaticCastTest.php
tests/{Feature,Unit}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

tests/{Feature,Unit}/**/*.php: All tests must be written using Pest and live under tests/Feature or tests/Unit
Use specific response assertion helpers (e.g., assertForbidden, assertNotFound) instead of generic assertStatus
Use datasets in Pest to reduce duplication where suitable (e.g., validation tests)
Write or update unit/feature tests for every change and run the affected tests

Files:

  • tests/Unit/ApplicationSettingStaticCastTest.php
database/migrations/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Create descriptive migrations with proper foreign keys, cascading deletes, indexes, and timestamps

database/migrations/**/*.php: When modifying a column in a migration, re-specify all previously defined attributes to avoid dropping them
Use php artisan make: commands (e.g., make:migration) to create new Laravel files; pass --no-interaction and appropriate options

Files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
database/migrations/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Keep database migrations under database/migrations

Files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
database/migrations/*.php

📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)

database/migrations/*.php: Place all Laravel database migrations under database/migrations and follow standard Schema::create/Schema::table structure
Always provide both up() and down() methods in migrations to ensure rollback support
Use incremental, reversible migrations; separate structural changes from data migrations when appropriate
Create appropriate indexes: primary keys, foreign key indexes, composite indexes for common queries, and unique constraints for business rules

Files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.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

Place Livewire components under app/Livewire/ (e.g., Dashboard.php, ActivityMonitor.php, MonacoEditor.php)

Livewire 3 components should use the App\Livewire namespace and lifecycle hooks like mount() and updatedFoo()

Files:

  • app/Livewire/Project/Application/Advanced.php
  • app/Livewire/Project/Application/Previews.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/Livewire/Project/Application/Advanced.php
  • app/Livewire/Project/Application/Previews.php
  • app/Actions/Application/StopApplicationOneServer.php
  • app/Jobs/ApplicationDeploymentJob.php
  • app/Models/ApplicationSetting.php
  • app/Actions/Application/StopApplication.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/Advanced.php
  • app/Livewire/Project/Application/Previews.php
{app/Http/Controllers/**/*.php,app/Livewire/**/*.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Authorize sensitive actions with policies (e.g., $this->authorize('deploy', $model)) before invoking services

Files:

  • app/Livewire/Project/Application/Advanced.php
  • app/Livewire/Project/Application/Previews.php
app/Actions/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Encapsulate business logic using the Action pattern; Action classes live in app/Actions and (via lorisleiva/laravel-actions) may expose handle()/asJob() methods

Files:

  • app/Actions/Application/StopApplicationOneServer.php
  • app/Actions/Application/StopApplication.php
app/Actions/Application/*.php

📄 CodeRabbit inference engine (.cursor/rules/deployment-architecture.mdc)

Place core application deployment logic in app/Actions/Application

Files:

  • app/Actions/Application/StopApplicationOneServer.php
  • app/Actions/Application/StopApplication.php
app/Actions/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Put business logic actions under app/Actions

Files:

  • app/Actions/Application/StopApplicationOneServer.php
  • app/Actions/Application/StopApplication.php
app/Jobs/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Queueable background work should be implemented as Jobs under app/Jobs; Actions may expose asJob to dispatch

app/Jobs/**/*.php: Queue jobs should implement ShouldQueue and define retry strategy (tries, maxExceptions, backoff)
Queue jobs must handle success/failure state updates and broadcast corresponding events; implement failed(Throwable) for final failure handling

Queue jobs should update status, wrap external calls in try/catch, set failure status on exceptions, and rethrow

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/*.php

📄 CodeRabbit inference engine (.cursor/rules/deployment-architecture.mdc)

Implement background deployment, monitoring, backup, and notification work as Laravel Job classes under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement background jobs under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.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 Eloquent ORM and relationships over raw queries or DB:: calls
Prevent N+1 queries by using eager loading when appropriate
When creating new models, also create useful factories and seeders

Files:

  • app/Models/ApplicationSetting.php
app/Models/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Place domain models under app/Models

Files:

  • app/Models/ApplicationSetting.php
app/Models/*.php

📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)

app/Models/*.php: When adding new database columns, update the model's $fillable array to allow mass assignment (e.g., for Model::create() and $model->update())
Use UUID primary keys via HasUuids for distributed systems
Enable soft deletes on models that require audit trails using SoftDeletes
Log model activity using the Spatie LogsActivity trait where auditing is required
Define explicit Eloquent relationships (belongsTo/hasMany/etc.) for navigable associations used by queries and eager loading

Files:

  • app/Models/ApplicationSetting.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: 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/:canResource instead.
For custom interactive components not using x-forms.* (e.g., Alpine.js widgets), wrap with @can('gate', $resource) and provide a disabled/readonly fallback in @else with opacity-50 and cursor-not-allowed styling.

resources/views/**/*.blade.php: Prefer named routes and the route() helper when generating links in views
Dispatch events using $this->dispatch() (not emit/dispatchBrowserEvent) in Livewire 3 templates/components
Do not manually include Alpine.js; Livewire 3 bundles Alpine with required plugins

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
resources/views/livewire/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Livewire components must have a single root element; use wire:key in loops and wire:loading/wire:dirty for UX

Files:

  • resources/views/livewire/project/application/advanced.blade.php
resources/**/*.{blade.php,vue}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Use Tailwind gap utilities for spacing lists instead of margins when listing items

Files:

  • resources/views/livewire/project/application/advanced.blade.php
🧠 Learnings (5)
📚 Learning: 2025-08-27T15:01:58.710Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/development-workflow.mdc:0-0
Timestamp: 2025-08-27T15:01:58.710Z
Learning: Applies to app/Models/**/*.php : Use $casts for attribute casting (e.g., arrays, enums, datetimes) in Eloquent models

Applied to files:

  • tests/Unit/ApplicationSettingStaticCastTest.php
  • app/Models/ApplicationSetting.php
📚 Learning: 2025-10-15T09:16:25.875Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/laravel-boost.mdc:0-0
Timestamp: 2025-10-15T09:16:25.875Z
Learning: Applies to database/migrations/**/*.php : When modifying a column in a migration, re-specify all previously defined attributes to avoid dropping them

Applied to files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
📚 Learning: 2025-08-27T15:01:58.710Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/development-workflow.mdc:0-0
Timestamp: 2025-08-27T15:01:58.710Z
Learning: Applies to database/migrations/**/*.php : Create descriptive migrations with proper foreign keys, cascading deletes, indexes, and timestamps

Applied to files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
📚 Learning: 2025-10-15T09:14:50.127Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/database-patterns.mdc:0-0
Timestamp: 2025-10-15T09:14:50.127Z
Learning: Applies to database/migrations/*.php : Always provide both up() and down() methods in migrations to ensure rollback support

Applied to files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
📚 Learning: 2025-10-15T09:14:50.127Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/database-patterns.mdc:0-0
Timestamp: 2025-10-15T09:14:50.127Z
Learning: Applies to database/migrations/*.php : Use incremental, reversible migrations; separate structural changes from data migrations when appropriate

Applied to files:

  • database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php
🔇 Additional comments (6)
app/Models/ApplicationSetting.php (1)

26-26: LGTM! Proper integer casting applied.

The stop_grace_period field is correctly cast as an integer, following the established pattern in the model and adhering to coding guidelines.

database/migrations/2025_11_05_091558_add_stop_grace_period_to_application_settings.php (1)

14-19: LGTM! Well-structured migration with clear documentation.

The migration properly adds the nullable integer column with a descriptive comment that clearly documents the valid range and default behavior. Both up() and down() methods are present for rollback support.

bootstrap/helpers/constants.php (1)

19-19: LGTM! Well-named constant with appropriate default value.

The constant is properly named, follows existing conventions, and the 30-second default maintains backward compatibility with the previous hardcoded timeout.

app/Jobs/ApplicationDeploymentJob.php (1)

2991-2995: Good dev environment optimization, but verify edge case handling.

The special case for isDev() with a 1-second timeout is a good optimization for development environments. However, the same edge case concern applies here: if stop_grace_period is set to 0 or a negative number in production, it will bypass the fallback to DEFAULT_STOP_GRACE_PERIOD_SECONDS.

Consider adding validation:

 if (isDev()) {
     $timeout = 1;
 } else {
-    $timeout = $this->application->settings->stop_grace_period ?? DEFAULT_STOP_GRACE_PERIOD_SECONDS;
+    $timeout = ($this->application->settings->stop_grace_period > 0)
+        ? $this->application->settings->stop_grace_period
+        : DEFAULT_STOP_GRACE_PERIOD_SECONDS;
 }
app/Actions/Application/StopApplicationOneServer.php (1)

23-23: Consistent implementation, but same edge case concern.

The implementation follows the same pattern as other files in this PR. However, the same validation concern applies: the ?? operator won't catch 0 or negative values. See the comment on app/Livewire/Project/Application/Previews.php for details and suggested fix.

app/Actions/Application/StopApplication.php (1)

39-39: Consistent pattern across all stop actions.

The timeout derivation is consistent with all other files in this PR. The same validation recommendation applies here to handle edge cases where stop_grace_period might be 0 or negative.

Comment on lines +142 to 145
// Load stop_grace_period separately since it has its own save handler
// Convert null to empty string to prevent dirty detection issues
$this->stopGracePeriod = $this->application->settings->stop_grace_period ?? '';
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix type mismatch when loading stop grace period
stop_grace_period is cast to integer on the model, but here you assign it directly to public ?string $stopGracePeriod. When the stored value is, say, 60, Livewire will throw TypeError: Cannot assign int to property App\Livewire\Project\Application\Advanced::$stopGracePeriod of type ?string. Cast the value to string (or change the property type) before assignment, e.g.:

-        $this->stopGracePeriod = $this->application->settings->stop_grace_period ?? '';
+        $value = $this->application->settings->stop_grace_period;
+        $this->stopGracePeriod = $value === null ? '' : (string) $value;
🤖 Prompt for AI Agents
In app/Livewire/Project/Application/Advanced.php around lines 142 to 145,
assigning the integer model field stop_grace_period directly to the property
typed as ?string causes a TypeError in Livewire; convert the loaded value to a
string (e.g. (string)$this->application->settings->stop_grace_period) before
assigning, or alternatively change the property type to allow int, so that the
assignment matches the declared property type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into this. I think it was working without a cast as I could refresh the page with a number configured and it worked.

The reason I use a string here is the user cannot set the current value to null after they have entered any content. So to fix problems with the change indicator incorrectly identifying an empty string as a change from the previous value of null I found it easiest to convert to string and back again after. Happy to adjust if someone has a suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jackman3005, you're right to be concerned about dirty detection, and your approach of converting null'' is sound for that use case. However, the explicit cast is still necessary for type safety.

What's likely happening is that PHP or Livewire is performing implicit type coercion in your current setup, which can work in many cases but isn't guaranteed and can break with strict type checking enabled. Since stop_grace_period is cast to integer on the model, when the value is, say, 60, you're assigning int(60) to a ?string property, which should trigger a TypeError in strict PHP 8+.

The bot's suggestion actually preserves your dirty detection fix while adding the necessary type safety:

$value = $this->application->settings->stop_grace_period;
$this->stopGracePeriod = $value === null ? '' : (string) $value;

This still converts null to '' (preventing dirty detection issues), but also explicitly casts non-null integers to strings (e.g., 60'60'), ensuring the assignment matches your ?string property type. Your saveStopGracePeriod() method already handles the reverse conversion ((int) $this->stopGracePeriod), so the round-trip behavior remains intact.

I'd recommend accepting the bot's suggestion—it maintains your intended behavior while being more robust.

@djsisson
Copy link
Contributor

djsisson commented Nov 6, 2025

one thing to note, docker already has options for stop_grace_period as well as stop_signal

so a different solution would be to instead just add the option to the generated compose in app deployment (or use the user defined values in their own compose) and then remove the timeout altogether from the command line, since docker stop would by default use the value for that container.

since the above pr would use the same setting for all services in a compose based application, but they might need different values, this might not be such a big issue though.

@Jackman3005
Copy link
Author

@djsisson we're using Dockerfile build pack not docker compose. How would you imagine it working for that? I did look into respecting the docker compose stop_grace_period in this set of code changes as well but ran into some problems, maybe I wasn't looking at it correctly. See the section of the PR message with the related issue where I detail the problems I found.

@Jackman3005
Copy link
Author

Jackman3005 commented Nov 6, 2025

@djsisson ahh maybe I understand what you're saying a bit. By removing --time from docker stop it should respect the configured stop_grace_period set from the compose file it was deployed from. That might just solve it for docker compose applications. I didn't look deeper into it but it sounds like you're saying that a Dockerfile build pack creates a docker compose file under the hood and uses that for deployment. So if that's the case I could use this new option to configure the stop_grace_period in the generated docker-compose.yml used under the hood.

Pardon me if I misunderstood I don't have the code in front of me.

If so, I guess I'd need to check the different application types to make sure each get a chance to set the grace period either through the UI or via the user-supplied docker-compose.yml... likely hiding the UI option for Docker compose apps.

@djsisson
Copy link
Contributor

djsisson commented Nov 6, 2025

the compose is generated here:

$docker_compose = [

also i assume you would surround the view with @if ($application->build_pack !== 'dockercompose') to hide it from compose based apps, since they can set it themselves if a different value is needed

it might be worth adding stop_signal as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠️ Feature Issues requesting a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants