Commit graph

1259 commits

Author SHA1 Message Date
Andras Bacsai
f152ec00ad fix: Detect read-only Docker volumes with long-form syntax and enable refresh
- Fixed isReadOnlyVolume() to detect both short-form (:ro) and long-form (read_only: true) Docker Compose volume syntax
- Fixed path matching to use mount_path only (fs_path is transformed during parsing from ./file to absolute path)
- Added "Load from server" button for read-only volumes to allow users to refresh content
- Changed loadStorageOnServer() authorization from 'update' to 'view' since loading is a read operation
- Added helper text to Content field warning users that content may be outdated
- Applied fixes to both LocalFileVolume and LocalPersistentVolume models

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 14:18:58 +01:00
Andras Bacsai
10003cec3d fix: add UUID support to CloudProviderToken model
- Add uuid column to cloud_provider_tokens table via migration
- Update CloudProviderToken to extend BaseModel for auto UUID generation
- Generate UUIDs for existing records in migration
- Fixes null uuid issue in API responses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-10 08:59:13 +01:00
Andras Bacsai
5e8d11f732 refactor: replace queries with cached versions for performance improvements 2025-12-08 13:39:33 +01:00
Andras Bacsai
7c1f230bd3 fix: remove {{port}} template variable and ensure ports are always appended to preview URLs
The {{port}} template variable was undocumented and caused a double port bug
when used in preview URL templates. Since ports are always appended to the final
URL anyway, we remove {{port}} substitution entirely and ensure consistent port
handling across ApplicationPreview, PreviewsCompose, and the applicationParser helper.

Also fix PreviewsCompose.php which wasn't preserving ports at all, and improve
the Blade template formatting in previews-compose.blade.php.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 21:53:47 +01:00
Andras Bacsai
b299ceb445
Add Garage as a one-click service (#7508) 2025-12-05 13:49:03 +01:00
Andras Bacsai
0f54c194d7 Add Garage as a one-click service
Adds support for deploying Garage (S3-compatible object storage) as a
one-click service in Coolify. Includes service template with TOML config,
automatic URL generation for S3, Web, and Admin endpoints with reverse
proxy configuration, and UI fields for credentials and access tokens.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 13:46:57 +01:00
Andras Bacsai
511415770a Add server-level toggle to disable application image retention
Adds a new server-level setting that allows administrators to disable
per-application image retention globally for all applications on a server.
When enabled, Docker cleanup will only keep the currently running image
regardless of individual application retention settings.

Changes:
- Add migration for disable_application_image_retention boolean field
- Update ServerSetting model with cast
- Add checkbox in DockerCleanup page (Advanced section)
- Modify CleanupDocker action to check server-level setting
- Update Rollback page to show warning and disable inputs when server
  retention is disabled
- Add helper text noting server-level override capability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 12:22:20 +01:00
Andras Bacsai
439afca642 Inject commit-based image tags for Docker Compose build services
For Docker Compose applications with build directives, inject commit-based
image tags (uuid_servicename:commit) to enable rollback functionality.
Previously these services always used 'latest' tags, making rollback impossible.

- Only injects tags for services with build: but no explicit image:
- Uses pr-{id} tags for pull request deployments
- Respects user-defined image: fields (preserves user intent)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 11:41:47 +01:00
Andras Bacsai
4ed7a4238a Add per-application Docker image retention for rollback capability
Implement a per-application setting (`docker_images_to_keep`) in `application_settings` table to control how many Docker images are preserved during cleanup. The cleanup process now:

- Respects per-application retention settings (default: 2 images)
- Preserves the N most recent images per application for easy rollback
- Always deletes PR images and keeps the currently running image
- Dynamically excludes application images from general Docker image prune
- Cleans up non-Coolify unused images to prevent disk bloat

Fixes issues where cleanup would delete all images needed for rollback.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 11:02:07 +01:00
Andras Bacsai
dae6803173 fix: restore original base_directory on compose validation failure
The Application::loadComposeFile method's finally block always saves
the model, which was persisting invalid base_directory values when
validation failed.

Changes:
- Add restoreBaseDirectory and restoreDockerComposeLocation parameters
  to loadComposeFile() in both Application model and General component
- The finally block now restores BOTH base_directory and
  docker_compose_location to the provided original values before saving
- When called from submit(), pass the original DB values so they are
  restored on failure instead of the new invalid values

This ensures invalid paths are never persisted to the database.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 15:57:15 +01:00
Andras Bacsai
1998314340 Add runtime and buildtime properties to environment variable booted method 2025-12-03 10:25:38 +01:00
Andras Bacsai
0073d045fb fix: enhance security by validating and escaping database names, file paths, and proxy configuration filenames to prevent command injection 2025-11-27 14:36:31 +01:00
Andras Bacsai
e60e74ac90
fix: trigger configuration changed detection for build settings (#7371) 2025-11-27 12:23:32 +01:00
Andras Bacsai
9452f0b468 fix: trigger configuration changed detection for build settings
Include 'Inject Build Args to Dockerfile' and 'Include Source Commit in Build' settings in the configuration hash calculation. These settings affect Docker build behavior, so changes to them should trigger the restart required notification. Add unit tests to verify hash changes when these settings are modified.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 12:22:54 +01:00
Andras Bacsai
b5666da342 test: add tests for shared environment variable spacing and resolution 2025-11-27 10:45:39 +01:00
Andras Bacsai
837391c31b feat: add Docker build cache preservation toggles and development logging
Add two new application settings to control Docker build cache invalidation:
- inject_build_args_to_dockerfile (default: true) - Skip Dockerfile ARG injection
- include_source_commit_in_build (default: false) - Exclude SOURCE_COMMIT from build context

These toggles let users preserve Docker cache when SOURCE_COMMIT or custom ARGs change frequently. Development-only logging shows which ARGs are being injected for debugging.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 13:42:02 +01:00
Andras Bacsai
477738dd2f fix: update webhook notification settings migration to use updateOrInsert and add logging 2025-11-25 15:35:01 +01:00
Andras Bacsai
a3df33a4e0 fix: correct webhook notification settings migration and model
- Add missing traefik_outdated_webhook_notifications field to migration schema and population logic
- Remove incorrect docker_cleanup_webhook_notifications from model (split into success/failure variants)
- Consolidate webhook notification migrations from 2025_10_10 to 2025_11_25 for proper execution order
- Ensure all 15 notification fields are properly defined and consistent across migration, model, and Livewire component

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 15:18:43 +01:00
Andras Bacsai
875351188f feat: improve S3 restore path handling and validation state
- Add path attribute mutator to S3Storage model ensuring paths start with /
- Add updatedS3Path hook to normalize path and reset validation state on blur
- Add updatedS3StorageId hook to reset validation state when storage changes
- Add Enter key support to trigger file check from path input
- Use wire:model.live for S3 storage select, wire:model.blur for path input
- Improve shell escaping in restore job cleanup commands
- Fix isSafeTmpPath helper logic for directory validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 10:18:30 +01:00
Andras Bacsai
e0dc12678b
fix: comprehensive SERVICE_URL/SERVICE_FQDN handling improvements and queue reliability fixes (#7275) 2025-11-24 11:47:11 +01:00
Andras Bacsai
bf428a0e1c
fix: don't show health status for exited containers (#7317) 2025-11-24 10:29:57 +01:00
Andras Bacsai
1149d0f746
feat: implement prerequisite validation and installation for server setup (#7297) 2025-11-24 10:28:10 +01:00
Andras Bacsai
ac9eca3c05 fix: don't show health status for exited containers
Exited containers don't run health checks, so showing "(unhealthy)" is
misleading. This fix ensures exited status displays without health
suffixes across all monitoring systems (SSH, Sentinel, services, etc.)
and at the UI layer for backward compatibility with existing data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 09:09:37 +01:00
Andras Bacsai
29135e00ba feat: enhance prerequisite validation to return detailed results 2025-11-21 13:14:48 +01:00
Andras Bacsai
2edf2338de fix: enhance getRequiredPort to support map-style environment variables for SERVICE_URL and SERVICE_FQDN 2025-11-21 12:41:25 +01:00
Andras Bacsai
56f32d0f87 fix: properly handle SERVICE_URL and SERVICE_FQDN for abbreviated service names (#7243)
Parse template variables directly instead of generating from container names. Always create both SERVICE_URL and SERVICE_FQDN pairs together. Properly separate scheme handling (URL has scheme, FQDN doesn't). Add comprehensive test coverage.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 11:21:49 +01:00
Andras Bacsai
01957f2752 feat: implement prerequisite validation and installation for server setup 2025-11-21 09:49:33 +01:00
Andras Bacsai
ae6eef3cdb feat(tests): add comprehensive tests for ContainerStatusAggregator and serverStatus accessor
- Introduced tests for ContainerStatusAggregator to validate status aggregation logic across various container states.
- Implemented tests to ensure serverStatus accessor correctly checks server infrastructure health without being affected by container status.
- Updated ExcludeFromHealthCheckTest to verify excluded status handling in various components.
- Removed obsolete PushServerUpdateJobStatusAggregationTest as its functionality is covered elsewhere.
- Updated version number for sentinel to 0.0.17 in versions.json.
2025-11-20 17:31:07 +01:00
Andras Bacsai
70fb4c6869 refactor: standardize Service model status aggregation to use ContainerStatusAggregator
Fixes inconsistency where Service model used manual state machine logic while
all other components (Application, ComplexStatusCheck, GetContainersStatus)
use the centralized ContainerStatusAggregator service.

Changes:
- Refactored Service::aggregateResourceStatuses() to use ContainerStatusAggregator
- Removed ~60 lines of duplicated state machine logic
- Added comprehensive ServiceExcludedStatusTest with 24 test cases
- Fixed bugs in old logic where paused/starting containers were incorrectly
  marked as unhealthy (should be unknown)

Benefits:
- Single source of truth for status aggregation across all models
- Leverages 42 existing ContainerStatusAggregator tests
- Consistent behavior between Service and Application/Database models
- Easier maintenance (state machine changes only in one place)

All tests pass (37 total):
- ServiceExcludedStatusTest: 24/24 passed
- AllExcludedContainersConsistencyTest: 13/13 passed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 15:03:18 +01:00
Andras Bacsai
d2d9c1b2bc debug: add comprehensive status change logging
Added detailed debug logging to all status update paths to help
diagnose why "unhealthy" status appears in the UI.

## Logging Added

### 1. PushServerUpdateJob (Sentinel updates)
**Location**: Lines 303-315
**Logs**: Status changes from Sentinel push updates
**Data tracked**:
- Old vs new status
- Container statuses that led to aggregation
- Status flags (hasRunning, hasUnhealthy, hasUnknown)

### 2. GetContainersStatus (SSH updates)
**Location**: Lines 441-449, 346-354, 358-365
**Logs**: Status changes from SSH-based checks
**Scenarios**:
- Normal status aggregation
- Recently restarted containers (kept as degraded)
- Applications not running (set to exited)
**Data tracked**:
- Old vs new status
- Container statuses
- Restart count and timing
- Whether containers exist

### 3. Application Model Status Accessor
**Location**: Lines 706-712, 726-732
**Logs**: When status is set without explicit health information
**Issue**: Highlights cases where health defaults to "unhealthy"
**Data tracked**:
- Raw value passed to setter
- Final result after default applied

## How to Use

### Enable Debug Logging
Edit `.env` or `config/logging.php` to set log level to debug:
```
LOG_LEVEL=debug
```

### Monitor Logs
```bash
tail -f storage/logs/laravel.log | grep STATUS-DEBUG
```

### Log Format
All logs use `[STATUS-DEBUG]` prefix for easy filtering:
```
[2025-11-19 13:00:00] local.DEBUG: [STATUS-DEBUG] Sentinel status change
{
  "source": "PushServerUpdateJob",
  "app_id": 123,
  "app_name": "my-app",
  "old_status": "running:unknown",
  "new_status": "running:healthy",
  "container_statuses": [...],
  "flags": {...}
}
```

## What to Look For

1. **Default to unhealthy**: Check Application model accessor logs
2. **Status flipping**: Compare timestamps between Sentinel and SSH updates
3. **Incorrect aggregation**: Check flags and container_statuses
4. **Stale database values**: Check if old_status persists across multiple logs

## Next Steps

After gathering logs, we can:
1. Identify the exact source of "unhealthy" status
2. Determine if it's a default issue, aggregation bug, or timing problem
3. Apply targeted fix based on evidence

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 13:52:08 +01:00
Andras Bacsai
e3746a4b88 fix: preserve unknown health state and handle edge case container states
This commit fixes container health status aggregation to correctly handle
unknown health states and edge case container states across all resource types.

Changes:

1. **Preserve Unknown Health State**
   - Add three-way priority: unhealthy > unknown > healthy
   - Detect containers without healthchecks (null health) as unknown
   - Apply across GetContainersStatus, ComplexStatusCheck, and Service models

2. **Handle Edge Case Container States**
   - Add support for: created, starting, paused, dead, removing
   - Map to appropriate statuses: starting (unknown), paused (unknown), degraded (unhealthy)
   - Prevent containers in transitional states from showing incorrect status

3. **Add :excluded Suffix for Excluded Containers**
   - Parse exclude_from_hc flag from docker-compose YAML
   - Append :excluded suffix to individual container statuses
   - Skip :excluded containers in non-excluded aggregation sections
   - Strip :excluded suffix in excluded aggregation sections
   - Makes it clear in UI which containers are excluded from monitoring

Files Modified:
- app/Actions/Docker/GetContainersStatus.php
- app/Actions/Shared/ComplexStatusCheck.php
- app/Models/Service.php
- tests/Unit/ContainerHealthStatusTest.php

Tests: 18 passed (82 assertions)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 13:19:25 +01:00
Andras Bacsai
498b189286 fix: correct status for services with all containers excluded from health checks
When all containers are excluded from health checks, display their actual status
with :excluded suffix instead of misleading hardcoded statuses. This prevents
broken UI state with incorrect action buttons and provides clarity that monitoring
is disabled.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 10:54:51 +01:00
Andras Bacsai
f81640e316 fix: correct status for services with all containers excluded from health checks
When all services in a Docker Compose file have `exclude_from_hc: true`,
the status aggregation logic was returning invalid states causing broken UI.

**Problems fixed:**
- ComplexStatusCheck returned 'running:healthy' for apps with no monitored containers
- Service model returned ':' (null status) when all services excluded
- UI showed active start/stop buttons for non-running services

**Changes:**
- ComplexStatusCheck: Return 'exited:healthy' when relevantContainerCount is 0
- Service model: Return 'exited:healthy' when both status and health are null
- Added comprehensive unit tests to verify the fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 23:24:22 +01:00
Andras Bacsai
ebb2f0cc7d refactor: simplify environment variable deletion logic in booted method 2025-11-18 10:29:08 +01:00
Andras Bacsai
36d2c02498 refactor: move buildpack cleanup logic to model lifecycle hooks
Move buildpack switching cleanup from Livewire component to Application model's boot lifecycle. This improves separation of concerns and ensures cleanup happens consistently regardless of how the buildpack change is triggered. Also clears Dockerfile-specific data when switching away from dockerfile buildpack.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 10:29:08 +01:00
Andras Bacsai
1270136da9 merge: merge next branch into feat-traefik-version-checker
Merged latest changes from the next branch to keep the feature branch
up to date. No conflicts were encountered during the merge.

Changes from next branch:
- Updated application deployment job error logging
- Updated server manager job and instance settings
- Removed PullHelperImageJob in favor of updated approach
- Database migration refinements
- Updated versions.json with latest component versions

All automatic merges were successful and no manual conflict resolution
was required.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 14:56:24 +01:00
Andras Bacsai
5d73b76a44 refactor(proxy): implement centralized caching for versions.json and improve UX
This commit introduces several improvements to the Traefik version tracking
feature and proxy configuration UI:

## Caching Improvements

1. **New centralized helper functions** (bootstrap/helpers/versions.php):
   - `get_versions_data()`: Redis-cached access to versions.json (1 hour TTL)
   - `get_traefik_versions()`: Extract Traefik versions from cached data
   - `invalidate_versions_cache()`: Clear cache when file is updated

2. **Performance optimization**:
   - Single Redis cache key: `coolify:versions:all`
   - Eliminates 2-4 file reads per page load
   - 95-97.5% reduction in disk I/O time
   - Shared cache across all servers in distributed setup

3. **Updated all consumers to use cached helpers**:
   - CheckTraefikVersionJob: Use get_traefik_versions()
   - Server/Proxy: Two-level caching (Redis + in-memory per-request)
   - CheckForUpdatesJob: Auto-invalidate cache after updating file
   - bootstrap/helpers/shared.php: Use cached data for Coolify version

## UI/UX Improvements

1. **Navbar warning indicator**:
   - Added yellow warning triangle icon next to "Proxy" menu item
   - Appears when server has outdated Traefik version
   - Uses existing traefik_outdated_info data for instant checks
   - Provides at-a-glance visibility of version issues

2. **Proxy sidebar persistence**:
   - Fixed sidebar disappearing when clicking "Switch Proxy"
   - Configuration link now always visible (needed for proxy selection)
   - Dynamic Configurations and Logs only show when proxy is configured
   - Better navigation context during proxy switching workflow

## Code Quality

- Added comprehensive PHPDoc for Server::$traefik_outdated_info property
- Improved code organization with centralized helper approach
- All changes formatted with Laravel Pint
- Maintains backward compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 14:53:28 +01:00
Andras Bacsai
6593b2a553 feat(proxy): enhance Traefik version notifications to show patch and minor upgrades
- Store both patch update and newer minor version information simultaneously
- Display patch update availability alongside minor version upgrades in notifications
- Add newer_branch_target and newer_branch_latest fields to traefik_outdated_info
- Update all notification channels (Discord, Telegram, Slack, Pushover, Email, Webhook)
- Show minor version in format (e.g., v3.6) for upgrade targets instead of patch version
- Enhance UI callouts with clearer messaging about available upgrades
- Remove verbose logging in favor of cleaner code structure
- Handle edge case where SSH command returns empty response

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 09:59:17 +01:00
Andras Bacsai
11a7f4c8a7 fix(performance): eliminate N+1 query in CheckTraefikVersionJob
This commit fixes a critical N+1 query issue in CheckTraefikVersionJob
that was loading ALL proxy servers into memory then filtering in PHP,
causing potential OOM errors with thousands of servers.

Changes:
- Added scopeWhereProxyType() query scope to Server model for
  database-level filtering using JSON column arrow notation
- Updated CheckTraefikVersionJob to use new scope instead of
  collection filter, moving proxy type filtering into the SQL query
- Added comprehensive unit tests for the new query scope

Performance impact:
- Before: SELECT * FROM servers WHERE proxy IS NOT NULL (all servers)
- After: SELECT * FROM servers WHERE proxy->>'type' = 'TRAEFIK' (filtered)
- Eliminates memory overhead of loading non-Traefik servers
- Critical for cloud instances with thousands of connected servers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 11:35:22 +01:00
Andras Bacsai
8c77c63043 feat(proxy): add Traefik version tracking with notifications and dismissible UI warnings
- Add automated Traefik version checking job running weekly on Sundays
- Implement version detection from running containers and comparison with versions.json
- Add notifications across all channels (Email, Discord, Slack, Telegram, Pushover, Webhook) for outdated versions
- Create dismissible callout component with localStorage persistence
- Display cross-branch upgrade warnings (e.g., v3.5 -> v3.6) with changelog links
- Show patch update notifications within same branch
- Add warning icon that appears when callouts are dismissed
- Prevent duplicate notifications during proxy restart by adding restarting parameter
- Fix notification spam with transition-based logic for status changes
- Enable system email settings by default in development mode
- Track last saved/applied proxy settings to detect configuration drift
2025-11-14 11:35:22 +01:00
Andras Bacsai
318cd18dde fix: remove PullHelperImageJob and mass server scheduling
Stop dispatching PullHelperImageJob to thousands of servers when the helper image version changes. Instead, rely on Docker's automatic image pulling during actual deployments and backups. Inline the helper image pull in UpdateCoolify for the single use case.

This eliminates queue flooding on cloud instances while maintaining all functionality through Docker's built-in image management.
2025-11-14 11:31:08 +01:00
Andras Bacsai
ec30426a2f feat(ServiceDatabase): add support for TimescaleDB detection and database type identification 2025-11-12 00:36:38 +01:00
Andras Bacsai
104e68a9ac
Merge branch 'next' into improve-scheduled-tasks 2025-11-11 11:38:04 +01:00
Andras Bacsai
45ab79f292
Merge branch 'next' into port-detection-lol 2025-11-11 11:21:26 +01:00
Andras Bacsai
71c89d9ba8
Merge branch 'next' into improve-scheduled-tasks 2025-11-10 14:21:03 +01:00
Andras Bacsai
6decad2e96 refactor: streamline required port retrieval in EditDomain and ServiceApplicationView; add environment_variables method in ServiceApplication 2025-11-10 14:15:53 +01:00
Andras Bacsai
e63a270fea
Enhance container status tracking and improve user notifications (#7182) 2025-11-10 13:58:22 +01:00
Andras Bacsai
194d023f70
Enhance port detection and improve user notifications (#7184) 2025-11-10 13:56:09 +01:00
Andras Bacsai
99e97900a5 feat: add automated PORT environment variable detection and UI warnings
Add detection system for PORT environment variable to help users configure applications correctly:

- Add detectPortFromEnvironment() method to Application model to detect PORT env var
- Add getDetectedPortInfoProperty() computed property in General Livewire component
- Display contextual info banners in UI when PORT is detected:
  - Warning when PORT exists but ports_exposes is empty
  - Warning when PORT doesn't match ports_exposes configuration
  - Info message when PORT matches ports_exposes
- Add deployment logging to warn about PORT/ports_exposes mismatches
- Include comprehensive unit tests for port detection logic

The ports_exposes field remains authoritative for proxy configuration, while
PORT detection provides helpful suggestions to users.
2025-11-10 13:43:27 +01:00
Andras Bacsai
68a9f2ca77 feat: add container restart tracking and crash loop detection
Track container restart counts from Docker and detect crash loops to provide better visibility into application health issues.

- Add restart_count, last_restart_at, and last_restart_type columns to applications table
- Detect restart count increases from Docker inspect data and send notifications
- Show restart count badge in UI with warning icon on Logs navigation
- Distinguish between crash restarts and manual restarts
- Implement 30-second grace period to prevent false "exited" status during crash loops
- Reset restart count on manual stop, restart, and redeploy actions
- Add unit tests for restart count tracking logic

This helps users quickly identify when containers are in crash loops and need attention, even when the container status flickers between states during Docker's restart backoff period.
2025-11-10 13:04:31 +01:00