# Coolify Application Architecture

## Laravel Project Structure

### **Core Application Directory** ([app/](mdc:app))

```
app/
├── Actions/           # Business logic actions (Action pattern)
├── Console/           # Artisan commands
├── Contracts/         # Interface definitions
├── Data/              # Data Transfer Objects (Spatie Laravel Data)
├── Enums/             # Enumeration classes
├── Events/            # Event classes
├── Exceptions/        # Custom exception classes
├── Helpers/           # Utility helper classes
├── Http/              # HTTP layer (Controllers, Middleware, Requests)
├── Jobs/              # Background job classes
├── Listeners/         # Event listeners
├── Livewire/          # Livewire components (Frontend)
├── Models/            # Eloquent models (Domain entities)
├── Notifications/     # Notification classes
├── Policies/          # Authorization policies
├── Providers/         # Service providers
├── Repositories/      # Repository pattern implementations
├── Services/          # Service layer classes
├── Traits/            # Reusable trait classes
└── View/              # View composers and creators
```

## Core Domain Models

### **Infrastructure Management**

#### **[Server.php](mdc:app/Models/Server.php)** (46KB, 1343 lines)
- **Purpose**: Physical/virtual server management
- **Key Relationships**:
  - `hasMany(Application::class)` - Deployed applications
  - `hasMany(StandalonePostgresql::class)` - Database instances
  - `belongsTo(Team::class)` - Team ownership
- **Key Features**:
  - SSH connection management
  - Resource monitoring
  - Proxy configuration (Traefik/Caddy)
  - Docker daemon interaction

#### **[Application.php](mdc:app/Models/Application.php)** (74KB, 1734 lines)
- **Purpose**: Application deployment and management
- **Key Relationships**:
  - `belongsTo(Server::class)` - Deployment target
  - `belongsTo(Environment::class)` - Environment context
  - `hasMany(ApplicationDeploymentQueue::class)` - Deployment history
- **Key Features**:
  - Git repository integration
  - Docker build and deployment
  - Environment variable management
  - SSL certificate handling

#### **[Service.php](mdc:app/Models/Service.php)** (58KB, 1325 lines)
- **Purpose**: Multi-container service orchestration
- **Key Relationships**:
  - `hasMany(ServiceApplication::class)` - Service components
  - `hasMany(ServiceDatabase::class)` - Service databases
  - `belongsTo(Environment::class)` - Environment context
- **Key Features**:
  - Docker Compose generation
  - Service dependency management
  - Health check configuration

### **Team & Project Organization**

#### **[Team.php](mdc:app/Models/Team.php)** (8.9KB, 308 lines)
- **Purpose**: Multi-tenant team management
- **Key Relationships**:
  - `hasMany(User::class)` - Team members
  - `hasMany(Project::class)` - Team projects
  - `hasMany(Server::class)` - Team servers
- **Key Features**:
  - Resource limits and quotas
  - Team-based access control
  - Subscription management

#### **[Project.php](mdc:app/Models/Project.php)** (4.3KB, 156 lines)
- **Purpose**: Project organization and grouping
- **Key Relationships**:
  - `hasMany(Environment::class)` - Project environments
  - `belongsTo(Team::class)` - Team ownership
- **Key Features**:
  - Environment isolation
  - Resource organization

#### **[Environment.php](mdc:app/Models/Environment.php)**
- **Purpose**: Environment-specific configuration
- **Key Relationships**:
  - `hasMany(Application::class)` - Environment applications
  - `hasMany(Service::class)` - Environment services
  - `belongsTo(Project::class)` - Project context

### **Database Management Models**

#### **Standalone Database Models**
- **[StandalonePostgresql.php](mdc:app/Models/StandalonePostgresql.php)** (11KB, 351 lines)
- **[StandaloneMysql.php](mdc:app/Models/StandaloneMysql.php)** (11KB, 351 lines)
- **[StandaloneMariadb.php](mdc:app/Models/StandaloneMariadb.php)** (10KB, 337 lines)
- **[StandaloneMongodb.php](mdc:app/Models/StandaloneMongodb.php)** (12KB, 370 lines)
- **[StandaloneRedis.php](mdc:app/Models/StandaloneRedis.php)** (12KB, 394 lines)
- **[StandaloneKeydb.php](mdc:app/Models/StandaloneKeydb.php)** (11KB, 347 lines)
- **[StandaloneDragonfly.php](mdc:app/Models/StandaloneDragonfly.php)** (11KB, 347 lines)
- **[StandaloneClickhouse.php](mdc:app/Models/StandaloneClickhouse.php)** (10KB, 336 lines)

**Common Features**:
- Database configuration management
- Backup scheduling and execution
- Connection string generation
- Health monitoring

### **Configuration & Settings**

#### **[EnvironmentVariable.php](mdc:app/Models/EnvironmentVariable.php)** (7.6KB, 219 lines)
- **Purpose**: Application environment variable management
- **Key Features**:
  - Encrypted value storage
  - Build-time vs runtime variables
  - Shared variable inheritance

#### **[InstanceSettings.php](mdc:app/Models/InstanceSettings.php)** (3.2KB, 124 lines)
- **Purpose**: Global Coolify instance configuration
- **Key Features**:
  - FQDN and port configuration
  - Auto-update settings
  - Security configurations

## Architectural Patterns

### **Action Pattern** ([app/Actions/](mdc:app/Actions))

Using [lorisleiva/laravel-actions](mdc:composer.json) for business logic encapsulation:

```php
// Example Action structure
class DeployApplication extends Action
{
    public function handle(Application $application): void
    {
        // Business logic for deployment
    }
    
    public function asJob(Application $application): void
    {
        // Queue job implementation
    }
}
```

**Key Action Categories**:
- **Application/**: Deployment and management actions
- **Database/**: Database operations
- **Server/**: Server management actions
- **Service/**: Service orchestration actions

### **Repository Pattern** ([app/Repositories/](mdc:app/Repositories))

Data access abstraction layer:
- Encapsulates database queries
- Provides testable data layer
- Abstracts complex query logic

### **Service Layer** ([app/Services/](mdc:app/Services))

Business logic services:
- External API integrations
- Complex business operations
- Cross-cutting concerns

## Data Flow Architecture

### **Request Lifecycle**

1. **HTTP Request** → [routes/web.php](mdc:routes/web.php)
2. **Middleware** → Authentication, authorization
3. **Livewire Component** → [app/Livewire/](mdc:app/Livewire)
4. **Action/Service** → Business logic execution
5. **Model/Repository** → Data persistence
6. **Response** → Livewire reactive update

### **Background Processing**

1. **Job Dispatch** → Queue system (Redis)
2. **Job Processing** → [app/Jobs/](mdc:app/Jobs)
3. **Action Execution** → Business logic
4. **Event Broadcasting** → Real-time updates
5. **Notification** → User feedback

## Security Architecture

### **Multi-Tenant Isolation**

```php
// Team-based query scoping
class Application extends Model
{
    public function scopeOwnedByCurrentTeam($query)
    {
        return $query->whereHas('environment.project.team', function ($q) {
            $q->where('id', currentTeam()->id);
        });
    }
}
```

### **Authorization Layers**

1. **Team Membership** → User belongs to team
2. **Resource Ownership** → Resource belongs to team
3. **Policy Authorization** → [app/Policies/](mdc:app/Policies)
4. **Environment Isolation** → Project/environment boundaries

### **Data Protection**

- **Environment Variables**: Encrypted at rest
- **SSH Keys**: Secure storage and transmission
- **API Tokens**: Sanctum-based authentication
- **Audit Logging**: [spatie/laravel-activitylog](mdc:composer.json)

## Configuration Hierarchy

### **Global Configuration**
- **[InstanceSettings](mdc:app/Models/InstanceSettings.php)**: System-wide settings
- **[config/](mdc:config)**: Laravel configuration files

### **Team Configuration**
- **[Team](mdc:app/Models/Team.php)**: Team-specific settings
- **[ServerSetting](mdc:app/Models/ServerSetting.php)**: Server configurations

### **Project Configuration**
- **[ProjectSetting](mdc:app/Models/ProjectSetting.php)**: Project settings
- **[Environment](mdc:app/Models/Environment.php)**: Environment variables

### **Application Configuration**
- **[ApplicationSetting](mdc:app/Models/ApplicationSetting.php)**: App-specific settings
- **[EnvironmentVariable](mdc:app/Models/EnvironmentVariable.php)**: Runtime configuration

## Event-Driven Architecture

### **Event Broadcasting** ([app/Events/](mdc:app/Events))

Real-time updates using Laravel Echo and WebSockets:

```php
// Example event structure
class ApplicationDeploymentStarted implements ShouldBroadcast
{
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("team.{$this->application->team->id}"),
        ];
    }
}
```

### **Event Listeners** ([app/Listeners/](mdc:app/Listeners))

- Deployment status updates
- Resource monitoring alerts
- Notification dispatching
- Audit log creation

## Database Design Patterns

### **Polymorphic Relationships**

```php
// Environment variables can belong to multiple resource types
class EnvironmentVariable extends Model
{
    public function resource(): MorphTo
    {
        return $this->morphTo();
    }
}
```

### **Team-Based Soft Scoping**

All major resources include team-based query scoping with request-level caching:

```php
// ✅ CORRECT - Use cached methods (request-level cache via once())
$applications = Application::ownedByCurrentTeamCached();
$servers = Server::ownedByCurrentTeamCached();

// ✅ CORRECT - Filter cached collection in memory
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);

// Only use query builder when you need eager loading or fresh data
$projects = Project::ownedByCurrentTeam()->with('environments')->get();
```

See [Database Patterns](.ai/patterns/database-patterns.md#request-level-caching-with-ownedbycurrentteamcached) for full documentation.

### **Configuration Inheritance**

Environment variables cascade from:
1. **Shared Variables** → Team-wide defaults
2. **Project Variables** → Project-specific overrides
3. **Application Variables** → Application-specific values

## Integration Patterns

### **Git Provider Integration**

Abstracted git operations supporting:
- **GitHub**: [app/Models/GithubApp.php](mdc:app/Models/GithubApp.php)
- **GitLab**: [app/Models/GitlabApp.php](mdc:app/Models/GitlabApp.php)
- **Bitbucket**: Webhook integration
- **Gitea**: Self-hosted Git support

### **Docker Integration**

- **Container Management**: Direct Docker API communication
- **Image Building**: Dockerfile and Buildpack support
- **Network Management**: Custom Docker networks
- **Volume Management**: Persistent storage handling

### **SSH Communication**

- **[phpseclib/phpseclib](mdc:composer.json)**: Secure SSH connections
- **Multiplexing**: Connection pooling for efficiency
- **Key Management**: [PrivateKey](mdc:app/Models/PrivateKey.php) model

## Testing Architecture

### **Test Structure** ([tests/](mdc:tests))

```
tests/
├── Feature/           # Integration tests
├── Unit/              # Unit tests
├── Browser/           # Dusk browser tests
├── Traits/            # Test helper traits
├── Pest.php           # Pest configuration
└── TestCase.php       # Base test case
```

### **Testing Patterns**

- **Feature Tests**: Full request lifecycle testing
- **Unit Tests**: Individual class/method testing
- **Browser Tests**: End-to-end user workflows
- **Database Testing**: Factories and seeders

## Performance Considerations

### **Query Optimization**

- **Eager Loading**: Prevent N+1 queries
- **Query Scoping**: Team-based filtering
- **Database Indexing**: Optimized for common queries

### **Caching Strategy**

- **Redis**: Session and cache storage
- **Model Caching**: Frequently accessed data
- **Query Caching**: Expensive query results

### **Background Processing**

- **Queue Workers**: Horizon-managed job processing
- **Job Batching**: Related job grouping
- **Failed Job Handling**: Automatic retry logic

## Container Status Monitoring System

### **Overview**

Container health status is monitored and updated through **multiple independent paths**. When modifying status logic, **ALL paths must be updated** to ensure consistency.

### **Critical Implementation Locations**

#### **1. SSH-Based Status Updates (Scheduled)**
**File**: [app/Actions/Docker/GetContainersStatus.php](mdc:app/Actions/Docker/GetContainersStatus.php)
**Method**: `aggregateApplicationStatus()` (lines 487-540)
**Trigger**: Scheduled job or manual refresh
**Frequency**: Every minute (via `ServerCheckJob`)

**Status Aggregation Logic**:
```php
// Tracks multiple status flags
$hasRunning = false;
$hasRestarting = false;
$hasUnhealthy = false;
$hasUnknown = false;  // ⚠️ CRITICAL: Must track unknown
$hasExited = false;
// ... more states

// Priority: restarting > degraded > running (unhealthy > unknown > healthy)
if ($hasRunning) {
    if ($hasUnhealthy) return 'running (unhealthy)';
    elseif ($hasUnknown) return 'running (unknown)';
    else return 'running (healthy)';
}
```

#### **2. Sentinel-Based Status Updates (Real-time)**
**File**: [app/Jobs/PushServerUpdateJob.php](mdc:app/Jobs/PushServerUpdateJob.php)
**Method**: `aggregateMultiContainerStatuses()` (lines 269-298)
**Trigger**: Sentinel push updates from remote servers
**Frequency**: Every ~30 seconds (real-time)

**Status Aggregation Logic**:
```php
// ⚠️ MUST match GetContainersStatus logic
$hasRunning = false;
$hasUnhealthy = false;
$hasUnknown = false;  // ⚠️ CRITICAL: Added to fix bug

foreach ($relevantStatuses as $status) {
    if (str($status)->contains('running')) {
        $hasRunning = true;
        if (str($status)->contains('unhealthy')) $hasUnhealthy = true;
        if (str($status)->contains('unknown')) $hasUnknown = true;  // ⚠️ CRITICAL
    }
}

// Priority: unhealthy > unknown > healthy
if ($hasRunning) {
    if ($hasUnhealthy) $aggregatedStatus = 'running (unhealthy)';
    elseif ($hasUnknown) $aggregatedStatus = 'running (unknown)';
    else $aggregatedStatus = 'running (healthy)';
}
```

#### **3. Multi-Server Status Aggregation**
**File**: [app/Actions/Shared/ComplexStatusCheck.php](mdc:app/Actions/Shared/ComplexStatusCheck.php)
**Method**: `resource()` (lines 48-210)
**Purpose**: Aggregates status across multiple servers for applications
**Used by**: Applications with multiple destinations

**Key Features**:
- Aggregates statuses from main + additional servers
- Handles excluded containers (`:excluded` suffix)
- Calculates overall application health from all containers

**Status Format with Excluded Containers**:
```php
// When all containers excluded from health checks:
return 'running:unhealthy:excluded';  // Container running but unhealthy, monitoring disabled
return 'running:unknown:excluded';     // Container running, health unknown, monitoring disabled
return 'running:healthy:excluded';     // Container running and healthy, monitoring disabled
return 'degraded:excluded';            // Some containers down, monitoring disabled
return 'exited:excluded';              // All containers stopped, monitoring disabled
```

#### **4. Service-Level Status Aggregation**
**File**: [app/Models/Service.php](mdc:app/Models/Service.php)
**Method**: `complexStatus()` (lines 176-288)
**Purpose**: Aggregates status for multi-container services
**Used by**: Docker Compose services

**Status Calculation**:
```php
// Aggregates status from all service applications and databases
// Handles excluded containers separately
// Returns status with :excluded suffix when all containers excluded
if (!$hasNonExcluded && $complexStatus === null && $complexHealth === null) {
    // All services excluded - calculate from excluded containers
    return "{$excludedStatus}:excluded";
}
```

### **Status Flow Diagram**

```
┌─────────────────────────────────────────────────────────────┐
│                    Container Status Sources                  │
└─────────────────────────────────────────────────────────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ▼                    ▼                    ▼
┌───────────────┐   ┌─────────────────┐   ┌──────────────┐
│ SSH-Based     │   │ Sentinel-Based  │   │ Multi-Server │
│ (Scheduled)   │   │ (Real-time)     │   │ Aggregation  │
├───────────────┤   ├─────────────────┤   ├──────────────┤
│ ServerCheck   │   │ PushServerUp-   │   │ ComplexStatus│
│ Job           │   │ dateJob         │   │ Check        │
│               │   │                 │   │              │
│ Every ~1min   │   │ Every ~30sec    │   │ On demand    │
└───────┬───────┘   └────────┬────────┘   └──────┬───────┘
        │                    │                    │
        └────────────────────┼────────────────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │ Application/Service   │
                 │ Status Property       │
                 └───────────────────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │ UI Display (Livewire) │
                 └───────────────────────┘
```

### **Status Priority System**

All status aggregation locations **MUST** follow the same priority:

**For Running Containers**:
1. **unhealthy** - Container has failing health checks
2. **unknown** - Container health status cannot be determined
3. **healthy** - Container is healthy

**For Non-Running States**:
1. **restarting** → `degraded (unhealthy)`
2. **running + exited** → `degraded (unhealthy)`
3. **dead/removing** → `degraded (unhealthy)`
4. **paused** → `paused`
5. **created/starting** → `starting`
6. **exited** → `exited (unhealthy)`

### **Excluded Containers**

When containers have `exclude_from_hc: true` flag or `restart: no`:

**Behavior**:
- Status is still calculated from container state
- `:excluded` suffix is appended to indicate monitoring disabled
- UI shows "(Monitoring Disabled)" badge
- Action buttons respect the actual container state

**Format**: `{actual-status}:excluded`
**Examples**: `running:unknown:excluded`, `degraded:excluded`, `exited:excluded`

**All-Excluded Scenario**:
When ALL containers are excluded from health checks:
- All three status update paths (PushServerUpdateJob, GetContainersStatus, ComplexStatusCheck) **MUST** calculate status from excluded containers
- Status is returned with `:excluded` suffix (e.g., `running:healthy:excluded`)
- **NEVER** skip status updates - always calculate from excluded containers
- This ensures consistent status regardless of which update mechanism runs
- Shared logic is in `app/Traits/CalculatesExcludedStatus.php`

### **Important Notes for Developers**

✅ **Container Status Aggregation Service**:

The container status aggregation logic is centralized in `App\Services\ContainerStatusAggregator`.

**Status Format Standard**:
- **Backend/Storage**: Colon format (`running:healthy`, `degraded:unhealthy`)
- **UI/Display**: Transform to human format (`Running (Healthy)`, `Degraded (Unhealthy)`)

1. **Using the ContainerStatusAggregator Service**:
   - Import `App\Services\ContainerStatusAggregator` in any class needing status aggregation
   - Two methods available:
     - `aggregateFromStrings(Collection $statusStrings, int $maxRestartCount = 0)` - For pre-formatted status strings
     - `aggregateFromContainers(Collection $containers, int $maxRestartCount = 0)` - For raw Docker container objects
   - Returns colon format: `running:healthy`, `degraded:unhealthy`, etc.
   - Automatically handles crash loop detection via `$maxRestartCount` parameter

2. **State Machine Priority** (handled by service):
   - Restarting → `degraded:unhealthy` (highest priority)
   - Crash loop (exited with restarts) → `degraded:unhealthy`
   - Mixed state (running + exited) → `degraded:unhealthy`
   - Running → `running:unhealthy` / `running:unknown` / `running:healthy`
   - Dead/Removing → `degraded:unhealthy`
   - Paused → `paused:unknown`
   - Starting/Created → `starting:unknown`
   - Exited → `exited:unhealthy` (lowest priority)

3. **Test both update paths**:
   - Run unit tests: `./vendor/bin/pest tests/Unit/ContainerStatusAggregatorTest.php`
   - Run integration tests: `./vendor/bin/pest tests/Unit/`
   - Test SSH updates (manual refresh)
   - Test Sentinel updates (wait 30 seconds)

4. **Handle excluded containers**:
   - All containers excluded (`exclude_from_hc: true`) - Use `CalculatesExcludedStatus` trait
   - Mixed excluded/non-excluded containers - Filter then use `ContainerStatusAggregator`
   - Containers with `restart: no` - Treated same as `exclude_from_hc: true`

5. **Use shared trait for excluded containers**:
   - Import `App\Traits\CalculatesExcludedStatus` in status calculation classes
   - Use `getExcludedContainersFromDockerCompose()` to parse exclusions
   - Use `calculateExcludedStatus()` for full Docker inspect objects (ComplexStatusCheck)
   - Use `calculateExcludedStatusFromStrings()` for status strings (PushServerUpdateJob, GetContainersStatus)

### **Related Tests**

- **[tests/Unit/ContainerStatusAggregatorTest.php](mdc:tests/Unit/ContainerStatusAggregatorTest.php)**: Core state machine logic (42 comprehensive tests)
- **[tests/Unit/ContainerHealthStatusTest.php](mdc:tests/Unit/ContainerHealthStatusTest.php)**: Health status aggregation integration
- **[tests/Unit/PushServerUpdateJobStatusAggregationTest.php](mdc:tests/Unit/PushServerUpdateJobStatusAggregationTest.php)**: Sentinel update logic
- **[tests/Unit/ExcludeFromHealthCheckTest.php](mdc:tests/Unit/ExcludeFromHealthCheckTest.php)**: Excluded container handling

### **Common Bugs to Avoid**

✅ **Prevented by ContainerStatusAggregator Service**:
- ❌ **Old Bug**: Forgetting to track `$hasUnknown` flag → ✅ Now centralized in service
- ❌ **Old Bug**: Inconsistent priority across paths → ✅ Single source of truth
- ❌ **Old Bug**: Forgetting to update all 4 locations → ✅ Only one location to update

**Still Relevant**:

❌ **Bug**: Forgetting to filter excluded containers before aggregation
✅ **Fix**: Always use `CalculatesExcludedStatus` trait to filter before calling `ContainerStatusAggregator`

❌ **Bug**: Not passing `$maxRestartCount` for crash loop detection
✅ **Fix**: Calculate max restart count from containers and pass to `aggregateFromStrings()`/`aggregateFromContainers()`

❌ **Bug**: Not handling excluded containers with `:excluded` suffix
✅ **Fix**: Check for `:excluded` suffix in UI logic and button visibility
