Merge remote-tracking branch 'origin/next' into coolify-design-docs

This commit is contained in:
Andras Bacsai committed 2026-02-09 14:34:04 +01:00
commit 70f5fa5ca5
80 files changed
+4731 -9968

No files matched your search

-148
View File
@@ -1,148 +0,0 @@
# Coolify AI Documentation
Welcome to the Coolify AI documentation hub. This directory contains all AI assistant instructions organized by topic for easy navigation and maintenance.
## Quick Start
- **For Claude Code**: Start with [CLAUDE.md in root directory](../CLAUDE.md)
- **For Cursor IDE**: Check `.cursor/rules/coolify-ai-docs.mdc` which references this directory
- **For Other AI Tools**: Continue reading below
## Documentation Structure
### 📚 Core Documentation
Essential project information and architecture:
- **[Technology Stack](core/technology-stack.md)** - All versions, packages, and dependencies (Laravel 12.4.1, PHP 8.4.7, etc.)
- **[Project Overview](core/project-overview.md)** - What Coolify is and how it works
- **[Application Architecture](core/application-architecture.md)** - System design and component relationships
- **[Deployment Architecture](core/deployment-architecture.md)** - How deployments work end-to-end, including Coolify Docker Compose extensions (custom fields)
### 💻 Development
Day-to-day development practices:
- **[Workflow](development/development-workflow.md)** - Development setup, commands, and daily workflows
- **[Testing Patterns](development/testing-patterns.md)** - How to write and run tests (Unit vs Feature, Docker requirements)
- **[Laravel Boost](development/laravel-boost.md)** - Laravel-specific guidelines and best practices
### 🎨 Patterns
Code patterns and best practices by domain:
- **[Database Patterns](patterns/database-patterns.md)** - Eloquent, migrations, relationships
- **[Frontend Patterns](patterns/frontend-patterns.md)** - Livewire, Alpine.js, Tailwind CSS
- **[Security Patterns](patterns/security-patterns.md)** - Authentication, authorization, security best practices
- **[Form Components](patterns/form-components.md)** - Enhanced form components with authorization
- **[API & Routing](patterns/api-and-routing.md)** - API design, routing conventions, REST patterns
### 📖 Meta
Documentation about documentation:
- **[Maintaining Docs](meta/maintaining-docs.md)** - How to update and improve this documentation
- **[Sync Guide](meta/sync-guide.md)** - Keeping documentation synchronized across tools
## Quick Decision Tree
**What do you need help with?**
### Running Commands
→ [development/development-workflow.md](development/development-workflow.md)
- Frontend: `npm run dev`, `npm run build`
- Backend: `php artisan serve`, `php artisan migrate`
- Tests: Docker for Feature tests, mocking for Unit tests
- Code quality: `./vendor/bin/pint`, `./vendor/bin/phpstan`
### Writing Tests
→ [development/testing-patterns.md](development/testing-patterns.md)
- **Unit tests**: No database, use mocking, run outside Docker
- **Feature tests**: Can use database, must run inside Docker
- Command: `docker exec coolify php artisan test`
### Building UI
→ [patterns/frontend-patterns.md](patterns/frontend-patterns.md) or [patterns/form-components.md](patterns/form-components.md)
- Livewire components with server-side state
- Alpine.js for client-side interactivity
- Tailwind CSS 4.1.4 for styling
- Form components with built-in authorization
### Database Work
→ [patterns/database-patterns.md](patterns/database-patterns.md)
- Eloquent ORM patterns
- Migration best practices
- Relationship definitions
- Query optimization
### Security & Auth
→ [patterns/security-patterns.md](patterns/security-patterns.md)
- Team-based access control
- Policy and gate patterns
- Form authorization (canGate, canResource)
- API security
### Laravel-Specific Questions
→ [development/laravel-boost.md](development/laravel-boost.md)
- Laravel 12 patterns
- Livewire 3 best practices
- Pest testing patterns
- Laravel conventions
### Docker Compose Extensions
→ [core/deployment-architecture.md](core/deployment-architecture.md#coolify-docker-compose-extensions)
- Custom fields: `exclude_from_hc`, `content`, `isDirectory`
- How to use inline file content
- Health check exclusion patterns
- Volume creation control
### Version Numbers
→ [core/technology-stack.md](core/technology-stack.md)
- **Single source of truth** for all version numbers
- Don't duplicate versions elsewhere, reference this file
## Navigation Tips
1. **Start broad**: Begin with project-overview or ../CLAUDE.md
2. **Get specific**: Navigate to topic-specific files for details
3. **Cross-reference**: Files link to related topics
4. **Single source**: Version numbers and critical data exist in ONE place only
## For AI Assistants
### Important Patterns to Follow
**Testing Commands:**
- Unit tests: `./vendor/bin/pest tests/Unit` (no database, outside Docker)
- Feature tests: `docker exec coolify php artisan test` (requires database, inside Docker)
- NEVER run Feature tests outside Docker - they will fail with database connection errors
**Version Numbers:**
- Always use exact versions from [technology-stack.md](core/technology-stack.md)
- Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4
- Don't use "v12" or "8.4" - be precise
**Form Authorization:**
- ALWAYS include `canGate` and `:canResource` on form components
- See [form-components.md](patterns/form-components.md) for examples
**Livewire Components:**
- MUST have exactly ONE root element
- See [frontend-patterns.md](patterns/frontend-patterns.md) for details
**Code Style:**
- Run `./vendor/bin/pint` before finalizing changes
- Follow PSR-12 standards
- Use PHP 8.4 features (constructor promotion, typed properties, etc.)
## Contributing
When updating documentation:
1. Read [meta/maintaining-docs.md](meta/maintaining-docs.md)
2. Follow the single source of truth principle
3. Update cross-references when moving content
4. Test all links work
5. Run Pint on markdown files if applicable
## Questions?
- **Claude Code users**: Check [../CLAUDE.md](../CLAUDE.md) first
- **Cursor IDE users**: Check `.cursor/rules/coolify-ai-docs.mdc`
- **Documentation issues**: See [meta/maintaining-docs.md](meta/maintaining-docs.md)
- **Sync issues**: See [meta/sync-guide.md](meta/sync-guide.md)
-612
View File
@@ -1,612 +0,0 @@
# 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
-666
View File
@@ -1,666 +0,0 @@
# Coolify Deployment Architecture
## Deployment Philosophy
Coolify orchestrates **Docker-based deployments** across multiple servers with automated configuration generation, zero-downtime deployments, and comprehensive monitoring.
## Core Deployment Components
### Deployment Models
- **[Application.php](mdc:app/Models/Application.php)** - Main application entity with deployment configurations
- **[ApplicationDeploymentQueue.php](mdc:app/Models/ApplicationDeploymentQueue.php)** - Deployment job orchestration
- **[Service.php](mdc:app/Models/Service.php)** - Multi-container service definitions
- **[Server.php](mdc:app/Models/Server.php)** - Target deployment infrastructure
### Infrastructure Management
- **[PrivateKey.php](mdc:app/Models/PrivateKey.php)** - SSH key management for secure server access
- **[StandaloneDocker.php](mdc:app/Models/StandaloneDocker.php)** - Single container deployments
- **[SwarmDocker.php](mdc:app/Models/SwarmDocker.php)** - Docker Swarm orchestration
## Deployment Workflow
### 1. Source Code Integration
```
Git Repository → Webhook → Coolify → Build & Deploy
```
#### Source Control Models
- **[GithubApp.php](mdc:app/Models/GithubApp.php)** - GitHub integration and webhooks
- **[GitlabApp.php](mdc:app/Models/GitlabApp.php)** - GitLab CI/CD integration
#### Deployment Triggers
- **Git push** to configured branches
- **Manual deployment** via UI
- **Scheduled deployments** via cron
- **API-triggered** deployments
### 2. Build Process
```
Source Code → Docker Build → Image Registry → Deployment
```
#### Build Configurations
- **Dockerfile detection** and custom Dockerfile support
- **Buildpack integration** for framework detection
- **Multi-stage builds** for optimization
- **Cache layer** management for faster builds
### 3. Deployment Orchestration
```
Queue Job → Configuration Generation → Container Deployment → Health Checks
```
## Deployment Actions
### Location: [app/Actions/](mdc:app/Actions)
#### Application Deployment Actions
- **Application/** - Core application deployment logic
- **Docker/** - Docker container management
- **Service/** - Multi-container service orchestration
- **Proxy/** - Reverse proxy configuration
#### Database Actions
- **Database/** - Database deployment and management
- Automated backup scheduling
- Connection management and health checks
#### Server Management Actions
- **Server/** - Server provisioning and configuration
- SSH connection establishment
- Docker daemon management
## Configuration Generation
### Dynamic Configuration
- **[ConfigurationGenerator.php](mdc:app/Services/ConfigurationGenerator.php)** - Generates deployment configurations
- **[ConfigurationRepository.php](mdc:app/Services/ConfigurationRepository.php)** - Configuration management
### Generated Configurations
#### Docker Compose Files
```yaml
# Generated docker-compose.yml structure
version: '3.8'
services:
app:
image: ${APP_IMAGE}
environment:
- ${ENV_VARIABLES}
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`${FQDN}`)
volumes:
- ${VOLUME_MAPPINGS}
networks:
- coolify
```
#### Nginx Configurations
- **Reverse proxy** setup
- **SSL termination** with automatic certificates
- **Load balancing** for multiple instances
- **Custom headers** and routing rules
## Container Orchestration
### Docker Integration
- **[DockerImageParser.php](mdc:app/Services/DockerImageParser.php)** - Parse and validate Docker images
- **Container lifecycle** management
- **Resource allocation** and limits
- **Network isolation** and communication
### Volume Management
- **[LocalFileVolume.php](mdc:app/Models/LocalFileVolume.php)** - Persistent file storage
- **[LocalPersistentVolume.php](mdc:app/Models/LocalPersistentVolume.php)** - Data persistence
- **Backup integration** for volume data
### Network Configuration
- **Custom Docker networks** for isolation
- **Service discovery** between containers
- **Port mapping** and exposure
- **SSL/TLS termination**
## Environment Management
### Environment Isolation
- **[Environment.php](mdc:app/Models/Environment.php)** - Development, staging, production environments
- **[EnvironmentVariable.php](mdc:app/Models/EnvironmentVariable.php)** - Application-specific variables
- **[SharedEnvironmentVariable.php](mdc:app/Models/SharedEnvironmentVariable.php)** - Cross-application variables
### Configuration Hierarchy
```
Instance Settings → Server Settings → Project Settings → Application Settings
```
## Preview Environments
### Git-Based Previews
- **[ApplicationPreview.php](mdc:app/Models/ApplicationPreview.php)** - Preview environment management
- **Automatic PR/MR previews** for feature branches
- **Isolated environments** for testing
- **Automatic cleanup** after merge/close
### Preview Workflow
```
Feature Branch → Auto-Deploy → Preview URL → Review → Cleanup
```
## SSL & Security
### Certificate Management
- **[SslCertificate.php](mdc:app/Models/SslCertificate.php)** - SSL certificate automation
- **Let's Encrypt** integration for free certificates
- **Custom certificate** upload support
- **Automatic renewal** and monitoring
### Security Patterns
- **Private Docker networks** for container isolation
- **SSH key-based** server authentication
- **Environment variable** encryption
- **Access control** via team permissions
## Backup & Recovery
### Database Backups
- **[ScheduledDatabaseBackup.php](mdc:app/Models/ScheduledDatabaseBackup.php)** - Automated database backups
- **[ScheduledDatabaseBackupExecution.php](mdc:app/Models/ScheduledDatabaseBackupExecution.php)** - Backup execution tracking
- **S3-compatible storage** for backup destinations
### Application Backups
- **Volume snapshots** for persistent data
- **Configuration export** for disaster recovery
- **Cross-region replication** for high availability
## Monitoring & Logging
### Real-Time Monitoring
- **[ActivityMonitor.php](mdc:app/Livewire/ActivityMonitor.php)** - Live deployment monitoring
- **WebSocket-based** log streaming
- **Container health checks** and alerts
- **Resource usage** tracking
### Deployment Logs
- **Build process** logging
- **Container startup** logs
- **Application runtime** logs
- **Error tracking** and alerting
## Queue System
### Background Jobs
Location: [app/Jobs/](mdc:app/Jobs)
- **Deployment jobs** for async processing
- **Server monitoring** jobs
- **Backup scheduling** jobs
- **Notification delivery** jobs
### Queue Processing
- **Redis-backed** job queues
- **Laravel Horizon** for queue monitoring
- **Failed job** retry mechanisms
- **Queue worker** auto-scaling
## Multi-Server Deployment
### Server Types
- **Standalone servers** - Single Docker host
- **Docker Swarm** - Multi-node orchestration
- **Remote servers** - SSH-based deployment
- **Local development** - Docker Desktop integration
### Load Balancing
- **Traefik integration** for automatic load balancing
- **Health check** based routing
- **Blue-green deployments** for zero downtime
- **Rolling updates** with configurable strategies
## Deployment Strategies
### Zero-Downtime Deployment
```
Old Container → New Container Build → Health Check → Traffic Switch → Old Container Cleanup
```
### Blue-Green Deployment
- **Parallel environments** for safe deployments
- **Instant rollback** capability
- **Database migration** handling
- **Configuration synchronization**
### Rolling Updates
- **Gradual instance** replacement
- **Configurable update** strategy
- **Automatic rollback** on failure
- **Health check** validation
## API Integration
### Deployment API
Routes: [routes/api.php](mdc:routes/api.php)
- **RESTful endpoints** for deployment management
- **Webhook receivers** for CI/CD integration
- **Status reporting** endpoints
- **Deployment triggering** via API
### Authentication
- **Laravel Sanctum** API tokens
- **Team-based** access control
- **Rate limiting** for API calls
- **Audit logging** for API usage
## Error Handling & Recovery
### Deployment Failure Recovery
- **Automatic rollback** on deployment failure
- **Health check** failure handling
- **Container crash** recovery
- **Resource exhaustion** protection
### Monitoring & Alerting
- **Failed deployment** notifications
- **Resource threshold** alerts
- **SSL certificate** expiry warnings
- **Backup failure** notifications
## Performance Optimization
### Build Optimization
- **Docker layer** caching
- **Multi-stage builds** for smaller images
- **Build artifact** reuse
- **Parallel build** processing
### Docker Build Cache Preservation
Coolify provides settings to preserve Docker build cache across deployments, addressing cache invalidation issues.
#### The Problem
By default, Coolify injects `ARG` statements into user Dockerfiles for build-time variables. This breaks Docker's cache mechanism because:
1. **ARG declarations invalidate cache** - Any change in ARG values after the `ARG` instruction invalidates all subsequent layers
2. **SOURCE_COMMIT changes every commit** - Causes full rebuilds even when code changes are minimal
#### Application Settings
Two toggles in **Advanced Settings** control this behavior:
| Setting | Default | Description |
|---------|---------|-------------|
| `inject_build_args_to_dockerfile` | `true` | Controls whether Coolify adds `ARG` statements to Dockerfile |
| `include_source_commit_in_build` | `false` | Controls whether `SOURCE_COMMIT` is included in build context |
**Database columns:** `application_settings.inject_build_args_to_dockerfile`, `application_settings.include_source_commit_in_build`
#### Buildpack Coverage
| Build Pack | ARG Injection | Method |
|------------|---------------|--------|
| **Dockerfile** | ✅ Yes | `add_build_env_variables_to_dockerfile()` |
| **Docker Compose** (with `build:`) | ✅ Yes | `modify_dockerfiles_for_compose()` |
| **PR Deployments** (Dockerfile only) | ✅ Yes | `add_build_env_variables_to_dockerfile()` |
| **Nixpacks** | ❌ No | Generates its own Dockerfile internally |
| **Static** | ❌ No | Uses internal Dockerfile |
| **Docker Image** | ❌ No | No build phase |
#### How It Works
**When `inject_build_args_to_dockerfile` is enabled (default):**
```dockerfile
# Coolify modifies your Dockerfile to add:
FROM node:20
ARG MY_VAR=value
ARG COOLIFY_URL=...
ARG SOURCE_COMMIT=abc123 # (if include_source_commit_in_build is true)
# ... rest of your Dockerfile
```
**When `inject_build_args_to_dockerfile` is disabled:**
- Coolify does NOT modify the Dockerfile
- `--build-arg` flags are still passed (harmless without matching `ARG` in Dockerfile)
- User must manually add `ARG` statements for any build-time variables they need
**When `include_source_commit_in_build` is disabled (default):**
- `SOURCE_COMMIT` is NOT included in build-time variables
- `SOURCE_COMMIT` is still available at **runtime** (in container environment)
- Docker cache preserved across different commits
#### Recommended Configuration
| Use Case | inject_build_args | include_source_commit | Cache Behavior |
|----------|-------------------|----------------------|----------------|
| Maximum cache preservation | `false` | `false` | Best cache retention |
| Need build-time vars, no commit | `true` | `false` | Cache breaks on var changes |
| Need commit at build-time | `true` | `true` | Cache breaks every commit |
| Manual ARG management | `false` | `true` | Cache preserved (no ARG in Dockerfile) |
#### Implementation Details
**Files:**
- `app/Jobs/ApplicationDeploymentJob.php`:
- `set_coolify_variables()` - Conditionally adds SOURCE_COMMIT to Docker build context based on `include_source_commit_in_build` setting
- `generate_coolify_env_variables(bool $forBuildTime)` - Distinguishes build-time vs. runtime variables; excludes cache-busting variables like SOURCE_COMMIT from build context unless explicitly enabled
- `generate_env_variables()` - Populates `$this->env_args` with build-time ARG values, respecting `include_source_commit_in_build` toggle
- `add_build_env_variables_to_dockerfile()` - Injects ARG statements into Dockerfiles after FROM instructions; skips injection if `inject_build_args_to_dockerfile` is disabled
- `modify_dockerfiles_for_compose()` - Applies ARG injection to Docker Compose service Dockerfiles; respects `inject_build_args_to_dockerfile` toggle
- `app/Models/ApplicationSetting.php` - Defines `inject_build_args_to_dockerfile` and `include_source_commit_in_build` boolean properties
- `app/Livewire/Project/Application/Advanced.php` - Livewire component providing UI bindings for cache preservation toggles
- `resources/views/livewire/project/application/advanced.blade.php` - Checkbox UI elements for user-facing toggles
**Note:** Docker Compose services without a `build:` section (image-only) are automatically skipped.
### Runtime Optimization
- **Container resource** limits
- **Auto-scaling** based on metrics
- **Connection pooling** for databases
- **CDN integration** for static assets
## Compliance & Governance
### Audit Trail
- **Deployment history** tracking
- **Configuration changes** logging
- **User action** auditing
- **Resource access** monitoring
### Backup Compliance
- **Retention policies** for backups
- **Encryption at rest** for sensitive data
- **Cross-region** backup replication
- **Recovery testing** automation
## Integration Patterns
### CI/CD Integration
- **GitHub Actions** compatibility
- **GitLab CI** pipeline integration
- **Custom webhook** endpoints
- **Build status** reporting
### External Services
- **S3-compatible** storage integration
- **External database** connections
- **Third-party monitoring** tools
- **Custom notification** channels
---
## Coolify Docker Compose Extensions
Coolify extends standard Docker Compose with custom fields (often called "magic fields") that provide Coolify-specific functionality. These extensions are processed during deployment and stripped before sending the final compose file to Docker, maintaining full compatibility with Docker's compose specification.
### Overview
**Why Custom Fields?**
- Enable Coolify-specific features without breaking Docker Compose compatibility
- Simplify configuration by embedding content directly in compose files
- Allow fine-grained control over health check monitoring
- Reduce external file dependencies
**Processing Flow:**
1. User defines compose file with custom fields
2. Coolify parses and processes custom fields (creates files, stores settings)
3. Custom fields are stripped from final compose sent to Docker
4. Docker receives standard, valid compose file
### Service-Level Extensions
#### `exclude_from_hc`
**Type:** Boolean
**Default:** `false`
**Purpose:** Exclude specific services from health check monitoring while still showing their status
**Example Usage:**
```yaml
services:
watchtower:
image: containrrr/watchtower
exclude_from_hc: true # Don't monitor this service's health
backup:
image: postgres:16
exclude_from_hc: true # Backup containers don't need monitoring
restart: always
```
**Behavior:**
- Container status is still calculated from Docker state (running, exited, etc.)
- Status displays with `:excluded` suffix (e.g., `running:healthy:excluded`)
- UI shows "Monitoring Disabled" indicator
- Functionally equivalent to `restart: no` for health check purposes
- See [Container Status with All Excluded](application-architecture.md#container-status-when-all-containers-excluded) for detailed status handling
**Use Cases:**
- Sidecar containers (watchtower, log collectors)
- Backup/maintenance containers
- One-time initialization containers
- Containers that intentionally restart frequently
**Implementation:**
- Parsed: `bootstrap/helpers/parsers.php`
- Status logic: `app/Traits/CalculatesExcludedStatus.php`
- Validation: `tests/Unit/ExcludeFromHealthCheckTest.php`
### Volume-Level Extensions
Volume extensions only work with **long syntax** (array/object format), not short syntax (string format).
#### `content`
**Type:** String (supports multiline with `|` or `>`)
**Purpose:** Embed file content directly in compose file for automatic creation during deployment
**Example Usage:**
```yaml
services:
app:
image: node:20
volumes:
# Inline entrypoint script
- type: bind
source: ./entrypoint.sh
target: /app/entrypoint.sh
content: |
#!/bin/sh
set -e
echo "Starting application..."
npm run migrate
exec "$@"
# Configuration file with environment variables
- type: bind
source: ./config.xml
target: /etc/app/config.xml
content: |
<?xml version='1.0' encoding='UTF-8'?>
<config>
<database>
<host>${DB_HOST}</host>
<port>${DB_PORT}</port>
</database>
</config>
```
**Behavior:**
- Content is written to the host at `source` path before container starts
- File is created with mode `644` (readable by all, writable by owner)
- Environment variables in content are interpolated at deployment time
- Content is stored in `LocalFileVolume` model (encrypted at rest)
- Original `docker_compose_raw` retains content for editing
**Use Cases:**
- Entrypoint scripts
- Configuration files
- Environment-specific settings
- Small initialization scripts
- Templates that require dynamic content
**Limitations:**
- Not suitable for large files (use git repo or external storage instead)
- Binary files not supported
- Changes require redeployment
**Real-World Examples:**
- `templates/compose/traccar.yaml` - XML configuration file
- `templates/compose/supabase.yaml` - Multiple config files
- `templates/compose/chaskiq.yaml` - Entrypoint script
**Implementation:**
- Parsed: `bootstrap/helpers/parsers.php` in `parseCompose()` function (handles `content` field extraction)
- Storage: `app/Models/LocalFileVolume.php`
- Validation: `tests/Unit/StripCoolifyCustomFieldsTest.php`
#### `is_directory` / `isDirectory`
**Type:** Boolean
**Default:** `true` (if neither `content` nor explicit flag provided)
**Purpose:** Indicate whether bind mount source should be created as directory or file
**Example Usage:**
```yaml
services:
app:
volumes:
# Explicit file
- type: bind
source: ./config.json
target: /app/config.json
is_directory: false # Create as file
# Explicit directory
- type: bind
source: ./logs
target: /var/log/app
is_directory: true # Create as directory
# Auto-detected as file (has content)
- type: bind
source: ./script.sh
target: /entrypoint.sh
content: |
#!/bin/sh
echo "Hello"
# is_directory: false implied by content presence
```
**Behavior:**
- If `is_directory: true` → Creates directory with `mkdir -p`
- If `is_directory: false` → Creates empty file with `touch`
- If `content` provided → Implies `is_directory: false`
- If neither specified → Defaults to `true` (directory)
**Naming Conventions:**
- `is_directory` (snake_case) - **Preferred**, consistent with PHP/Laravel conventions
- `isDirectory` (camelCase) - **Legacy support**, both work identically
**Use Cases:**
- Disambiguating files vs directories when no content provided
- Ensuring correct bind mount type for Docker
- Pre-creating mount points before container starts
**Implementation:**
- Parsed: `bootstrap/helpers/parsers.php` in `parseCompose()` function (handles `is_directory`/`isDirectory` field extraction)
- Storage: `app/Models/LocalFileVolume.php` (`is_directory` column)
- Validation: `tests/Unit/StripCoolifyCustomFieldsTest.php`
### Custom Field Stripping
**Function:** `stripCoolifyCustomFields()` in `bootstrap/helpers/docker.php`
All custom fields are removed before the compose file is sent to Docker. This happens in two contexts:
**1. Validation (User-Triggered)**
```php
// In validateComposeFile() - Edit Docker Compose modal
$yaml_compose = Yaml::parse($compose);
$yaml_compose = stripCoolifyCustomFields($yaml_compose); // Strip custom fields
// Send to docker compose config for validation
```
**2. Deployment (Automatic)**
```php
// In Service::parse() - During deployment
$docker_compose = parseCompose($docker_compose_raw);
// Custom fields are processed and then stripped
// Final compose sent to Docker has no custom fields
```
**What Gets Stripped:**
- Service-level: `exclude_from_hc`
- Volume-level: `content`, `isDirectory`, `is_directory`
**What's Preserved:**
- All standard Docker Compose fields
- Environment variables
- Standard volume definitions (after custom fields removed)
### Important Notes
#### Long vs Short Volume Syntax
**✅ Long Syntax (Works with Custom Fields):**
```yaml
volumes:
- type: bind
source: ./data
target: /app/data
content: "Hello" # ✅ Custom fields work here
```
**❌ Short Syntax (Custom Fields Ignored):**
```yaml
volumes:
- "./data:/app/data" # ❌ Cannot add custom fields to strings
```
#### Docker Compose Compatibility
Custom fields are **Coolify-specific** and won't work with standalone `docker compose` CLI:
```bash
# ❌ Won't work - Docker doesn't recognize custom fields
docker compose -f compose.yaml up
# ✅ Works - Use Coolify's deployment (strips custom fields first)
# Deploy through Coolify UI or API
```
#### Editing Custom Fields
When editing in "Edit Docker Compose" modal:
- Custom fields are preserved in the editor
- "Validate" button strips them temporarily for Docker validation
- "Save" button preserves them in `docker_compose_raw`
- They're processed again on next deployment
### Template Examples
See these templates for real-world usage:
**Service Exclusions:**
- `templates/compose/budibase.yaml` - Excludes watchtower from monitoring
- `templates/compose/pgbackweb.yaml` - Excludes backup service
- `templates/compose/elasticsearch-with-kibana.yaml` - Excludes elasticsearch
**Inline Content:**
- `templates/compose/traccar.yaml` - XML configuration (multiline)
- `templates/compose/supabase.yaml` - Multiple config files
- `templates/compose/searxng.yaml` - Settings file
- `templates/compose/invoice-ninja.yaml` - Nginx config
**Directory Flags:**
- `templates/compose/paperless.yaml` - Explicit directory creation
### Testing
**Unit Tests:**
- `tests/Unit/StripCoolifyCustomFieldsTest.php` - Custom field stripping logic
- `tests/Unit/ExcludeFromHealthCheckTest.php` - Health check exclusion behavior
- `tests/Unit/ContainerStatusAggregatorTest.php` - Status aggregation with exclusions
**Test Coverage:**
- ✅ All custom fields (exclude_from_hc, content, isDirectory, is_directory)
- ✅ Multiline content (YAML `|` syntax)
- ✅ Short vs long volume syntax
- ✅ Field stripping without data loss
- ✅ Standard Docker Compose field preservation
-156
View File
@@ -1,156 +0,0 @@
# Coolify Project Overview
## What is Coolify?
Coolify is an **open-source & self-hostable alternative to Heroku / Netlify / Vercel**. It's a comprehensive deployment platform that helps you manage servers, applications, and databases on your own hardware with just an SSH connection.
## Core Mission
**"Imagine having the ease of a cloud but with your own servers. That is Coolify."**
- **No vendor lock-in** - All configurations saved to your servers
- **Self-hosted** - Complete control over your infrastructure
- **SSH-only requirement** - Works with VPS, Bare Metal, Raspberry PIs, anything
- **Docker-first** - Container-based deployment architecture
## Key Features
### 🚀 **Application Deployment**
- Git-based deployments (GitHub, GitLab, Bitbucket, Gitea)
- Docker & Docker Compose support
- Preview deployments for pull requests
- Zero-downtime deployments
- Build cache optimization
### 🖥️ **Server Management**
- Multi-server orchestration
- Real-time monitoring and logs
- SSH key management
- Proxy configuration (Traefik/Caddy)
- Resource usage tracking
### 🗄️ **Database Management**
- PostgreSQL, MySQL, MariaDB, MongoDB
- Redis, KeyDB, Dragonfly, ClickHouse
- Automated backups with S3 integration
- Database clustering support
### 🔧 **Infrastructure as Code**
- Docker Compose generation
- Environment variable management
- SSL certificate automation
- Custom domain configuration
### 👥 **Team Collaboration**
- Multi-tenant team organization
- Role-based access control
- Project and environment isolation
- Team-wide resource sharing
### 📊 **Monitoring & Observability**
- Real-time application logs
- Server resource monitoring
- Deployment status tracking
- Webhook integrations
- Notification systems (Email, Discord, Slack, Telegram)
## Target Users
### **DevOps Engineers**
- Infrastructure automation
- Multi-environment management
- CI/CD pipeline integration
### **Developers**
- Easy application deployment
- Development environment provisioning
- Preview deployments for testing
### **Small to Medium Businesses**
- Cost-effective Heroku alternative
- Self-hosted control and privacy
- Scalable infrastructure management
### **Agencies & Consultants**
- Client project isolation
- Multi-tenant management
- White-label deployment solutions
## Business Model
### **Open Source (Free)**
- Complete feature set
- Self-hosted deployment
- Community support
- No feature restrictions
### **Cloud Version (Paid)**
- Managed Coolify instance
- High availability
- Premium support
- Email notifications included
- Same price as self-hosted server (~$4-5/month)
## Architecture Philosophy
### **Server-Side First**
- Laravel backend with Livewire frontend
- Minimal JavaScript footprint
- Real-time updates via WebSockets
- Progressive enhancement approach
### **Docker-Native**
- Container-first deployment strategy
- Docker Compose orchestration
- Image building and registry integration
- Volume and network management
### **Security-Focused**
- SSH-based server communication
- Environment variable encryption
- Team-based access isolation
- Audit logging and activity tracking
## Project Structure
```
coolify/
├── app/ # Laravel application core
│ ├── Models/ # Domain models (Application, Server, Service)
│ ├── Livewire/ # Frontend components
│ ├── Actions/ # Business logic actions
│ └── Jobs/ # Background job processing
├── resources/ # Frontend assets and views
├── database/ # Migrations and seeders
├── docker/ # Docker configuration
├── scripts/ # Installation and utility scripts
└── tests/ # Test suites (Pest, Dusk)
```
## Key Differentiators
### **vs. Heroku**
- ✅ Self-hosted (no vendor lock-in)
- ✅ Multi-server support
- ✅ No usage-based pricing
- ✅ Full infrastructure control
### **vs. Vercel/Netlify**
- ✅ Backend application support
- ✅ Database management included
- ✅ Multi-environment workflows
- ✅ Custom server infrastructure
### **vs. Docker Swarm/Kubernetes**
- ✅ User-friendly web interface
- ✅ Git-based deployment workflows
- ✅ Integrated monitoring and logging
- ✅ No complex YAML configuration
## Development Principles
- **Simplicity over complexity**
- **Convention over configuration**
- **Security by default**
- **Developer experience focused**
- **Community-driven development**
-245
View File
@@ -1,245 +0,0 @@
# Coolify Technology Stack
Complete technology stack, dependencies, and infrastructure components.
## Backend Framework
### **Laravel 12.4.1** (PHP Framework)
- **Purpose**: Core application framework
- **Key Features**:
- Eloquent ORM for database interactions
- Artisan CLI for development tasks
- Queue system for background jobs
- Event-driven architecture
### **PHP 8.4.7**
- **Requirement**: `^8.4` in composer.json
- **Features Used**:
- Typed properties and return types
- Attributes for validation and configuration
- Match expressions
- Constructor property promotion
## Frontend Stack
### **Livewire 3.5.20** (Primary Frontend Framework)
- **Purpose**: Server-side rendering with reactive components
- **Location**: `app/Livewire/`
- **Key Components**:
- Dashboard - Main interface
- ActivityMonitor - Real-time monitoring
- MonacoEditor - Code editor
### **Alpine.js** (Client-Side Interactivity)
- **Purpose**: Lightweight JavaScript for DOM manipulation
- **Integration**: Works seamlessly with Livewire components
- **Usage**: Declarative directives in Blade templates
### **Tailwind CSS 4.1.4** (Styling Framework)
- **Configuration**: `postcss.config.cjs`
- **Extensions**:
- `@tailwindcss/forms` - Form styling
- `@tailwindcss/typography` - Content typography
- `tailwind-scrollbar` - Custom scrollbars
### **Vue.js 3.5.13** (Component Framework)
- **Purpose**: Enhanced interactive components
- **Integration**: Used alongside Livewire for complex UI
- **Build Tool**: Vite with Vue plugin
## Database & Caching
### **PostgreSQL 15** (Primary Database)
- **Purpose**: Main application data storage
- **Features**: JSONB support, advanced indexing
- **Models**: `app/Models/`
### **Redis 7** (Caching & Real-time)
- **Purpose**:
- Session storage
- Queue backend
- Real-time data caching
- WebSocket session management
### **Supported Databases** (For User Applications)
- **PostgreSQL**: StandalonePostgresql
- **MySQL**: StandaloneMysql
- **MariaDB**: StandaloneMariadb
- **MongoDB**: StandaloneMongodb
- **Redis**: StandaloneRedis
- **KeyDB**: StandaloneKeydb
- **Dragonfly**: StandaloneDragonfly
- **ClickHouse**: StandaloneClickhouse
## Authentication & Security
### **Laravel Sanctum 4.0.8**
- **Purpose**: API token authentication
- **Usage**: Secure API access for external integrations
### **Laravel Fortify 1.25.4**
- **Purpose**: Authentication scaffolding
- **Features**: Login, registration, password reset
### **Laravel Socialite 5.18.0**
- **Purpose**: OAuth provider integration
- **Providers**:
- GitHub, GitLab, Google
- Microsoft Azure, Authentik, Discord, Clerk
- Custom OAuth implementations
## Background Processing
### **Laravel Horizon 5.30.3**
- **Purpose**: Queue monitoring and management
- **Features**: Real-time queue metrics, failed job handling
### **Queue System**
- **Backend**: Redis-based queues
- **Jobs**: `app/Jobs/`
- **Processing**: Background deployment and monitoring tasks
## Development Tools
### **Build Tools**
- **Vite 6.2.6**: Modern build tool and dev server
- **Laravel Vite Plugin**: Laravel integration
- **PostCSS**: CSS processing pipeline
### **Code Quality**
- **Laravel Pint**: PHP code style fixer
- **Rector**: PHP automated refactoring
- **PHPStan**: Static analysis tool
### **Testing Framework**
- **Pest 3.8.0**: Modern PHP testing framework
- **Laravel Dusk**: Browser automation testing
- **PHPUnit**: Unit testing foundation
## External Integrations
### **Git Providers**
- **GitHub**: Repository integration and webhooks
- **GitLab**: Self-hosted and cloud GitLab support
- **Bitbucket**: Atlassian integration
- **Gitea**: Self-hosted Git service
### **Cloud Storage**
- **AWS S3**: league/flysystem-aws-s3-v3
- **SFTP**: league/flysystem-sftp-v3
- **Local Storage**: File system integration
### **Notification Services**
- **Email**: resend/resend-laravel
- **Discord**: Custom webhook integration
- **Slack**: Webhook notifications
- **Telegram**: Bot API integration
- **Pushover**: Push notifications
### **Monitoring & Logging**
- **Sentry**: sentry/sentry-laravel - Error tracking
- **Laravel Ray**: spatie/laravel-ray - Debug tool
- **Activity Log**: spatie/laravel-activitylog
## DevOps & Infrastructure
### **Docker & Containerization**
- **Docker**: Container runtime
- **Docker Compose**: Multi-container orchestration
- **Docker Swarm**: Container clustering (optional)
### **Web Servers & Proxies**
- **Nginx**: Primary web server
- **Traefik**: Reverse proxy and load balancer
- **Caddy**: Alternative reverse proxy
### **Process Management**
- **S6 Overlay**: Process supervisor
- **Supervisor**: Alternative process manager
### **SSL/TLS**
- **Let's Encrypt**: Automatic SSL certificates
- **Custom Certificates**: Manual SSL management
## Terminal & Code Editing
### **XTerm.js 5.5.0**
- **Purpose**: Web-based terminal emulator
- **Features**: SSH session management, real-time command execution
- **Addons**: Fit addon for responsive terminals
### **Monaco Editor**
- **Purpose**: Code editor component
- **Features**: Syntax highlighting, auto-completion
- **Integration**: Environment variable editing, configuration files
## API & Documentation
### **OpenAPI/Swagger**
- **Documentation**: openapi.json (373KB)
- **Generator**: zircote/swagger-php
- **API Routes**: `routes/api.php`
### **WebSocket Communication**
- **Laravel Echo**: Real-time event broadcasting
- **Pusher**: WebSocket service integration
- **Soketi**: Self-hosted WebSocket server
## Package Management
### **PHP Dependencies** (composer.json)
```json
{
"require": {
"php": "^8.4",
"laravel/framework": "12.4.1",
"livewire/livewire": "^3.5.20",
"spatie/laravel-data": "^4.13.1",
"lorisleiva/laravel-actions": "^2.8.6"
}
}
```
### **JavaScript Dependencies** (package.json)
```json
{
"devDependencies": {
"vite": "^6.2.6",
"tailwindcss": "^4.1.4",
"@vitejs/plugin-vue": "5.2.3"
},
"dependencies": {
"@xterm/xterm": "^5.5.0",
"ioredis": "5.6.0"
}
}
```
## Configuration Files
### **Build Configuration**
- **vite.config.js**: Frontend build setup
- **postcss.config.cjs**: CSS processing
- **rector.php**: PHP refactoring rules
- **pint.json**: Code style configuration
### **Testing Configuration**
- **phpunit.xml**: Unit test configuration
- **phpunit.dusk.xml**: Browser test configuration
- **tests/Pest.php**: Pest testing setup
## Version Requirements
### **Minimum Requirements**
- **PHP**: 8.4+
- **Node.js**: 18+ (for build tools)
- **PostgreSQL**: 15+
- **Redis**: 7+
- **Docker**: 20.10+
- **Docker Compose**: 2.0+
### **Recommended Versions**
- **Ubuntu**: 22.04 LTS or 24.04 LTS
- **Memory**: 2GB+ RAM
- **Storage**: 20GB+ available space
- **Network**: Stable internet connection for deployments
-648
View File
@@ -1,648 +0,0 @@
# Coolify Development Workflow
## Development Environment Setup
### Prerequisites
- **PHP 8.4+** - Latest PHP version for modern features
- **Node.js 18+** - For frontend asset compilation
- **Docker & Docker Compose** - Container orchestration
- **PostgreSQL 15** - Primary database
- **Redis 7** - Caching and queues
### Local Development Setup
#### Using Docker (Recommended)
```bash
# Clone the repository
git clone https://github.com/coollabsio/coolify.git
cd coolify
# Copy environment configuration
cp .env.example .env
# Start development environment
docker-compose -f docker-compose.dev.yml up -d
# Install PHP dependencies
docker-compose exec app composer install
# Install Node.js dependencies
docker-compose exec app npm install
# Generate application key
docker-compose exec app php artisan key:generate
# Run database migrations
docker-compose exec app php artisan migrate
# Seed development data
docker-compose exec app php artisan db:seed
```
#### Native Development
```bash
# Install PHP dependencies
composer install
# Install Node.js dependencies
npm install
# Setup environment
cp .env.example .env
php artisan key:generate
# Setup database
createdb coolify_dev
php artisan migrate
php artisan db:seed
# Start development servers
php artisan serve &
npm run dev &
php artisan queue:work &
```
## Development Tools & Configuration
### Code Quality Tools
- **[Laravel Pint](mdc:pint.json)** - PHP code style fixer
- **[Rector](mdc:rector.php)** - PHP automated refactoring (989B, 35 lines)
- **PHPStan** - Static analysis for type safety
- **ESLint** - JavaScript code quality
### Development Configuration Files
- **[docker-compose.dev.yml](mdc:docker-compose.dev.yml)** - Development Docker setup (3.4KB, 126 lines)
- **[vite.config.js](mdc:vite.config.js)** - Frontend build configuration (1.0KB, 42 lines)
- **[.editorconfig](mdc:.editorconfig)** - Code formatting standards (258B, 19 lines)
### Git Configuration
- **[.gitignore](mdc:.gitignore)** - Version control exclusions (522B, 40 lines)
- **[.gitattributes](mdc:.gitattributes)** - Git file handling (185B, 11 lines)
## Development Workflow Process
### 1. Feature Development
```bash
# Create feature branch
git checkout -b feature/new-deployment-strategy
# Make changes following coding standards
# Run code quality checks
./vendor/bin/pint
./vendor/bin/rector process --dry-run
./vendor/bin/phpstan analyse
# Run tests
./vendor/bin/pest
./vendor/bin/pest --coverage
# Commit changes
git add .
git commit -m "feat: implement blue-green deployment strategy"
```
### 2. Code Review Process
```bash
# Push feature branch
git push origin feature/new-deployment-strategy
# Create pull request with:
# - Clear description of changes
# - Screenshots for UI changes
# - Test coverage information
# - Breaking change documentation
```
### 3. Testing Requirements
- **Unit tests** for new models and services
- **Feature tests** for API endpoints
- **Browser tests** for UI changes
- **Integration tests** for deployment workflows
## Coding Standards & Conventions
### PHP Coding Standards
```php
// Follow PSR-12 coding standards
class ApplicationDeploymentService
{
public function __construct(
private readonly DockerService $dockerService,
private readonly ConfigurationGenerator $configGenerator
) {}
public function deploy(Application $application): ApplicationDeploymentQueue
{
return DB::transaction(function () use ($application) {
$deployment = $application->deployments()->create([
'status' => 'queued',
'commit_sha' => $application->getLatestCommitSha(),
]);
DeployApplicationJob::dispatch($deployment);
return $deployment;
});
}
}
```
### Laravel Best Practices
```php
// Use Laravel conventions
class Application extends Model
{
// Mass assignment protection
protected $fillable = [
'name', 'git_repository', 'git_branch', 'fqdn'
];
// Type casting
protected $casts = [
'environment_variables' => 'array',
'build_pack' => BuildPack::class,
'created_at' => 'datetime',
];
// Relationships
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function deployments(): HasMany
{
return $this->hasMany(ApplicationDeploymentQueue::class);
}
}
```
### Frontend Standards
```javascript
// Alpine.js component structure
document.addEventListener('alpine:init', () => {
Alpine.data('deploymentMonitor', () => ({
status: 'idle',
logs: [],
init() {
this.connectWebSocket();
},
connectWebSocket() {
Echo.private(`application.${this.applicationId}`)
.listen('DeploymentStarted', (e) => {
this.status = 'deploying';
})
.listen('DeploymentCompleted', (e) => {
this.status = 'completed';
});
}
}));
});
```
### CSS/Tailwind Standards
```html
<!-- Use semantic class names and consistent spacing -->
<div class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Application Status
</h3>
<div class="space-y-3">
<!-- Content with consistent spacing -->
</div>
</div>
</div>
```
## Database Development
### Migration Best Practices
```php
// Create descriptive migration files
class CreateApplicationDeploymentQueuesTable extends Migration
{
public function up(): void
{
Schema::create('application_deployment_queues', function (Blueprint $table) {
$table->id();
$table->foreignId('application_id')->constrained()->cascadeOnDelete();
$table->string('status')->default('queued');
$table->string('commit_sha')->nullable();
$table->text('build_logs')->nullable();
$table->text('deployment_logs')->nullable();
$table->timestamp('started_at')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
$table->index(['application_id', 'status']);
$table->index('created_at');
});
}
public function down(): void
{
Schema::dropIfExists('application_deployment_queues');
}
}
```
### Model Factory Development
```php
// Create comprehensive factories for testing
class ApplicationFactory extends Factory
{
protected $model = Application::class;
public function definition(): array
{
return [
'name' => $this->faker->words(2, true),
'fqdn' => $this->faker->domainName,
'git_repository' => 'https://github.com/' . $this->faker->userName . '/' . $this->faker->word . '.git',
'git_branch' => 'main',
'build_pack' => BuildPack::NIXPACKS,
'server_id' => Server::factory(),
'environment_id' => Environment::factory(),
];
}
public function withCustomDomain(): static
{
return $this->state(fn (array $attributes) => [
'fqdn' => $this->faker->domainName,
]);
}
}
```
## API Development
### Controller Standards
```php
class ApplicationController extends Controller
{
public function __construct()
{
$this->middleware('auth:sanctum');
$this->middleware('team.access');
}
public function index(Request $request): AnonymousResourceCollection
{
$applications = $request->user()
->currentTeam
->applications()
->with(['server', 'environment', 'latestDeployment'])
->paginate();
return ApplicationResource::collection($applications);
}
public function store(StoreApplicationRequest $request): ApplicationResource
{
$application = $request->user()
->currentTeam
->applications()
->create($request->validated());
return new ApplicationResource($application);
}
public function deploy(Application $application): JsonResponse
{
$this->authorize('deploy', $application);
$deployment = app(ApplicationDeploymentService::class)
->deploy($application);
return response()->json([
'message' => 'Deployment started successfully',
'deployment_id' => $deployment->id,
]);
}
}
```
### API Resource Development
```php
class ApplicationResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'fqdn' => $this->fqdn,
'status' => $this->status,
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => $this->build_pack,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
// Conditional relationships
'server' => new ServerResource($this->whenLoaded('server')),
'environment' => new EnvironmentResource($this->whenLoaded('environment')),
'latest_deployment' => new DeploymentResource($this->whenLoaded('latestDeployment')),
// Computed attributes
'deployment_url' => $this->getDeploymentUrl(),
'can_deploy' => $this->canDeploy(),
];
}
}
```
## Livewire Component Development
### Component Structure
```php
class ApplicationShow extends Component
{
public Application $application;
public bool $showLogs = false;
protected $listeners = [
'deployment.started' => 'refreshDeploymentStatus',
'deployment.completed' => 'refreshDeploymentStatus',
];
public function mount(Application $application): void
{
$this->authorize('view', $application);
$this->application = $application;
}
public function deploy(): void
{
$this->authorize('deploy', $this->application);
try {
app(ApplicationDeploymentService::class)->deploy($this->application);
$this->dispatch('deployment.started', [
'application_id' => $this->application->id
]);
session()->flash('success', 'Deployment started successfully');
} catch (Exception $e) {
session()->flash('error', 'Failed to start deployment: ' . $e->getMessage());
}
}
public function refreshDeploymentStatus(): void
{
$this->application->refresh();
}
public function render(): View
{
return view('livewire.application.show', [
'deployments' => $this->application
->deployments()
->latest()
->limit(10)
->get()
]);
}
}
```
## Queue Job Development
### Job Structure
```php
class DeployApplicationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $maxExceptions = 1;
public function __construct(
public ApplicationDeploymentQueue $deployment
) {}
public function handle(
DockerService $dockerService,
ConfigurationGenerator $configGenerator
): void {
$this->deployment->update(['status' => 'running', 'started_at' => now()]);
try {
// Generate configuration
$config = $configGenerator->generateDockerCompose($this->deployment->application);
// Build and deploy
$imageTag = $dockerService->buildImage($this->deployment->application);
$dockerService->deployContainer($this->deployment->application, $imageTag);
$this->deployment->update([
'status' => 'success',
'finished_at' => now()
]);
// Broadcast success
broadcast(new DeploymentCompleted($this->deployment));
} catch (Exception $e) {
$this->deployment->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
'finished_at' => now()
]);
broadcast(new DeploymentFailed($this->deployment));
throw $e;
}
}
public function backoff(): array
{
return [1, 5, 10];
}
public function failed(Throwable $exception): void
{
$this->deployment->update([
'status' => 'failed',
'error_message' => $exception->getMessage(),
'finished_at' => now()
]);
}
}
```
## Testing Development
### Test Structure
```php
// Feature test example
test('user can deploy application via API', function () {
$user = User::factory()->create();
$application = Application::factory()->create([
'team_id' => $user->currentTeam->id
]);
// Mock external services
$this->mock(DockerService::class, function ($mock) {
$mock->shouldReceive('buildImage')->andReturn('app:latest');
$mock->shouldReceive('deployContainer')->andReturn(true);
});
$response = $this->actingAs($user)
->postJson("/api/v1/applications/{$application->id}/deploy");
$response->assertStatus(200)
->assertJson([
'message' => 'Deployment started successfully'
]);
expect($application->deployments()->count())->toBe(1);
expect($application->deployments()->first()->status)->toBe('queued');
});
```
## Documentation Standards
### Code Documentation
```php
/**
* Deploy an application to the specified server.
*
* This method creates a new deployment queue entry and dispatches
* a background job to handle the actual deployment process.
*
* @param Application $application The application to deploy
* @param array $options Additional deployment options
* @return ApplicationDeploymentQueue The created deployment queue entry
*
* @throws DeploymentException When deployment cannot be started
* @throws ServerConnectionException When server is unreachable
*/
public function deploy(Application $application, array $options = []): ApplicationDeploymentQueue
{
// Implementation
}
```
### API Documentation
```php
/**
* @OA\Post(
* path="/api/v1/applications/{application}/deploy",
* summary="Deploy an application",
* description="Triggers a new deployment for the specified application",
* operationId="deployApplication",
* tags={"Applications"},
* security={{"bearerAuth":{}}},
* @OA\Parameter(
* name="application",
* in="path",
* required=true,
* @OA\Schema(type="integer"),
* description="Application ID"
* ),
* @OA\Response(
* response=200,
* description="Deployment started successfully",
* @OA\JsonContent(
* @OA\Property(property="message", type="string"),
* @OA\Property(property="deployment_id", type="integer")
* )
* )
* )
*/
```
## Performance Optimization
### Database Optimization
```php
// Use eager loading to prevent N+1 queries
$applications = Application::with([
'server:id,name,ip',
'environment:id,name',
'latestDeployment:id,application_id,status,created_at'
])->get();
// Use database transactions for consistency
DB::transaction(function () use ($application) {
$deployment = $application->deployments()->create(['status' => 'queued']);
$application->update(['last_deployment_at' => now()]);
DeployApplicationJob::dispatch($deployment);
});
```
### Caching Strategies
```php
// Cache expensive operations
public function getServerMetrics(Server $server): array
{
return Cache::remember(
"server.{$server->id}.metrics",
now()->addMinutes(5),
fn () => $this->fetchServerMetrics($server)
);
}
```
## Deployment & Release Process
### Version Management
- **[versions.json](mdc:versions.json)** - Version tracking (355B, 19 lines)
- **[CHANGELOG.md](mdc:CHANGELOG.md)** - Release notes (187KB, 7411 lines)
- **[cliff.toml](mdc:cliff.toml)** - Changelog generation (3.2KB, 85 lines)
### Release Workflow
```bash
# Create release branch
git checkout -b release/v4.1.0
# Update version numbers
# Update CHANGELOG.md
# Run full test suite
./vendor/bin/pest
npm run test
# Create release commit
git commit -m "chore: release v4.1.0"
# Create and push tag
git tag v4.1.0
git push origin v4.1.0
# Merge to main
git checkout main
git merge release/v4.1.0
```
## Contributing Guidelines
### Pull Request Process
1. **Fork** the repository
2. **Create** feature branch from `main`
3. **Implement** changes with tests
4. **Run** code quality checks
5. **Submit** pull request with clear description
6. **Address** review feedback
7. **Merge** after approval
### Code Review Checklist
- [ ] Code follows project standards
- [ ] Tests cover new functionality
- [ ] Documentation is updated
- [ ] No breaking changes without migration
- [ ] Performance impact considered
- [ ] Security implications reviewed
### Issue Reporting
- Use issue templates
- Provide reproduction steps
- Include environment details
- Add relevant logs/screenshots
- Label appropriately
-402
View File
@@ -1,402 +0,0 @@
<laravel-boost-guidelines>
=== foundation rules ===
# Laravel Boost Guidelines
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications.
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.7
- laravel/fortify (FORTIFY) - v1
- laravel/framework (LARAVEL) - v12
- laravel/horizon (HORIZON) - v5
- laravel/prompts (PROMPTS) - v0
- laravel/sanctum (SANCTUM) - v4
- laravel/socialite (SOCIALITE) - v5
- livewire/livewire (LIVEWIRE) - v3
- laravel/dusk (DUSK) - v8
- laravel/pint (PINT) - v1
- laravel/telescope (TELESCOPE) - v5
- pestphp/pest (PEST) - v3
- phpunit/phpunit (PHPUNIT) - v11
- rector/rector (RECTOR) - v2
- laravel-echo (ECHO) - v2
- tailwindcss (TAILWINDCSS) - v4
- vue (VUE) - v3
## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.
## Verification Scripts
- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
## Application Structure & Architecture
- Stick to existing directory structure - don't create new base folders without approval.
- Do not change the application's dependencies without approval.
## Frontend Bundling
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.
## Replies
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
## Documentation Files
- You must only create documentation files if explicitly requested by the user.
=== boost rules ===
## Laravel Boost
- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
## Artisan
- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters.
## URLs
- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port.
## Tinker / Debugging
- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
- Use the `database-query` tool when you only need to read from the database.
## Reading Browser Logs With the `browser-logs` Tool
- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
- Only recent browser logs will be useful - ignore old logs.
## Searching Documentation (Critically Important)
- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc.
- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches.
- Search the documentation before making code changes to ensure we are taking the correct approach.
- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`.
- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
### Available Search Syntax
- You can and should pass multiple queries at once. The most relevant results will be returned first.
1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit"
3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit"
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms
=== php rules ===
## PHP
- Always use curly braces for control structures, even if it has one line.
### Constructors
- Use PHP 8 constructor property promotion in `__construct()`.
- <code-snippet>public function __construct(public GitHub $github) { }</code-snippet>
- Do not allow empty `__construct()` methods with zero parameters.
### Type Declarations
- Always use explicit return type declarations for methods and functions.
- Use appropriate PHP type hints for method parameters.
<code-snippet name="Explicit Return Types and Method Params" lang="php">
protected function isAccessible(User $user, ?string $path = null): bool
{
...
}
</code-snippet>
## Comments
- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on.
## PHPDoc Blocks
- Add useful array shape type definitions for arrays when appropriate.
## Enums
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
=== laravel/core rules ===
## Do Things the Laravel Way
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
- If you're creating a generic PHP class, use `artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
### Database
- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
- Use Eloquent models and relationships before suggesting raw database queries
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
- Generate code that prevents N+1 query problems by using eager loading.
- Use Laravel's query builder for very complex database operations.
### Model Creation
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`.
### APIs & Eloquent Resources
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
### Controllers & Validation
- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
- Check sibling Form Requests to see if the application uses array or string based validation rules.
### Queues
- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
### Authentication & Authorization
- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
### URL Generation
- When generating links to other pages, prefer named routes and the `route()` function.
### Configuration
- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
### Testing
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `php artisan make:test [options] <name>` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
### Vite Error
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.
=== laravel/v12 rules ===
## Laravel 12
- Use the `search-docs` tool to get version specific documentation.
- This project upgraded from Laravel 10 without migrating to the new streamlined Laravel file structure.
- This is **perfectly fine** and recommended by Laravel. Follow the existing structure from Laravel 10. We do not to need migrate to the new Laravel structure unless the user explicitly requests that.
### Laravel 10 Structure
- Middleware typically lives in `app/Http/Middleware/` and service providers in `app/Providers/`.
- There is no `bootstrap/app.php` application configuration in a Laravel 10 structure:
- Middleware registration happens in `app/Http/Kernel.php`
- Exception handling is in `app/Exceptions/Handler.php`
- Console commands and schedule register in `app/Console/Kernel.php`
- Rate limits likely exist in `RouteServiceProvider` or `app/Http/Kernel.php`
### Database
- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.
### Models
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
=== livewire/core rules ===
## Livewire Core
- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests.
- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components
- State should live on the server, with the UI reflecting it.
- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions.
## Livewire Best Practices
- Livewire components require a single root element.
- Use `wire:loading` and `wire:dirty` for delightful loading states.
- Add `wire:key` in loops:
```blade
@foreach ($items as $item)
<div wire:key="item-{{ $item->id }}">
{{ $item->name }}
</div>
@endforeach
```
- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects:
<code-snippet name="Lifecycle hook examples" lang="php">
public function mount(User $user) { $this->user = $user; }
public function updatedSearch() { $this->resetPage(); }
</code-snippet>
## Testing Livewire
<code-snippet name="Example Livewire component test" lang="php">
Livewire::test(Counter::class)
->assertSet('count', 0)
->call('increment')
->assertSet('count', 1)
->assertSee(1)
->assertStatus(200);
</code-snippet>
<code-snippet name="Testing a Livewire component exists within a page" lang="php">
$this->get('/posts/create')
->assertSeeLivewire(CreatePost::class);
</code-snippet>
=== livewire/v3 rules ===
## Livewire 3
### Key Changes From Livewire 2
- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions.
- Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default.
- Components now use the `App\Livewire` namespace (not `App\Http\Livewire`).
- Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`).
- Use the `components.layouts.app` view as the typical layout path (not `layouts.app`).
### New Directives
- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples.
### Alpine
- Alpine is now included with Livewire, don't manually include Alpine.js.
- Plugins included with Alpine: persist, intersect, collapse, and focus.
### Lifecycle Hooks
- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring:
<code-snippet name="livewire:load example" lang="js">
document.addEventListener('livewire:init', function () {
Livewire.hook('request', ({ fail }) => {
if (fail && fail.status === 419) {
alert('Your session expired');
}
});
Livewire.hook('message.failed', (message, component) => {
console.error(message);
});
});
</code-snippet>
=== pint/core rules ===
## Laravel Pint Code Formatter
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
=== pest/core rules ===
## Pest
### Testing
- If you need to verify a feature is working, write or update a Unit / Feature test.
### Pest Tests
- All tests must be written using Pest. Use `php artisan make:test --pest <name>`.
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
- Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories.
- Pest tests look and behave like this:
<code-snippet name="Basic Pest Test Example" lang="php">
it('is true', function () {
expect(true)->toBeTrue();
});
</code-snippet>
### Running Tests
- Run the minimal number of tests using an appropriate filter before finalizing code edits.
- To run all tests: `php artisan test`.
- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`.
- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file).
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
### Pest Assertions
- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.:
<code-snippet name="Pest Example Asserting postJson Response" lang="php">
it('returns all', function () {
$response = $this->postJson('/api/docs', []);
$response->assertSuccessful();
});
</code-snippet>
### Mocking
- Mocking can be very helpful when appropriate.
- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do.
- You can also create partial mocks using the same import or self method.
### Datasets
- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules.
<code-snippet name="Pest Dataset Example" lang="php">
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with([
'james' => 'james@laravel.com',
'taylor' => 'taylor@laravel.com',
]);
</code-snippet>
=== tailwindcss/core rules ===
## Tailwind Core
- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own.
- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..)
- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically
- You can use the `search-docs` tool to get exact examples from the official documentation when needed.
### Spacing
- When listing items, use gap utilities for spacing, don't use margins.
<code-snippet name="Valid Flex Gap Spacing Example" lang="html">
<div class="flex gap-8">
<div>Superior</div>
<div>Michigan</div>
<div>Erie</div>
</div>
</code-snippet>
### Dark Mode
- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`.
=== tailwindcss/v4 rules ===
## Tailwind 4
- Always use Tailwind CSS v4 - do not use the deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff"
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
</code-snippet>
### Replaced Utilities
- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement.
- Opacity values are still numeric.
| Deprecated | Replacement |
|------------+--------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |
=== tests rules ===
## Test Enforcement
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter.
</laravel-boost-guidelines>
-648
View File
@@ -1,648 +0,0 @@
# Coolify Testing Architecture & Patterns
> **Cross-Reference**: These detailed testing patterns align with the testing guidelines in **[CLAUDE.md](mdc:CLAUDE.md)**. Both documents share the same core principles about Docker execution and mocking preferences.
## Testing Philosophy
Coolify employs **comprehensive testing strategies** using modern PHP testing frameworks to ensure reliability of deployment operations, infrastructure management, and user interactions.
### Test Execution Rules
**CRITICAL**: Tests are categorized by database dependency:
#### Unit Tests (`tests/Unit/`)
- **MUST NOT** use database connections
- **MUST** use mocking for models and external dependencies
- **CAN** run outside Docker: `./vendor/bin/pest tests/Unit`
- Purpose: Test isolated logic, helper functions, and business rules
#### Feature Tests (`tests/Feature/`)
- **MAY** use database connections (factories, migrations, models)
- **MUST** run inside Docker container: `docker exec coolify php artisan test`
- **MUST** use `RefreshDatabase` trait if touching database
- Purpose: Test API endpoints, workflows, and integration scenarios
**Rule of thumb**: If your test needs `Server::factory()->create()` or any database operation, it's a Feature test and MUST run in Docker.
### Prefer Mocking Over Database
When writing tests, always prefer mocking over real database operations:
```php
// ❌ BAD: Unit test using database
it('extracts custom commands', function () {
$server = Server::factory()->create(['ip' => '1.2.3.4']);
$commands = extract_custom_proxy_commands($server, $yaml);
expect($commands)->toBeArray();
});
// ✅ GOOD: Unit test using mocking
it('extracts custom commands', function () {
$server = Mockery::mock('App\Models\Server');
$server->shouldReceive('proxyType')->andReturn('traefik');
$commands = extract_custom_proxy_commands($server, $yaml);
expect($commands)->toBeArray();
});
```
**Design principles for testable code:**
- Use dependency injection instead of global state
- Create interfaces for external dependencies (SSH, Docker, etc.)
- Separate business logic from data persistence
- Make functions accept interfaces instead of concrete models when possible
## Testing Framework Stack
### Core Testing Tools
- **Pest PHP 3.8+** - Primary testing framework with expressive syntax
- **Laravel Dusk** - Browser automation and end-to-end testing
- **PHPUnit** - Underlying unit testing framework
- **Mockery** - Mocking and stubbing for isolated tests
### Testing Configuration
- **[tests/Pest.php](mdc:tests/Pest.php)** - Pest configuration and global setup (1.5KB, 45 lines)
- **[tests/TestCase.php](mdc:tests/TestCase.php)** - Base test case class (163B, 11 lines)
- **[tests/CreatesApplication.php](mdc:tests/CreatesApplication.php)** - Application factory trait (375B, 22 lines)
- **[tests/DuskTestCase.php](mdc:tests/DuskTestCase.php)** - Browser testing setup (1.4KB, 58 lines)
## Test Directory Structure
### Test Organization
- **[tests/Feature/](mdc:tests/Feature)** - Feature and integration tests
- **[tests/Unit/](mdc:tests/Unit)** - Unit tests for isolated components
- **[tests/Browser/](mdc:tests/Browser)** - Laravel Dusk browser tests
- **[tests/Traits/](mdc:tests/Traits)** - Shared testing utilities
## Unit Testing Patterns
### Model Testing
```php
// Testing Eloquent models
test('application model has correct relationships', function () {
$application = Application::factory()->create();
expect($application->server)->toBeInstanceOf(Server::class);
expect($application->environment)->toBeInstanceOf(Environment::class);
expect($application->deployments)->toBeInstanceOf(Collection::class);
});
test('application can generate deployment configuration', function () {
$application = Application::factory()->create([
'name' => 'test-app',
'git_repository' => 'https://github.com/user/repo.git'
]);
$config = $application->generateDockerCompose();
expect($config)->toContain('test-app');
expect($config)->toContain('image:');
expect($config)->toContain('networks:');
});
```
### Service Layer Testing
```php
// Testing service classes
test('configuration generator creates valid docker compose', function () {
$generator = new ConfigurationGenerator();
$application = Application::factory()->create();
$compose = $generator->generateDockerCompose($application);
expect($compose)->toBeString();
expect(yaml_parse($compose))->toBeArray();
expect($compose)->toContain('version: "3.8"');
});
test('docker image parser validates image names', function () {
$parser = new DockerImageParser();
expect($parser->isValid('nginx:latest'))->toBeTrue();
expect($parser->isValid('invalid-image-name'))->toBeFalse();
expect($parser->parse('nginx:1.21'))->toEqual([
'registry' => 'docker.io',
'namespace' => 'library',
'repository' => 'nginx',
'tag' => '1.21'
]);
});
```
### Action Testing
```php
// Testing Laravel Actions
test('deploy application action creates deployment queue', function () {
$application = Application::factory()->create();
$action = new DeployApplicationAction();
$deployment = $action->handle($application);
expect($deployment)->toBeInstanceOf(ApplicationDeploymentQueue::class);
expect($deployment->status)->toBe('queued');
expect($deployment->application_id)->toBe($application->id);
});
test('server validation action checks ssh connectivity', function () {
$server = Server::factory()->create([
'ip' => '192.168.1.100',
'port' => 22
]);
$action = new ValidateServerAction();
// Mock SSH connection
$this->mock(SshConnection::class, function ($mock) {
$mock->shouldReceive('connect')->andReturn(true);
$mock->shouldReceive('execute')->with('docker --version')->andReturn('Docker version 20.10.0');
});
$result = $action->handle($server);
expect($result['ssh_connection'])->toBeTrue();
expect($result['docker_installed'])->toBeTrue();
});
```
## Feature Testing Patterns
### API Testing
```php
// Testing API endpoints
test('authenticated user can list applications', function () {
$user = User::factory()->create();
$team = Team::factory()->create();
$user->teams()->attach($team);
$applications = Application::factory(3)->create([
'team_id' => $team->id
]);
$response = $this->actingAs($user)
->getJson('/api/v1/applications');
$response->assertStatus(200)
->assertJsonCount(3, 'data')
->assertJsonStructure([
'data' => [
'*' => ['id', 'name', 'fqdn', 'status', 'created_at']
]
]);
});
test('user cannot access applications from other teams', function () {
$user = User::factory()->create();
$otherTeam = Team::factory()->create();
$application = Application::factory()->create([
'team_id' => $otherTeam->id
]);
$response = $this->actingAs($user)
->getJson("/api/v1/applications/{$application->id}");
$response->assertStatus(403);
});
```
### Deployment Testing
```php
// Testing deployment workflows
test('application deployment creates docker containers', function () {
$application = Application::factory()->create([
'git_repository' => 'https://github.com/laravel/laravel.git',
'git_branch' => 'main'
]);
// Mock Docker operations
$this->mock(DockerService::class, function ($mock) {
$mock->shouldReceive('buildImage')->andReturn('app:latest');
$mock->shouldReceive('createContainer')->andReturn('container_id');
$mock->shouldReceive('startContainer')->andReturn(true);
});
$deployment = $application->deploy();
expect($deployment->status)->toBe('queued');
// Process the deployment job
$this->artisan('queue:work --once');
$deployment->refresh();
expect($deployment->status)->toBe('success');
});
test('failed deployment triggers rollback', function () {
$application = Application::factory()->create();
// Mock failed deployment
$this->mock(DockerService::class, function ($mock) {
$mock->shouldReceive('buildImage')->andThrow(new DeploymentException('Build failed'));
});
$deployment = $application->deploy();
$this->artisan('queue:work --once');
$deployment->refresh();
expect($deployment->status)->toBe('failed');
expect($deployment->error_message)->toContain('Build failed');
});
```
### Webhook Testing
```php
// Testing webhook endpoints
test('github webhook triggers deployment', function () {
$application = Application::factory()->create([
'git_repository' => 'https://github.com/user/repo.git',
'git_branch' => 'main'
]);
$payload = [
'ref' => 'refs/heads/main',
'repository' => [
'clone_url' => 'https://github.com/user/repo.git'
],
'head_commit' => [
'id' => 'abc123',
'message' => 'Update application'
]
];
$response = $this->postJson("/webhooks/github/{$application->id}", $payload);
$response->assertStatus(200);
expect($application->deployments()->count())->toBe(1);
expect($application->deployments()->first()->commit_sha)->toBe('abc123');
});
test('webhook validates payload signature', function () {
$application = Application::factory()->create();
$payload = ['invalid' => 'payload'];
$response = $this->postJson("/webhooks/github/{$application->id}", $payload);
$response->assertStatus(400);
});
```
## Browser Testing (Laravel Dusk)
### End-to-End Testing
```php
// Testing complete user workflows
test('user can create and deploy application', function () {
$user = User::factory()->create();
$server = Server::factory()->create(['team_id' => $user->currentTeam->id]);
$this->browse(function (Browser $browser) use ($user, $server) {
$browser->loginAs($user)
->visit('/applications/create')
->type('name', 'Test Application')
->type('git_repository', 'https://github.com/laravel/laravel.git')
->type('git_branch', 'main')
->select('server_id', $server->id)
->press('Create Application')
->assertPathIs('/applications/*')
->assertSee('Test Application')
->press('Deploy')
->waitForText('Deployment started', 10)
->assertSee('Deployment started');
});
});
test('user can monitor deployment logs in real-time', function () {
$user = User::factory()->create();
$application = Application::factory()->create(['team_id' => $user->currentTeam->id]);
$this->browse(function (Browser $browser) use ($user, $application) {
$browser->loginAs($user)
->visit("/applications/{$application->id}")
->press('Deploy')
->waitForText('Deployment started')
->click('@logs-tab')
->waitFor('@deployment-logs')
->assertSee('Building Docker image')
->waitForText('Deployment completed', 30);
});
});
```
### UI Component Testing
```php
// Testing Livewire components
test('server status component updates in real-time', function () {
$user = User::factory()->create();
$server = Server::factory()->create(['team_id' => $user->currentTeam->id]);
$this->browse(function (Browser $browser) use ($user, $server) {
$browser->loginAs($user)
->visit("/servers/{$server->id}")
->assertSee('Status: Online')
->waitFor('@server-metrics')
->assertSee('CPU Usage')
->assertSee('Memory Usage')
->assertSee('Disk Usage');
// Simulate server going offline
$server->update(['status' => 'offline']);
$browser->waitForText('Status: Offline', 5)
->assertSee('Status: Offline');
});
});
```
## Database Testing Patterns
### Migration Testing
```php
// Testing database migrations
test('applications table has correct structure', function () {
expect(Schema::hasTable('applications'))->toBeTrue();
expect(Schema::hasColumns('applications', [
'id', 'name', 'fqdn', 'git_repository', 'git_branch',
'server_id', 'environment_id', 'created_at', 'updated_at'
]))->toBeTrue();
});
test('foreign key constraints are properly set', function () {
$application = Application::factory()->create();
expect($application->server)->toBeInstanceOf(Server::class);
expect($application->environment)->toBeInstanceOf(Environment::class);
// Test cascade deletion
$application->server->delete();
expect(Application::find($application->id))->toBeNull();
});
```
### Factory Testing
```php
// Testing model factories
test('application factory creates valid models', function () {
$application = Application::factory()->create();
expect($application->name)->toBeString();
expect($application->git_repository)->toStartWith('https://');
expect($application->server_id)->toBeInt();
expect($application->environment_id)->toBeInt();
});
test('application factory can create with custom attributes', function () {
$application = Application::factory()->create([
'name' => 'Custom App',
'git_branch' => 'develop'
]);
expect($application->name)->toBe('Custom App');
expect($application->git_branch)->toBe('develop');
});
```
## Queue Testing
### Job Testing
```php
// Testing background jobs
test('deploy application job processes successfully', function () {
$application = Application::factory()->create();
$deployment = ApplicationDeploymentQueue::factory()->create([
'application_id' => $application->id,
'status' => 'queued'
]);
$job = new DeployApplicationJob($deployment);
// Mock external dependencies
$this->mock(DockerService::class, function ($mock) {
$mock->shouldReceive('buildImage')->andReturn('app:latest');
$mock->shouldReceive('deployContainer')->andReturn(true);
});
$job->handle();
$deployment->refresh();
expect($deployment->status)->toBe('success');
});
test('failed job is retried with exponential backoff', function () {
$application = Application::factory()->create();
$deployment = ApplicationDeploymentQueue::factory()->create([
'application_id' => $application->id
]);
$job = new DeployApplicationJob($deployment);
// Mock failure
$this->mock(DockerService::class, function ($mock) {
$mock->shouldReceive('buildImage')->andThrow(new Exception('Network error'));
});
expect(fn() => $job->handle())->toThrow(Exception::class);
// Job should be retried
expect($job->tries)->toBe(3);
expect($job->backoff())->toBe([1, 5, 10]);
});
```
## Security Testing
### Authentication Testing
```php
// Testing authentication and authorization
test('unauthenticated users cannot access protected routes', function () {
$response = $this->get('/dashboard');
$response->assertRedirect('/login');
});
test('users can only access their team resources', function () {
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$team1 = Team::factory()->create();
$team2 = Team::factory()->create();
$user1->teams()->attach($team1);
$user2->teams()->attach($team2);
$application = Application::factory()->create(['team_id' => $team1->id]);
$response = $this->actingAs($user2)
->get("/applications/{$application->id}");
$response->assertStatus(403);
});
```
### Input Validation Testing
```php
// Testing input validation and sanitization
test('application creation validates required fields', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/v1/applications', []);
$response->assertStatus(422)
->assertJsonValidationErrors(['name', 'git_repository', 'server_id']);
});
test('malicious input is properly sanitized', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/v1/applications', [
'name' => '<script>alert("xss")</script>',
'git_repository' => 'javascript:alert("xss")',
'server_id' => 'invalid'
]);
$response->assertStatus(422);
});
```
## Performance Testing
### Load Testing
```php
// Testing application performance under load
test('application list endpoint handles concurrent requests', function () {
$user = User::factory()->create();
$applications = Application::factory(100)->create(['team_id' => $user->currentTeam->id]);
$startTime = microtime(true);
$response = $this->actingAs($user)
->getJson('/api/v1/applications');
$endTime = microtime(true);
$responseTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
$response->assertStatus(200);
expect($responseTime)->toBeLessThan(500); // Should respond within 500ms
});
```
### Memory Usage Testing
```php
// Testing memory efficiency
test('deployment process does not exceed memory limits', function () {
$initialMemory = memory_get_usage();
$application = Application::factory()->create();
$deployment = $application->deploy();
// Process deployment
$this->artisan('queue:work --once');
$finalMemory = memory_get_usage();
$memoryIncrease = $finalMemory - $initialMemory;
expect($memoryIncrease)->toBeLessThan(50 * 1024 * 1024); // Less than 50MB
});
```
## Test Utilities and Helpers
### Custom Assertions
```php
// Custom test assertions
expect()->extend('toBeValidDockerCompose', function () {
$yaml = yaml_parse($this->value);
return $yaml !== false &&
isset($yaml['version']) &&
isset($yaml['services']) &&
is_array($yaml['services']);
});
expect()->extend('toHaveValidSshConnection', function () {
$server = $this->value;
try {
$connection = new SshConnection($server);
return $connection->test();
} catch (Exception $e) {
return false;
}
});
```
### Test Traits
```php
// Shared testing functionality
trait CreatesTestServers
{
protected function createTestServer(array $attributes = []): Server
{
return Server::factory()->create(array_merge([
'name' => 'Test Server',
'ip' => '127.0.0.1',
'port' => 22,
'team_id' => $this->user->currentTeam->id
], $attributes));
}
}
trait MocksDockerOperations
{
protected function mockDockerService(): void
{
$this->mock(DockerService::class, function ($mock) {
$mock->shouldReceive('buildImage')->andReturn('test:latest');
$mock->shouldReceive('createContainer')->andReturn('container_123');
$mock->shouldReceive('startContainer')->andReturn(true);
$mock->shouldReceive('stopContainer')->andReturn(true);
});
}
}
```
## Continuous Integration Testing
### GitHub Actions Integration
```yaml
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: password
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
- name: Install dependencies
run: composer install
- name: Run tests
run: ./vendor/bin/pest
```
### Test Coverage
```php
// Generate test coverage reports
test('application has adequate test coverage', function () {
$coverage = $this->getCoverageData();
expect($coverage['application'])->toBeGreaterThan(80);
expect($coverage['models'])->toBeGreaterThan(90);
expect($coverage['actions'])->toBeGreaterThan(85);
});
```
-172
View File
@@ -1,172 +0,0 @@
# Maintaining AI Documentation
Guidelines for creating and maintaining AI documentation to ensure consistency and effectiveness across all AI tools (Claude Code, Cursor IDE, etc.).
## Documentation Structure
All AI documentation lives in the `.ai/` directory with the following structure:
```
.ai/
├── README.md # Navigation hub
├── core/ # Core project information
├── development/ # Development practices
├── patterns/ # Code patterns and best practices
└── meta/ # Documentation maintenance guides
```
> **Note**: `CLAUDE.md` is in the repository root, not in the `.ai/` directory.
## Required File Structure
When creating new documentation files:
```markdown
# Title
Brief description of what this document covers.
## Section 1
- **Main Points in Bold**
- Sub-points with details
- Examples and explanations
## Section 2
### Subsection
Content with code examples:
```language
// ✅ DO: Show good examples
const goodExample = true;
// ❌ DON'T: Show anti-patterns
const badExample = false;
```
```
## File References
- Use relative paths: `See [technology-stack.md](../core/technology-stack.md)`
- For code references: `` `app/Models/Application.php` ``
- Keep links working across different tools
## Content Guidelines
### DO:
- Start with high-level overview
- Include specific, actionable requirements
- Show examples of correct implementation
- Reference existing code when possible
- Keep documentation DRY by cross-referencing
- Use bullet points for clarity
- Include both DO and DON'T examples
### DON'T:
- Create theoretical examples when real code exists
- Duplicate content across multiple files
- Use tool-specific formatting that won't work elsewhere
- Make assumptions about versions - specify exact versions
## Rule Improvement Triggers
Update documentation when you notice:
- New code patterns not covered by existing docs
- Repeated similar implementations across files
- Common error patterns that could be prevented
- New libraries or tools being used consistently
- Emerging best practices in the codebase
## Analysis Process
When updating documentation:
1. Compare new code with existing rules
2. Identify patterns that should be standardized
3. Look for references to external documentation
4. Check for consistent error handling patterns
5. Monitor test patterns and coverage
## Rule Updates
### Add New Documentation When:
- A new technology/pattern is used in 3+ files
- Common bugs could be prevented by documentation
- Code reviews repeatedly mention the same feedback
- New security or performance patterns emerge
### Modify Existing Documentation When:
- Better examples exist in the codebase
- Additional edge cases are discovered
- Related documentation has been updated
- Implementation details have changed
## Quality Checks
Before committing documentation changes:
- [ ] Documentation is actionable and specific
- [ ] Examples come from actual code
- [ ] References are up to date
- [ ] Patterns are consistently enforced
- [ ] Cross-references work correctly
- [ ] Version numbers are exact and current
## Continuous Improvement
- Monitor code review comments
- Track common development questions
- Update docs after major refactors
- Add links to relevant documentation
- Cross-reference related docs
## Deprecation
When patterns become outdated:
1. Mark outdated patterns as deprecated
2. Remove docs that no longer apply
3. Update references to deprecated patterns
4. Document migration paths for old patterns
## Synchronization
### Single Source of Truth
- Each piece of information should exist in exactly ONE location
- Other files should reference the source, not duplicate it
- Example: Version numbers live in `core/technology-stack.md`, other files reference it
### Cross-Tool Compatibility
- **CLAUDE.md**: Main instructions for Claude Code users (references `.ai/` files)
- **.cursor/rules/**: Single master file pointing to `.ai/` documentation
- **Both tools**: Should get same information from `.ai/` directory
### When to Update What
**Version Changes** (Laravel, PHP, packages):
1. Update `core/technology-stack.md` (single source)
2. Verify CLAUDE.md references it correctly
3. No other files should duplicate version numbers
**Workflow Changes** (commands, setup):
1. Update `development/workflow.md`
2. Ensure CLAUDE.md quick reference is updated
3. Verify all cross-references work
**Pattern Changes** (how to write code):
1. Update appropriate file in `patterns/`
2. Add/update examples from real codebase
3. Cross-reference from related docs
## Documentation Files
Keep documentation files only when explicitly needed. Don't create docs that merely describe obvious functionality - the code itself should be clear.
## Breaking Changes
When making breaking changes to documentation structure:
1. Update this maintaining-docs.md file
2. Update `.ai/README.md` navigation
3. Update CLAUDE.md references
4. Update `.cursor/rules/coolify-ai-docs.mdc`
5. Test all cross-references still work
6. Document the changes in sync-guide.md
-214
View File
@@ -1,214 +0,0 @@
# AI Instructions Synchronization Guide
This document explains how AI instructions are organized and synchronized across different AI tools used with Coolify.
## Overview
Coolify maintains AI instructions with a **single source of truth** approach:
1. **CLAUDE.md** - Main entry point for Claude Code (references `.ai/` directory)
2. **.cursor/rules/coolify-ai-docs.mdc** - Master reference file for Cursor IDE (references `.ai/` directory)
3. **.ai/** - Single source of truth containing all detailed documentation
All AI tools (Claude Code, Cursor IDE, etc.) reference the same `.ai/` directory to ensure consistency.
## Structure
### CLAUDE.md (Root Directory)
- **Purpose**: Entry point for Claude Code with quick-reference guide
- **Format**: Single markdown file
- **Includes**:
- Quick-reference development commands
- High-level architecture overview
- Essential patterns and guidelines
- References to detailed `.ai/` documentation
### .cursor/rules/coolify-ai-docs.mdc
- **Purpose**: Master reference file for Cursor IDE
- **Format**: Single .mdc file with frontmatter
- **Content**: Quick decision tree and references to `.ai/` directory
- **Note**: Replaces all previous topic-specific .mdc files
### .ai/ Directory (Single Source of Truth)
- **Purpose**: All detailed, topic-specific documentation
- **Format**: Organized markdown files by category
- **Structure**:
```
.ai/
├── README.md # Navigation hub
├── core/ # Project information
│ ├── technology-stack.md # Version numbers (SINGLE SOURCE OF TRUTH)
│ ├── project-overview.md
│ ├── application-architecture.md
│ └── deployment-architecture.md
├── development/ # Development practices
│ ├── development-workflow.md
│ ├── testing-patterns.md
│ └── laravel-boost.md
├── patterns/ # Code patterns
│ ├── database-patterns.md
│ ├── frontend-patterns.md
│ ├── security-patterns.md
│ ├── form-components.md
│ └── api-and-routing.md
└── meta/ # Documentation guides
├── maintaining-docs.md
└── sync-guide.md (this file)
```
- **Used by**: All AI tools through CLAUDE.md or coolify-ai-docs.mdc
## Cross-References
All systems reference the `.ai/` directory as the source of truth:
- **CLAUDE.md** → references `.ai/` files for detailed documentation
- **.cursor/rules/coolify-ai-docs.mdc** → references `.ai/` files for detailed documentation
- **.ai/README.md** → provides navigation to all documentation
## Maintaining Consistency
### 1. Core Principles (MUST be consistent)
These are defined ONCE in `.ai/core/technology-stack.md`:
- Laravel version (currently Laravel 12.4.1)
- PHP version (8.4.7)
- All package versions (Livewire 3.5.20, Tailwind 4.1.4, etc.)
**Exception**: CLAUDE.md is permitted to show essential version numbers as a quick reference for convenience. These must stay synchronized with `technology-stack.md`. When updating versions, update both locations.
Other critical patterns defined in `.ai/`:
- Testing execution rules (Docker for Feature tests, mocking for Unit tests)
- Security patterns and authorization requirements
- Code style requirements (Pint, PSR-12)
### 2. Where to Make Changes
**For version numbers** (Laravel, PHP, packages):
1. Update `.ai/core/technology-stack.md` (single source of truth)
2. Update CLAUDE.md quick reference section (essential versions only)
3. Verify both files stay synchronized
4. Never duplicate version numbers in other locations
**For workflow changes** (how to run commands, development setup):
1. Update `.ai/development/development-workflow.md`
2. Update quick reference in CLAUDE.md if needed
3. Verify `.cursor/rules/coolify-ai-docs.mdc` references are correct
**For architectural patterns** (how code should be structured):
1. Update appropriate file in `.ai/core/`
2. Add cross-references from related docs
3. Update CLAUDE.md if it needs to highlight this pattern
**For code patterns** (how to write code):
1. Update appropriate file in `.ai/patterns/`
2. Add examples from real codebase
3. Cross-reference from related docs
**For testing patterns**:
1. Update `.ai/development/testing-patterns.md`
2. Ensure CLAUDE.md testing section references it
### 3. Update Checklist
When making significant changes:
- [ ] Identify if change affects core principles (version numbers, critical patterns)
- [ ] Update primary location in `.ai/` directory
- [ ] Check if CLAUDE.md needs quick-reference update
- [ ] Verify `.cursor/rules/coolify-ai-docs.mdc` references are still accurate
- [ ] Update cross-references in related `.ai/` files
- [ ] Verify all relative paths work correctly
- [ ] Test links in markdown files
- [ ] Run: `./vendor/bin/pint` on modified files (if applicable)
### 4. Common Inconsistencies to Watch
- **Version numbers**: Should ONLY exist in `.ai/core/technology-stack.md`
- **Testing instructions**: Docker execution requirements must be consistent
- **File paths**: Ensure relative paths work from their location
- **Command syntax**: Docker commands, artisan commands must be accurate
- **Cross-references**: Links must point to current file locations
## File Organization
```
/
├── CLAUDE.md # Claude Code entry point
├── .AI_INSTRUCTIONS_SYNC.md # Redirect to this file
├── .cursor/
│ └── rules/
│ └── coolify-ai-docs.mdc # Cursor IDE master reference
└── .ai/ # SINGLE SOURCE OF TRUTH
├── README.md # Navigation hub
├── core/ # Project information
├── development/ # Development practices
├── patterns/ # Code patterns
└── meta/ # Documentation guides
```
## Recent Updates
### 2025-11-18 - Documentation Consolidation
- ✅ Consolidated all documentation into `.ai/` directory
- ✅ Created single source of truth for version numbers
- ✅ Reduced CLAUDE.md from 719 to 319 lines
- ✅ Replaced 11 .cursor/rules/*.mdc files with single coolify-ai-docs.mdc
- ✅ Organized by topic: core/, development/, patterns/, meta/
- ✅ Standardized version numbers (Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4)
- ✅ Created comprehensive navigation with .ai/README.md
### 2025-10-07
- ✅ Added cross-references between CLAUDE.md and .cursor/rules/
- ✅ Synchronized Laravel version (12) across all files
- ✅ Added comprehensive testing execution rules (Docker for Feature tests)
- ✅ Added test design philosophy (prefer mocking over database)
- ✅ Fixed inconsistencies in testing documentation
## Maintenance Commands
```bash
# Check for version inconsistencies (should only be in technology-stack.md)
# Note: CLAUDE.md is allowed to show quick reference versions
grep -r "Laravel 12" .ai/ CLAUDE.md .cursor/rules/coolify-ai-docs.mdc
grep -r "PHP 8.4" .ai/ CLAUDE.md .cursor/rules/coolify-ai-docs.mdc
# Check for broken cross-references to old .mdc files
grep -r "\.cursor/rules/.*\.mdc" .ai/ CLAUDE.md
# Format all documentation
./vendor/bin/pint CLAUDE.md .ai/**/*.md
# Search for specific patterns across all docs
grep -r "pattern_to_check" CLAUDE.md .ai/ .cursor/rules/
# Verify all markdown links work (from repository root)
find .ai -name "*.md" -exec grep -H "\[.*\](.*)" {} \;
```
## Contributing
When contributing documentation:
1. **Check `.ai/` directory** for existing documentation
2. **Update `.ai/` files** - this is the single source of truth
3. **Use cross-references** - never duplicate content
4. **Update CLAUDE.md** if adding critical quick-reference information
5. **Verify `.cursor/rules/coolify-ai-docs.mdc`** still references correctly
6. **Test all links** work from their respective locations
7. **Update this sync-guide.md** if changing organizational structure
8. **Verify consistency** before submitting PR
## Questions?
If unsure about where to document something:
- **Version numbers** → `.ai/core/technology-stack.md` (ONLY location)
- **Quick reference / commands** → CLAUDE.md + `.ai/development/development-workflow.md`
- **Detailed patterns / examples** → `.ai/patterns/[topic].md`
- **Architecture / concepts** → `.ai/core/[topic].md`
- **Development practices** → `.ai/development/[topic].md`
- **Documentation guides** → `.ai/meta/[topic].md`
**Golden Rule**: Each piece of information exists in ONE location in `.ai/`, other files reference it.
When in doubt, prefer detailed documentation in `.ai/` and lightweight references in CLAUDE.md and coolify-ai-docs.mdc.
-469
View File
@@ -1,469 +0,0 @@
# Coolify API & Routing Architecture
## Routing Structure
Coolify implements **multi-layered routing** with web interfaces, RESTful APIs, webhook endpoints, and real-time communication channels.
## Route Files
### Core Route Definitions
- **[routes/web.php](mdc:routes/web.php)** - Web application routes (21KB, 362 lines)
- **[routes/api.php](mdc:routes/api.php)** - RESTful API endpoints (13KB, 185 lines)
- **[routes/webhooks.php](mdc:routes/webhooks.php)** - Webhook receivers (815B, 22 lines)
- **[routes/channels.php](mdc:routes/channels.php)** - WebSocket channel definitions (829B, 33 lines)
- **[routes/console.php](mdc:routes/console.php)** - Artisan command routes (592B, 20 lines)
## Web Application Routing
### Authentication Routes
```php
// Laravel Fortify authentication
Route::middleware('guest')->group(function () {
Route::get('/login', [AuthController::class, 'login']);
Route::get('/register', [AuthController::class, 'register']);
Route::get('/forgot-password', [AuthController::class, 'forgotPassword']);
});
```
### Dashboard & Core Features
```php
// Main application routes
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', Dashboard::class)->name('dashboard');
Route::get('/projects', ProjectIndex::class)->name('projects');
Route::get('/servers', ServerIndex::class)->name('servers');
Route::get('/teams', TeamIndex::class)->name('teams');
});
```
### Resource Management Routes
```php
// Server management
Route::prefix('servers')->group(function () {
Route::get('/{server}', ServerShow::class)->name('server.show');
Route::get('/{server}/edit', ServerEdit::class)->name('server.edit');
Route::get('/{server}/logs', ServerLogs::class)->name('server.logs');
});
// Application management
Route::prefix('applications')->group(function () {
Route::get('/{application}', ApplicationShow::class)->name('application.show');
Route::get('/{application}/deployments', ApplicationDeployments::class);
Route::get('/{application}/environment-variables', ApplicationEnvironmentVariables::class);
Route::get('/{application}/logs', ApplicationLogs::class);
});
```
## RESTful API Architecture
### API Versioning
```php
// API route structure
Route::prefix('v1')->group(function () {
// Application endpoints
Route::apiResource('applications', ApplicationController::class);
Route::apiResource('servers', ServerController::class);
Route::apiResource('teams', TeamController::class);
});
```
### Authentication & Authorization
```php
// Sanctum API authentication
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
// Team-scoped resources
Route::middleware('team.access')->group(function () {
Route::apiResource('applications', ApplicationController::class);
});
});
```
### Application Management API
```php
// Application CRUD operations
Route::prefix('applications')->group(function () {
Route::get('/', [ApplicationController::class, 'index']);
Route::post('/', [ApplicationController::class, 'store']);
Route::get('/{application}', [ApplicationController::class, 'show']);
Route::patch('/{application}', [ApplicationController::class, 'update']);
Route::delete('/{application}', [ApplicationController::class, 'destroy']);
// Deployment operations
Route::post('/{application}/deploy', [ApplicationController::class, 'deploy']);
Route::post('/{application}/restart', [ApplicationController::class, 'restart']);
Route::post('/{application}/stop', [ApplicationController::class, 'stop']);
Route::get('/{application}/logs', [ApplicationController::class, 'logs']);
});
```
### Server Management API
```php
// Server operations
Route::prefix('servers')->group(function () {
Route::get('/', [ServerController::class, 'index']);
Route::post('/', [ServerController::class, 'store']);
Route::get('/{server}', [ServerController::class, 'show']);
Route::patch('/{server}', [ServerController::class, 'update']);
Route::delete('/{server}', [ServerController::class, 'destroy']);
// Server actions
Route::post('/{server}/validate', [ServerController::class, 'validate']);
Route::get('/{server}/usage', [ServerController::class, 'usage']);
Route::post('/{server}/cleanup', [ServerController::class, 'cleanup']);
});
```
### Database Management API
```php
// Database operations
Route::prefix('databases')->group(function () {
Route::get('/', [DatabaseController::class, 'index']);
Route::post('/', [DatabaseController::class, 'store']);
Route::get('/{database}', [DatabaseController::class, 'show']);
Route::patch('/{database}', [DatabaseController::class, 'update']);
Route::delete('/{database}', [DatabaseController::class, 'destroy']);
// Database actions
Route::post('/{database}/backup', [DatabaseController::class, 'backup']);
Route::post('/{database}/restore', [DatabaseController::class, 'restore']);
Route::get('/{database}/logs', [DatabaseController::class, 'logs']);
});
```
## Webhook Architecture
### Git Integration Webhooks
```php
// GitHub webhook endpoints
Route::post('/webhooks/github/{application}', [GitHubWebhookController::class, 'handle'])
->name('webhooks.github');
// GitLab webhook endpoints
Route::post('/webhooks/gitlab/{application}', [GitLabWebhookController::class, 'handle'])
->name('webhooks.gitlab');
// Generic Git webhooks
Route::post('/webhooks/git/{application}', [GitWebhookController::class, 'handle'])
->name('webhooks.git');
```
### Deployment Webhooks
```php
// Deployment status webhooks
Route::post('/webhooks/deployment/{deployment}/success', [DeploymentWebhookController::class, 'success']);
Route::post('/webhooks/deployment/{deployment}/failure', [DeploymentWebhookController::class, 'failure']);
Route::post('/webhooks/deployment/{deployment}/progress', [DeploymentWebhookController::class, 'progress']);
```
### Third-Party Integration Webhooks
```php
// Monitoring webhooks
Route::post('/webhooks/monitoring/{server}', [MonitoringWebhookController::class, 'handle']);
// Backup status webhooks
Route::post('/webhooks/backup/{backup}', [BackupWebhookController::class, 'handle']);
// SSL certificate webhooks
Route::post('/webhooks/ssl/{certificate}', [SslWebhookController::class, 'handle']);
```
## WebSocket Channel Definitions
### Real-Time Channels
```php
// Private channels for team members
Broadcast::channel('team.{teamId}', function ($user, $teamId) {
return $user->teams->contains('id', $teamId);
});
// Application deployment channels
Broadcast::channel('application.{applicationId}', function ($user, $applicationId) {
return $user->hasAccessToApplication($applicationId);
});
// Server monitoring channels
Broadcast::channel('server.{serverId}', function ($user, $serverId) {
return $user->hasAccessToServer($serverId);
});
```
### Presence Channels
```php
// Team collaboration presence
Broadcast::channel('team.{teamId}.presence', function ($user, $teamId) {
if ($user->teams->contains('id', $teamId)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
```
## API Controllers
### Location: [app/Http/Controllers/Api/](mdc:app/Http/Controllers)
#### Resource Controllers
```php
class ApplicationController extends Controller
{
public function index(Request $request)
{
return ApplicationResource::collection(
$request->user()->currentTeam->applications()
->with(['server', 'environment'])
->paginate()
);
}
public function store(StoreApplicationRequest $request)
{
$application = $request->user()->currentTeam
->applications()
->create($request->validated());
return new ApplicationResource($application);
}
public function deploy(Application $application)
{
$deployment = $application->deploy();
return response()->json([
'message' => 'Deployment started',
'deployment_id' => $deployment->id
]);
}
}
```
### API Responses & Resources
```php
// API Resource classes
class ApplicationResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'fqdn' => $this->fqdn,
'status' => $this->status,
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'server' => new ServerResource($this->whenLoaded('server')),
'environment' => new EnvironmentResource($this->whenLoaded('environment')),
];
}
}
```
## API Authentication
### Sanctum Token Authentication
```php
// API token generation
Route::post('/auth/tokens', function (Request $request) {
$request->validate([
'name' => 'required|string',
'abilities' => 'array'
]);
$token = $request->user()->createToken(
$request->name,
$request->abilities ?? []
);
return response()->json([
'token' => $token->plainTextToken,
'abilities' => $token->accessToken->abilities
]);
});
```
### Team-Based Authorization
```php
// Team access middleware
class EnsureTeamAccess
{
public function handle($request, Closure $next)
{
$teamId = $request->route('team');
if (!$request->user()->teams->contains('id', $teamId)) {
abort(403, 'Access denied to team resources');
}
return $next($request);
}
}
```
## Rate Limiting
### API Rate Limits
```php
// API throttling configuration
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Deployment rate limiting
RateLimiter::for('deployments', function (Request $request) {
return Limit::perMinute(10)->by($request->user()->id);
});
```
### Webhook Rate Limiting
```php
// Webhook throttling
RateLimiter::for('webhooks', function (Request $request) {
return Limit::perMinute(100)->by($request->ip());
});
```
## Route Model Binding
### Custom Route Bindings
```php
// Custom model binding for applications
Route::bind('application', function ($value) {
return Application::where('uuid', $value)
->orWhere('id', $value)
->firstOrFail();
});
// Team-scoped model binding
Route::bind('team_application', function ($value, $route) {
$teamId = $route->parameter('team');
return Application::whereHas('environment.project', function ($query) use ($teamId) {
$query->where('team_id', $teamId);
})->findOrFail($value);
});
```
## API Documentation
### OpenAPI Specification
- **[openapi.json](mdc:openapi.json)** - API documentation (373KB, 8316 lines)
- **[openapi.yaml](mdc:openapi.yaml)** - YAML format documentation (184KB, 5579 lines)
### Documentation Generation
```php
// Swagger/OpenAPI annotations
/**
* @OA\Get(
* path="/api/v1/applications",
* summary="List applications",
* tags={"Applications"},
* security={{"bearerAuth":{}}},
* @OA\Response(
* response=200,
* description="List of applications",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Application"))
* )
* )
*/
```
## Error Handling
### API Error Responses
```php
// Standardized error response format
class ApiExceptionHandler
{
public function render($request, Throwable $exception)
{
if ($request->expectsJson()) {
return response()->json([
'message' => $exception->getMessage(),
'error_code' => $this->getErrorCode($exception),
'timestamp' => now()->toISOString()
], $this->getStatusCode($exception));
}
return parent::render($request, $exception);
}
}
```
### Validation Error Handling
```php
// Form request validation
class StoreApplicationRequest extends FormRequest
{
public function rules()
{
return [
'name' => 'required|string|max:255',
'git_repository' => 'required|url',
'git_branch' => 'required|string',
'server_id' => 'required|exists:servers,id',
'environment_id' => 'required|exists:environments,id'
];
}
public function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'message' => 'Validation failed',
'errors' => $validator->errors()
], 422)
);
}
}
```
## Real-Time API Integration
### WebSocket Events
```php
// Broadcasting deployment events
class DeploymentStarted implements ShouldBroadcast
{
public $application;
public $deployment;
public function broadcastOn()
{
return [
new PrivateChannel("application.{$this->application->id}"),
new PrivateChannel("team.{$this->application->team->id}")
];
}
public function broadcastWith()
{
return [
'deployment_id' => $this->deployment->id,
'status' => 'started',
'timestamp' => now()
];
}
}
```
### API Event Streaming
```php
// Server-Sent Events for real-time updates
Route::get('/api/v1/applications/{application}/events', function (Application $application) {
return response()->stream(function () use ($application) {
while (true) {
$events = $application->getRecentEvents();
foreach ($events as $event) {
echo "data: " . json_encode($event) . "\n\n";
}
usleep(1000000); // 1 second
}
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
]);
});
```
-377
View File
@@ -1,377 +0,0 @@
# Coolify Database Architecture & Patterns
## Database Strategy
Coolify uses **PostgreSQL 15** as the primary database with **Redis 7** for caching and real-time features. The architecture supports managing multiple external databases across different servers.
## Primary Database (PostgreSQL)
### Core Tables & Models
#### User & Team Management
- **[User.php](mdc:app/Models/User.php)** - User authentication and profiles
- **[Team.php](mdc:app/Models/Team.php)** - Multi-tenant organization structure
- **[TeamInvitation.php](mdc:app/Models/TeamInvitation.php)** - Team collaboration invitations
- **[PersonalAccessToken.php](mdc:app/Models/PersonalAccessToken.php)** - API token management
#### Infrastructure Management
- **[Server.php](mdc:app/Models/Server.php)** - Physical/virtual server definitions (46KB, complex)
- **[PrivateKey.php](mdc:app/Models/PrivateKey.php)** - SSH key management
- **[ServerSetting.php](mdc:app/Models/ServerSetting.php)** - Server-specific configurations
#### Project Organization
- **[Project.php](mdc:app/Models/Project.php)** - Project containers for applications
- **[Environment.php](mdc:app/Models/Environment.php)** - Environment isolation (staging, production, etc.)
- **[ProjectSetting.php](mdc:app/Models/ProjectSetting.php)** - Project-specific settings
#### Application Deployment
- **[Application.php](mdc:app/Models/Application.php)** - Main application entity (74KB, highly complex)
- **[ApplicationSetting.php](mdc:app/Models/ApplicationSetting.php)** - Application configurations
- **[ApplicationDeploymentQueue.php](mdc:app/Models/ApplicationDeploymentQueue.php)** - Deployment orchestration
- **[ApplicationPreview.php](mdc:app/Models/ApplicationPreview.php)** - Preview environment management
#### Service Management
- **[Service.php](mdc:app/Models/Service.php)** - Service definitions (58KB, complex)
- **[ServiceApplication.php](mdc:app/Models/ServiceApplication.php)** - Service components
- **[ServiceDatabase.php](mdc:app/Models/ServiceDatabase.php)** - Service-attached databases
## Database Type Support
### Standalone Database Models
Each database type has its own dedicated model with specific configurations:
#### SQL Databases
- **[StandalonePostgresql.php](mdc:app/Models/StandalonePostgresql.php)** - PostgreSQL instances
- **[StandaloneMysql.php](mdc:app/Models/StandaloneMysql.php)** - MySQL instances
- **[StandaloneMariadb.php](mdc:app/Models/StandaloneMariadb.php)** - MariaDB instances
#### NoSQL & Analytics
- **[StandaloneMongodb.php](mdc:app/Models/StandaloneMongodb.php)** - MongoDB instances
- **[StandaloneClickhouse.php](mdc:app/Models/StandaloneClickhouse.php)** - ClickHouse analytics
#### Caching & In-Memory
- **[StandaloneRedis.php](mdc:app/Models/StandaloneRedis.php)** - Redis instances
- **[StandaloneKeydb.php](mdc:app/Models/StandaloneKeydb.php)** - KeyDB instances
- **[StandaloneDragonfly.php](mdc:app/Models/StandaloneDragonfly.php)** - Dragonfly instances
## Configuration Management
### Environment Variables
- **[EnvironmentVariable.php](mdc:app/Models/EnvironmentVariable.php)** - Application-specific environment variables
- **[SharedEnvironmentVariable.php](mdc:app/Models/SharedEnvironmentVariable.php)** - Shared across applications
### Settings Hierarchy
- **[InstanceSettings.php](mdc:app/Models/InstanceSettings.php)** - Global Coolify instance settings
- **[ServerSetting.php](mdc:app/Models/ServerSetting.php)** - Server-specific settings
- **[ProjectSetting.php](mdc:app/Models/ProjectSetting.php)** - Project-level settings
- **[ApplicationSetting.php](mdc:app/Models/ApplicationSetting.php)** - Application settings
## Storage & Backup Systems
### Storage Management
- **[S3Storage.php](mdc:app/Models/S3Storage.php)** - S3-compatible storage configurations
- **[LocalFileVolume.php](mdc:app/Models/LocalFileVolume.php)** - Local filesystem volumes
- **[LocalPersistentVolume.php](mdc:app/Models/LocalPersistentVolume.php)** - Persistent volume management
### Backup Infrastructure
- **[ScheduledDatabaseBackup.php](mdc:app/Models/ScheduledDatabaseBackup.php)** - Automated backup scheduling
- **[ScheduledDatabaseBackupExecution.php](mdc:app/Models/ScheduledDatabaseBackupExecution.php)** - Backup execution tracking
### Task Scheduling
- **[ScheduledTask.php](mdc:app/Models/ScheduledTask.php)** - Cron job management
- **[ScheduledTaskExecution.php](mdc:app/Models/ScheduledTaskExecution.php)** - Task execution history
## Notification & Integration Models
### Notification Channels
- **[EmailNotificationSettings.php](mdc:app/Models/EmailNotificationSettings.php)** - Email notifications
- **[DiscordNotificationSettings.php](mdc:app/Models/DiscordNotificationSettings.php)** - Discord integration
- **[SlackNotificationSettings.php](mdc:app/Models/SlackNotificationSettings.php)** - Slack integration
- **[TelegramNotificationSettings.php](mdc:app/Models/TelegramNotificationSettings.php)** - Telegram bot
- **[PushoverNotificationSettings.php](mdc:app/Models/PushoverNotificationSettings.php)** - Pushover notifications
### Source Control Integration
- **[GithubApp.php](mdc:app/Models/GithubApp.php)** - GitHub App integration
- **[GitlabApp.php](mdc:app/Models/GitlabApp.php)** - GitLab integration
### OAuth & Authentication
- **[OauthSetting.php](mdc:app/Models/OauthSetting.php)** - OAuth provider configurations
## Docker & Container Management
### Container Orchestration
- **[StandaloneDocker.php](mdc:app/Models/StandaloneDocker.php)** - Standalone Docker containers
- **[SwarmDocker.php](mdc:app/Models/SwarmDocker.php)** - Docker Swarm management
### SSL & Security
- **[SslCertificate.php](mdc:app/Models/SslCertificate.php)** - SSL certificate management
## Database Migration Strategy
### Migration Location: [database/migrations/](mdc:database/migrations)
#### Migration Patterns
```php
// Typical Coolify migration structure
Schema::create('applications', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('fqdn')->nullable();
$table->json('environment_variables')->nullable();
$table->foreignId('destination_id');
$table->foreignId('source_id');
$table->timestamps();
});
```
### Schema Versioning
- **Incremental migrations** for database evolution
- **Data migrations** for complex transformations
- **Rollback support** for deployment safety
## Eloquent Model Patterns
### Base Model Structure
- **[BaseModel.php](mdc:app/Models/BaseModel.php)** - Common model functionality
- **UUID primary keys** for distributed systems
- **Soft deletes** for audit trails
- **Activity logging** with Spatie package
### **CRITICAL: Mass Assignment Protection**
**When adding new database columns, you MUST update the model's `$fillable` array.** Without this, Laravel will silently ignore mass assignment operations like `Model::create()` or `$model->update()`.
**Checklist for new columns:**
1. ✅ Create migration file
2. ✅ Run migration
3. ✅ **Add column to model's `$fillable` array**
4. ✅ Update any Livewire components that sync this property
5. ✅ Test that the column can be read and written
**Example:**
```php
class Server extends BaseModel
{
protected $fillable = [
'name',
'ip',
'port',
'is_validating', // ← MUST add new columns here
];
}
```
### Relationship Patterns
```php
// Typical relationship structure in Application model
class Application extends Model
{
public function server()
{
return $this->belongsTo(Server::class);
}
public function environment()
{
return $this->belongsTo(Environment::class);
}
public function deployments()
{
return $this->hasMany(ApplicationDeploymentQueue::class);
}
public function environmentVariables()
{
return $this->hasMany(EnvironmentVariable::class);
}
}
```
### Model Traits
```php
// Common traits used across models
use SoftDeletes;
use LogsActivity;
use HasFactory;
use HasUuids;
```
## Caching Strategy (Redis)
### Cache Usage Patterns
- **Session storage** - User authentication sessions
- **Queue backend** - Background job processing
- **Model caching** - Expensive query results
- **Real-time data** - WebSocket state management
### Cache Keys Structure
```
coolify:session:{session_id}
coolify:server:{server_id}:status
coolify:deployment:{deployment_id}:logs
coolify:user:{user_id}:teams
```
## Query Optimization Patterns
### Eager Loading
```php
// Optimized queries with relationships
$applications = Application::with([
'server',
'environment.project',
'environmentVariables',
'deployments' => function ($query) {
$query->latest()->limit(5);
}
])->get();
```
### Chunking for Large Datasets
```php
// Processing large datasets efficiently
Server::chunk(100, function ($servers) {
foreach ($servers as $server) {
// Process server monitoring
}
});
```
### Database Indexes
- **Primary keys** on all tables
- **Foreign key indexes** for relationships
- **Composite indexes** for common queries
- **Unique constraints** for business rules
### Request-Level Caching with ownedByCurrentTeamCached()
Many models have both `ownedByCurrentTeam()` (returns query builder) and `ownedByCurrentTeamCached()` (returns cached collection). **Always prefer the cached version** to avoid duplicate database queries within the same request.
**Models with cached methods available:**
- `Server`, `PrivateKey`, `Project`
- `Application`
- `StandalonePostgresql`, `StandaloneMysql`, `StandaloneRedis`, `StandaloneMariadb`, `StandaloneMongodb`, `StandaloneKeydb`, `StandaloneDragonfly`, `StandaloneClickhouse`
- `Service`, `ServiceApplication`, `ServiceDatabase`
**Usage patterns:**
```php
// ✅ CORRECT - Uses request-level cache (via Laravel's once() helper)
$servers = Server::ownedByCurrentTeamCached();
// ❌ AVOID - Makes a new database query each time
$servers = Server::ownedByCurrentTeam()->get();
// ✅ CORRECT - Filter cached collection in memory
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);
$server = Server::ownedByCurrentTeamCached()->firstWhere('id', $serverId);
$serverIds = Server::ownedByCurrentTeamCached()->pluck('id');
// ❌ AVOID - Making filtered database queries when data is already cached
$activeServers = Server::ownedByCurrentTeam()->where('is_active', true)->get();
```
**When to use which:**
- `ownedByCurrentTeamCached()` - **Default choice** for reading team data
- `ownedByCurrentTeam()` - Only when you need to chain query builder methods that can't be done on collections (like `with()` for eager loading), or when you explicitly need a fresh database query
**Implementation pattern for new models:**
```php
/**
* Get query builder for resources owned by current team.
* If you need all resources without further query chaining, use ownedByCurrentTeamCached() instead.
*/
public static function ownedByCurrentTeam()
{
return self::whereTeamId(currentTeam()->id);
}
/**
* Get all resources owned by current team (cached for request duration).
*/
public static function ownedByCurrentTeamCached()
{
return once(function () {
return self::ownedByCurrentTeam()->get();
});
}
```
## Data Consistency Patterns
### Database Transactions
```php
// Atomic operations for deployment
DB::transaction(function () {
$application = Application::create($data);
$application->environmentVariables()->createMany($envVars);
$application->deployments()->create(['status' => 'queued']);
});
```
### Model Events
```php
// Automatic cleanup on model deletion
class Application extends Model
{
protected static function booted()
{
static::deleting(function ($application) {
$application->environmentVariables()->delete();
$application->deployments()->delete();
});
}
}
```
## Backup & Recovery
### Database Backup Strategy
- **Automated PostgreSQL backups** via scheduled tasks
- **Point-in-time recovery** capability
- **Cross-region backup** replication
- **Backup verification** and testing
### Data Export/Import
- **Application configurations** export/import
- **Environment variable** bulk operations
- **Server configurations** backup and restore
## Performance Monitoring
### Query Performance
- **Laravel Telescope** for development debugging
- **Slow query logging** in production
- **Database connection** pooling
- **Read replica** support for scaling
### Metrics Collection
- **Database size** monitoring
- **Connection count** tracking
- **Query execution time** analysis
- **Cache hit rates** monitoring
## Multi-Tenancy Pattern
### Team-Based Isolation
```php
// Global scope for team-based filtering
class Application extends Model
{
protected static function booted()
{
static::addGlobalScope('team', function (Builder $builder) {
if (auth()->user()) {
$builder->whereHas('environment.project', function ($query) {
$query->where('team_id', auth()->user()->currentTeam->id);
});
}
});
}
}
```
### Data Separation
- **Team-scoped queries** by default
- **Cross-team access** controls
- **Admin access** patterns
- **Data isolation** guarantees
-447
View File
@@ -1,447 +0,0 @@
# Enhanced Form Components with Authorization
## Overview
Coolify's form components now feature **built-in authorization** that automatically handles permission-based UI control, dramatically reducing code duplication and improving security consistency.
## Enhanced Components
All form components now support the `canGate` authorization system:
- **[Input.php](mdc:app/View/Components/Forms/Input.php)** - Text, password, and other input fields
- **[Select.php](mdc:app/View/Components/Forms/Select.php)** - Dropdown selection components
- **[Textarea.php](mdc:app/View/Components/Forms/Textarea.php)** - Multi-line text areas
- **[Checkbox.php](mdc:app/View/Components/Forms/Checkbox.php)** - Boolean toggle components
- **[Button.php](mdc:app/View/Components/Forms/Button.php)** - Action buttons
## Authorization Parameters
### Core Parameters
```php
public ?string $canGate = null; // Gate name: 'update', 'view', 'deploy', 'delete'
public mixed $canResource = null; // Resource model instance to check against
public bool $autoDisable = true; // Automatically disable if no permission
```
### How It Works
```php
// Automatic authorization logic in each component
if ($this->canGate && $this->canResource && $this->autoDisable) {
$hasPermission = Gate::allows($this->canGate, $this->canResource);
if (! $hasPermission) {
$this->disabled = true;
// For Checkbox: also sets $this->instantSave = false;
}
}
```
## Usage Patterns
### ✅ Recommended: Single Line Pattern
**Before (Verbose, 6+ lines per element):**
```html
@can('update', $application)
<x-forms.input id="application.name" label="Name" />
<x-forms.checkbox instantSave id="application.settings.is_static" label="Static Site" />
<x-forms.button type="submit">Save</x-forms.button>
@else
<x-forms.input disabled id="application.name" label="Name" />
<x-forms.checkbox disabled id="application.settings.is_static" label="Static Site" />
@endcan
```
**After (Clean, 1 line per element):**
```html
<x-forms.input canGate="update" :canResource="$application" id="application.name" label="Name" />
<x-forms.checkbox instantSave canGate="update" :canResource="$application" id="application.settings.is_static" label="Static Site" />
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
```
**Result: 90% code reduction!**
### Component-Specific Examples
#### Input Fields
```html
<!-- Basic input with authorization -->
<x-forms.input
canGate="update"
:canResource="$application"
id="application.name"
label="Application Name" />
<!-- Password input with authorization -->
<x-forms.input
type="password"
canGate="update"
:canResource="$application"
id="application.database_password"
label="Database Password" />
<!-- Required input with authorization -->
<x-forms.input
required
canGate="update"
:canResource="$application"
id="application.fqdn"
label="Domain" />
```
#### Select Dropdowns
```html
<!-- Build pack selection -->
<x-forms.select
canGate="update"
:canResource="$application"
id="application.build_pack"
label="Build Pack"
required>
<option value="nixpacks">Nixpacks</option>
<option value="static">Static</option>
<option value="dockerfile">Dockerfile</option>
</x-forms.select>
<!-- Server selection -->
<x-forms.select
canGate="createAnyResource"
:canResource="auth()->user()->currentTeam"
id="server_id"
label="Target Server">
@foreach($servers as $server)
<option value="{{ $server->id }}">{{ $server->name }}</option>
@endforeach
</x-forms.select>
```
#### Checkboxes with InstantSave
```html
<!-- Static site toggle -->
<x-forms.checkbox
instantSave
canGate="update"
:canResource="$application"
id="application.settings.is_static"
label="Is it a static site?"
helper="Enable if your application serves static files" />
<!-- Debug mode toggle -->
<x-forms.checkbox
instantSave
canGate="update"
:canResource="$application"
id="application.settings.is_debug_enabled"
label="Debug Mode"
helper="Enable debug logging for troubleshooting" />
<!-- Build server toggle -->
<x-forms.checkbox
instantSave
canGate="update"
:canResource="$application"
id="application.settings.is_build_server_enabled"
label="Use Build Server"
helper="Use a dedicated build server for compilation" />
```
#### Textareas
```html
<!-- Configuration textarea -->
<x-forms.textarea
canGate="update"
:canResource="$application"
id="application.docker_compose_raw"
label="Docker Compose Configuration"
rows="10"
monacoEditorLanguage="yaml"
useMonacoEditor />
<!-- Custom commands -->
<x-forms.textarea
canGate="update"
:canResource="$application"
id="application.post_deployment_command"
label="Post-Deployment Commands"
placeholder="php artisan migrate"
helper="Commands to run after deployment" />
```
#### Buttons
```html
<!-- Save button -->
<x-forms.button
canGate="update"
:canResource="$application"
type="submit">
Save Configuration
</x-forms.button>
<!-- Deploy button -->
<x-forms.button
canGate="deploy"
:canResource="$application"
wire:click="deploy">
Deploy Application
</x-forms.button>
<!-- Delete button -->
<x-forms.button
canGate="delete"
:canResource="$application"
wire:click="confirmDelete"
class="button-danger">
Delete Application
</x-forms.button>
```
## Advanced Usage
### Custom Authorization Logic
```html
<!-- Disable auto-control for complex permissions -->
<x-forms.input
canGate="update"
:canResource="$application"
autoDisable="false"
:disabled="$application->is_deployed || !$application->canModifySettings()"
id="deployment.setting"
label="Advanced Setting" />
```
### Multiple Permission Checks
```html
<!-- Combine multiple authorization requirements -->
<x-forms.checkbox
canGate="deploy"
:canResource="$application"
autoDisable="false"
:disabled="!$application->hasDockerfile() || !Gate::allows('deploy', $application)"
id="docker.setting"
label="Docker-Specific Setting" />
```
### Conditional Resources
```html
<!-- Different resources based on context -->
<x-forms.button
:canGate="$isEditing ? 'update' : 'view'"
:canResource="$resource"
type="submit">
{{ $isEditing ? 'Save Changes' : 'View Details' }}
</x-forms.button>
```
## Supported Gates
### Resource-Level Gates
- `view` - Read access to resource details
- `update` - Modify resource configuration and settings
- `deploy` - Deploy, restart, or manage resource state
- `delete` - Remove or destroy resource
- `clone` - Duplicate resource to another location
### Global Gates
- `createAnyResource` - Create new resources of any type
- `manageTeam` - Team administration permissions
- `accessServer` - Server-level access permissions
## Supported Resources
### Primary Resources
- `$application` - Application instances and configurations
- `$service` - Docker Compose services and components
- `$database` - Database instances (PostgreSQL, MySQL, etc.)
- `$server` - Physical or virtual server instances
### Container Resources
- `$project` - Project containers and environments
- `$environment` - Environment-specific configurations
- `$team` - Team and organization contexts
### Infrastructure Resources
- `$privateKey` - SSH private keys and certificates
- `$source` - Git sources and repositories
- `$destination` - Deployment destinations and targets
## Component Behavior
### Input Components (Input, Select, Textarea)
When authorization fails:
- **disabled = true** - Field becomes non-editable
- **Visual styling** - Opacity reduction and disabled cursor
- **Form submission** - Values are ignored in forms
- **User feedback** - Clear visual indication of restricted access
### Checkbox Components
When authorization fails:
- **disabled = true** - Checkbox becomes non-clickable
- **instantSave = false** - Automatic saving is disabled
- **State preservation** - Current value is maintained but read-only
- **Visual styling** - Disabled appearance with reduced opacity
### Button Components
When authorization fails:
- **disabled = true** - Button becomes non-clickable
- **Event blocking** - Click handlers are ignored
- **Visual styling** - Disabled appearance and cursor
- **Loading states** - Loading indicators are disabled
## Migration Guide
### Converting Existing Forms
**Old Pattern:**
```html
<form wire:submit='submit'>
@can('update', $application)
<x-forms.input id="name" label="Name" />
<x-forms.select id="type" label="Type">...</x-forms.select>
<x-forms.checkbox instantSave id="enabled" label="Enabled" />
<x-forms.button type="submit">Save</x-forms.button>
@else
<x-forms.input disabled id="name" label="Name" />
<x-forms.select disabled id="type" label="Type">...</x-forms.select>
<x-forms.checkbox disabled id="enabled" label="Enabled" />
@endcan
</form>
```
**New Pattern:**
```html
<form wire:submit='submit'>
<x-forms.input canGate="update" :canResource="$application" id="name" label="Name" />
<x-forms.select canGate="update" :canResource="$application" id="type" label="Type">...</x-forms.select>
<x-forms.checkbox instantSave canGate="update" :canResource="$application" id="enabled" label="Enabled" />
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
</form>
```
### Gradual Migration Strategy
1. **Start with new forms** - Use the new pattern for all new components
2. **Convert high-traffic areas** - Migrate frequently used forms first
3. **Batch convert similar forms** - Group similar authorization patterns
4. **Test thoroughly** - Verify authorization behavior matches expectations
5. **Remove old patterns** - Clean up legacy @can/@else blocks
## Testing Patterns
### Component Authorization Tests
```php
// Test authorization integration in components
test('input component respects authorization', function () {
$user = User::factory()->member()->create();
$application = Application::factory()->create();
// Member should see disabled input
$component = Livewire::actingAs($user)
->test(TestComponent::class, [
'canGate' => 'update',
'canResource' => $application
]);
expect($component->get('disabled'))->toBeTrue();
});
test('checkbox disables instantSave for unauthorized users', function () {
$user = User::factory()->member()->create();
$application = Application::factory()->create();
$component = Livewire::actingAs($user)
->test(CheckboxComponent::class, [
'instantSave' => true,
'canGate' => 'update',
'canResource' => $application
]);
expect($component->get('disabled'))->toBeTrue();
expect($component->get('instantSave'))->toBeFalse();
});
```
### Integration Tests
```php
// Test full form authorization behavior
test('application form respects member permissions', function () {
$member = User::factory()->member()->create();
$application = Application::factory()->create();
$this->actingAs($member)
->get(route('application.edit', $application))
->assertSee('disabled')
->assertDontSee('Save Configuration');
});
```
## Best Practices
### Consistent Gate Usage
- Use `update` for configuration changes
- Use `deploy` for operational actions
- Use `view` for read-only access
- Use `delete` for destructive actions
### Resource Context
- Always pass the specific resource being acted upon
- Use team context for creation permissions
- Consider nested resource relationships
### Error Handling
- Provide clear feedback for disabled components
- Use helper text to explain permission requirements
- Consider tooltips for disabled buttons
### Performance
- Authorization checks are cached per request
- Use eager loading for resource relationships
- Consider query optimization for complex permissions
## Common Patterns
### Application Configuration Forms
```html
<!-- Application settings with consistent authorization -->
<x-forms.input canGate="update" :canResource="$application" id="application.name" label="Name" />
<x-forms.select canGate="update" :canResource="$application" id="application.build_pack" label="Build Pack">...</x-forms.select>
<x-forms.checkbox instantSave canGate="update" :canResource="$application" id="application.settings.is_static" label="Static Site" />
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
```
### Service Configuration Forms
```html
<!-- Service stack configuration with authorization -->
<x-forms.input canGate="update" :canResource="$service" id="service.name" label="Service Name" />
<x-forms.input canGate="update" :canResource="$service" id="service.description" label="Description" />
<x-forms.checkbox canGate="update" :canResource="$service" instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network" />
<x-forms.button canGate="update" :canResource="$service" type="submit">Save</x-forms.button>
<!-- Service-specific fields -->
<x-forms.input canGate="update" :canResource="$service" type="{{ data_get($field, 'isPassword') ? 'password' : 'text' }}"
required="{{ str(data_get($field, 'rules'))?->contains('required') }}"
id="fields.{{ $serviceName }}.value"></x-forms.input>
<!-- Service restart modal - wrapped with @can -->
@can('update', $service)
<x-modal-confirmation title="Confirm Service Application Restart?"
buttonTitle="Restart"
submitAction="restartApplication({{ $application->id }})" />
@endcan
```
### Server Management Forms
```html
<!-- Server configuration with appropriate gates -->
<x-forms.input canGate="update" :canResource="$server" id="server.name" label="Server Name" />
<x-forms.select canGate="update" :canResource="$server" id="server.type" label="Server Type">...</x-forms.select>
<x-forms.button canGate="delete" :canResource="$server" wire:click="deleteServer">Delete Server</x-forms.button>
```
### Resource Creation Forms
```html
<!-- New resource creation -->
<x-forms.input canGate="createAnyResource" :canResource="auth()->user()->currentTeam" id="name" label="Name" />
<x-forms.select canGate="createAnyResource" :canResource="auth()->user()->currentTeam" id="server_id" label="Server">...</x-forms.select>
<x-forms.button canGate="createAnyResource" :canResource="auth()->user()->currentTeam" type="submit">Create Application</x-forms.button>
```
-696
View File
@@ -1,696 +0,0 @@
# Coolify Frontend Architecture & Patterns
## Frontend Philosophy
Coolify uses a **server-side first** approach with minimal JavaScript, leveraging Livewire for reactivity and Alpine.js for lightweight client-side interactions.
## Core Frontend Stack
### Livewire 3.5+ (Primary Framework)
- **Server-side rendering** with reactive components
- **Real-time updates** without page refreshes
- **State management** handled on the server
- **WebSocket integration** for live updates
### Alpine.js (Client-Side Interactivity)
- **Lightweight JavaScript** for DOM manipulation
- **Declarative directives** in HTML
- **Component-like behavior** without build steps
- **Perfect companion** to Livewire
### Tailwind CSS 4.1+ (Styling)
- **Utility-first** CSS framework
- **Custom design system** for deployment platform
- **Responsive design** built-in
- **Dark mode support**
## Livewire Component Structure
### Location: [app/Livewire/](mdc:app/Livewire)
#### Core Application Components
- **[Dashboard.php](mdc:app/Livewire/Dashboard.php)** - Main dashboard interface
- **[ActivityMonitor.php](mdc:app/Livewire/ActivityMonitor.php)** - Real-time activity tracking
- **[MonacoEditor.php](mdc:app/Livewire/MonacoEditor.php)** - Code editor component
#### Server Management
- **Server/** directory - Server configuration and monitoring
- Real-time server status updates
- SSH connection management
- Resource monitoring
#### Project & Application Management
- **Project/** directory - Project organization
- Application deployment interfaces
- Environment variable management
- Service configuration
#### Settings & Configuration
- **Settings/** directory - System configuration
- **[SettingsEmail.php](mdc:app/Livewire/SettingsEmail.php)** - Email notification setup
- **[SettingsOauth.php](mdc:app/Livewire/SettingsOauth.php)** - OAuth provider configuration
- **[SettingsBackup.php](mdc:app/Livewire/SettingsBackup.php)** - Backup configuration
#### User & Team Management
- **Team/** directory - Team collaboration features
- **Profile/** directory - User profile management
- **Security/** directory - Security settings
## Blade Template Organization
### Location: [resources/views/](mdc:resources/views)
#### Layout Structure
- **layouts/** - Base layout templates
- **components/** - Reusable UI components
- **livewire/** - Livewire component views
#### Feature-Specific Views
- **server/** - Server management interfaces
- **auth/** - Authentication pages
- **emails/** - Email templates
- **errors/** - Error pages
## Interactive Components
### Monaco Editor Integration
- **Code editing** for configuration files
- **Syntax highlighting** for multiple languages
- **Live validation** and error detection
- **Integration** with deployment process
### Terminal Emulation (XTerm.js)
- **Real-time terminal** access to servers
- **WebSocket-based** communication
- **Multi-session** support
- **Secure connection** through SSH
### Real-Time Updates
- **WebSocket connections** via Laravel Echo
- **Live deployment logs** streaming
- **Server monitoring** with live metrics
- **Activity notifications** in real-time
## Alpine.js Patterns
### Common Directives Used
```html
<!-- State management -->
<div x-data="{ open: false }">
<!-- Event handling -->
<button x-on:click="open = !open">
<!-- Conditional rendering -->
<div x-show="open">
<!-- Data binding -->
<input x-model="searchTerm">
<!-- Component initialization -->
<div x-init="initializeComponent()">
```
### Integration with Livewire
```html
<!-- Livewire actions with Alpine state -->
<button
x-data="{ loading: false }"
x-on:click="loading = true"
wire:click="deploy"
wire:loading.attr="disabled"
wire:target="deploy"
>
<span x-show="!loading">Deploy</span>
<span x-show="loading">Deploying...</span>
</button>
```
## Tailwind CSS Patterns
### Design System
- **Consistent spacing** using Tailwind scale
- **Color palette** optimized for deployment platform
- **Typography** hierarchy for technical content
- **Component classes** for reusable elements
### Responsive Design
```html
<!-- Mobile-first responsive design -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<!-- Content adapts to screen size -->
</div>
```
### Dark Mode Support
```html
<!-- Dark mode variants -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<!-- Automatic dark mode switching -->
</div>
```
## Build Process
### Vite Configuration ([vite.config.js](mdc:vite.config.js))
- **Fast development** with hot module replacement
- **Optimized production** builds
- **Asset versioning** for cache busting
- **CSS processing** with PostCSS
### Asset Compilation
```bash
# Development
npm run dev
# Production build
npm run build
```
## State Management Patterns
### Server-Side State (Livewire)
- **Component properties** for persistent state
- **Session storage** for user preferences
- **Database models** for application state
- **Cache layer** for performance
### Client-Side State (Alpine.js)
- **Local component state** for UI interactions
- **Form validation** and user feedback
- **Modal and dropdown** state management
- **Temporary UI states** (loading, hover, etc.)
## Real-Time Features
### WebSocket Integration
```php
// Livewire component with real-time updates
class ActivityMonitor extends Component
{
public function getListeners()
{
return [
'deployment.started' => 'refresh',
'deployment.finished' => 'refresh',
'server.status.changed' => 'updateServerStatus',
];
}
}
```
### Event Broadcasting
- **Laravel Echo** for client-side WebSocket handling
- **Pusher protocol** for real-time communication
- **Private channels** for user-specific events
- **Presence channels** for collaborative features
## Performance Patterns
### Lazy Loading
```php
// Livewire lazy loading
class ServerList extends Component
{
public function placeholder()
{
return view('components.loading-skeleton');
}
}
```
### Caching Strategies
- **Fragment caching** for expensive operations
- **Image optimization** with lazy loading
- **Asset bundling** and compression
- **CDN integration** for static assets
## Enhanced Form Components
### Built-in Authorization System
Coolify features **enhanced form components** with automatic authorization handling:
```html
<!-- ✅ New Pattern: Single line with built-in authorization -->
<x-forms.input canGate="update" :canResource="$application" id="application.name" label="Name" />
<x-forms.checkbox instantSave canGate="update" :canResource="$application" id="application.settings.is_static" label="Static Site" />
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
<!-- ❌ Old Pattern: Verbose @can/@else blocks (deprecated) -->
@can('update', $application)
<x-forms.input id="application.name" label="Name" />
@else
<x-forms.input disabled id="application.name" label="Name" />
@endcan
```
### Authorization Parameters
```php
// Available on all form components (Input, Select, Textarea, Checkbox, Button)
public ?string $canGate = null; // Gate name: 'update', 'view', 'deploy', 'delete'
public mixed $canResource = null; // Resource model instance to check against
public bool $autoDisable = true; // Automatically disable if no permission (default: true)
```
### Benefits
- **90% code reduction** for authorization-protected forms
- **Consistent security** across all form components
- **Automatic disabling** for unauthorized users
- **Smart behavior** (disables instantSave on checkboxes for unauthorized users)
For complete documentation, see **[form-components.md](.ai/patterns/form-components.md)**
## Form Handling Patterns
### Livewire Component Data Synchronization Pattern
**IMPORTANT**: All Livewire components must use the **manual `syncData()` pattern** for synchronizing component properties with Eloquent models.
#### Property Naming Convention
- **Component properties**: Use camelCase (e.g., `$gitRepository`, `$isStatic`)
- **Database columns**: Use snake_case (e.g., `git_repository`, `is_static`)
- **View bindings**: Use camelCase matching component properties (e.g., `id="gitRepository"`)
#### The syncData() Method Pattern
```php
use Livewire\Attributes\Validate;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class MyComponent extends Component
{
use AuthorizesRequests;
public Application $application;
// Properties with validation attributes
#[Validate(['required'])]
public string $name;
#[Validate(['string', 'nullable'])]
public ?string $description = null;
#[Validate(['boolean', 'required'])]
public bool $isStatic = false;
public function mount()
{
$this->authorize('view', $this->application);
$this->syncData(); // Load from model
}
public function syncData(bool $toModel = false): void
{
if ($toModel) {
$this->validate();
// Sync TO model (camelCase → snake_case)
$this->application->name = $this->name;
$this->application->description = $this->description;
$this->application->is_static = $this->isStatic;
$this->application->save();
} else {
// Sync FROM model (snake_case → camelCase)
$this->name = $this->application->name;
$this->description = $this->application->description;
$this->isStatic = $this->application->is_static;
}
}
public function submit()
{
$this->authorize('update', $this->application);
$this->syncData(toModel: true); // Save to model
$this->dispatch('success', 'Saved successfully.');
}
}
```
#### Validation with #[Validate] Attributes
All component properties should have `#[Validate]` attributes:
```php
// Boolean properties
#[Validate(['boolean'])]
public bool $isEnabled = false;
// Required strings
#[Validate(['string', 'required'])]
public string $name;
// Nullable strings
#[Validate(['string', 'nullable'])]
public ?string $description = null;
// With constraints
#[Validate(['integer', 'min:1'])]
public int $timeout;
```
#### Benefits of syncData() Pattern
- **Explicit Control**: Clear visibility of what's being synchronized
- **Type Safety**: #[Validate] attributes provide compile-time validation info
- **Easy Debugging**: Single method to check for data flow issues
- **Maintainability**: All sync logic in one place
- **Flexibility**: Can add custom logic (encoding, transformations, etc.)
#### Creating New Form Components with syncData()
#### Step-by-Step Component Creation Guide
**Step 1: Define properties in camelCase with #[Validate] attributes**
```php
use Livewire\Attributes\Validate;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class MyFormComponent extends Component
{
use AuthorizesRequests;
// The model we're syncing with
public Application $application;
// Component properties in camelCase with validation
#[Validate(['string', 'required'])]
public string $name;
#[Validate(['string', 'nullable'])]
public ?string $gitRepository = null;
#[Validate(['string', 'nullable'])]
public ?string $installCommand = null;
#[Validate(['boolean'])]
public bool $isStatic = false;
}
```
**Step 2: Implement syncData() method**
```php
public function syncData(bool $toModel = false): void
{
if ($toModel) {
$this->validate();
// Sync TO model (component camelCase → database snake_case)
$this->application->name = $this->name;
$this->application->git_repository = $this->gitRepository;
$this->application->install_command = $this->installCommand;
$this->application->is_static = $this->isStatic;
$this->application->save();
} else {
// Sync FROM model (database snake_case → component camelCase)
$this->name = $this->application->name;
$this->gitRepository = $this->application->git_repository;
$this->installCommand = $this->application->install_command;
$this->isStatic = $this->application->is_static;
}
}
```
**Step 3: Implement mount() to load initial data**
```php
public function mount()
{
$this->authorize('view', $this->application);
$this->syncData(); // Load data from model to component properties
}
```
**Step 4: Implement action methods with authorization**
```php
public function instantSave()
{
try {
$this->authorize('update', $this->application);
$this->syncData(toModel: true); // Save component properties to model
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
try {
$this->authorize('update', $this->application);
$this->syncData(toModel: true); // Save component properties to model
$this->dispatch('success', 'Changes saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
```
**Step 5: Create Blade view with camelCase bindings**
```blade
<div>
<form wire:submit="submit">
<x-forms.input
canGate="update"
:canResource="$application"
id="name"
label="Name"
required />
<x-forms.input
canGate="update"
:canResource="$application"
id="gitRepository"
label="Git Repository" />
<x-forms.input
canGate="update"
:canResource="$application"
id="installCommand"
label="Install Command" />
<x-forms.checkbox
instantSave
canGate="update"
:canResource="$application"
id="isStatic"
label="Static Site" />
<x-forms.button
canGate="update"
:canResource="$application"
type="submit">
Save Changes
</x-forms.button>
</form>
</div>
```
**Key Points**:
- Use `wire:model="camelCase"` and `id="camelCase"` in Blade views
- Component properties are camelCase, database columns are snake_case
- Always include authorization checks (`authorize()`, `canGate`, `canResource`)
- Use `instantSave` for checkboxes that save immediately without form submission
#### Special Patterns
**Pattern 1: Related Models (e.g., Application → Settings)**
```php
public function syncData(bool $toModel = false): void
{
if ($toModel) {
$this->validate();
// Sync main model
$this->application->name = $this->name;
$this->application->save();
// Sync related model
$this->application->settings->is_static = $this->isStatic;
$this->application->settings->save();
} else {
// From main model
$this->name = $this->application->name;
// From related model
$this->isStatic = $this->application->settings->is_static;
}
}
```
**Pattern 2: Custom Encoding/Decoding**
```php
public function syncData(bool $toModel = false): void
{
if ($toModel) {
$this->validate();
// Encode before saving
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
} else {
// Decode when loading
$this->customLabels = $this->application->parseContainerLabels();
}
}
```
**Pattern 3: Error Rollback**
```php
public function submit()
{
$this->authorize('update', $this->resource);
$original = $this->model->getOriginal();
try {
$this->syncData(toModel: true);
$this->dispatch('success', 'Saved successfully.');
} catch (\Throwable $e) {
// Rollback on error
$this->model->setRawAttributes($original);
$this->model->save();
$this->syncData(); // Reload from model
return handleError($e, $this);
}
}
```
#### Property Type Patterns
**Required Strings**
```php
#[Validate(['string', 'required'])]
public string $name; // No ?, no default, always has value
```
**Nullable Strings**
```php
#[Validate(['string', 'nullable'])]
public ?string $description = null; // ?, = null, can be empty
```
**Booleans**
```php
#[Validate(['boolean'])]
public bool $isEnabled = false; // Always has default value
```
**Integers with Constraints**
```php
#[Validate(['integer', 'min:1'])]
public int $timeout; // Required
#[Validate(['integer', 'min:1', 'nullable'])]
public ?int $port = null; // Nullable
```
#### Testing Checklist
After creating a new component with syncData(), verify:
- [ ] All checkboxes save correctly (especially `instantSave` ones)
- [ ] All form inputs persist to database
- [ ] Custom encoded fields (like labels) display correctly if applicable
- [ ] Form validation works for all fields
- [ ] No console errors in browser
- [ ] Authorization checks work (`@can` directives and `authorize()` calls)
- [ ] Error rollback works if exceptions occur
- [ ] Related models save correctly if applicable (e.g., Application + ApplicationSetting)
#### Common Pitfalls to Avoid
1. **snake_case in component properties**: Always use camelCase for component properties (e.g., `$gitRepository` not `$git_repository`)
2. **Missing #[Validate] attributes**: Every property should have validation attributes for type safety
3. **Forgetting to call syncData()**: Must call `syncData()` in `mount()` to load initial data
4. **Missing authorization**: Always use `authorize()` in methods and `canGate`/`canResource` in views
5. **View binding mismatch**: Use camelCase in Blade (e.g., `id="gitRepository"` not `id="git_repository"`)
6. **wire:model vs wire:model.live**: Use `.live` for `instantSave` checkboxes to avoid timing issues
7. **Validation sync**: If using `rules()` method, keep it in sync with `#[Validate]` attributes
8. **Related models**: Don't forget to save both main and related models in syncData() method
### Livewire Forms
```php
class ServerCreateForm extends Component
{
public $name;
public $ip;
protected $rules = [
'name' => 'required|min:3',
'ip' => 'required|ip',
];
public function save()
{
$this->validate();
// Save logic
}
}
```
### Real-Time Validation
- **Live validation** as user types
- **Server-side validation** rules
- **Error message** display
- **Success feedback** patterns
## Component Communication
### Parent-Child Communication
```php
// Parent component
$this->emit('serverCreated', $server->id);
// Child component
protected $listeners = ['serverCreated' => 'refresh'];
```
### Cross-Component Events
- **Global events** for application-wide updates
- **Scoped events** for feature-specific communication
- **Browser events** for JavaScript integration
## Error Handling & UX
### Loading States
- **Skeleton screens** during data loading
- **Progress indicators** for long operations
- **Optimistic updates** with rollback capability
### Error Display
- **Toast notifications** for user feedback
- **Inline validation** errors
- **Global error** handling
- **Retry mechanisms** for failed operations
## Accessibility Patterns
### ARIA Labels and Roles
```html
<button
aria-label="Deploy application"
aria-describedby="deploy-help"
wire:click="deploy"
>
Deploy
</button>
```
### Keyboard Navigation
- **Tab order** management
- **Keyboard shortcuts** for power users
- **Focus management** in modals and forms
- **Screen reader** compatibility
## Mobile Optimization
### Touch-Friendly Interface
- **Larger tap targets** for mobile devices
- **Swipe gestures** where appropriate
- **Mobile-optimized** forms and navigation
### Progressive Enhancement
- **Core functionality** works without JavaScript
- **Enhanced experience** with JavaScript enabled
- **Offline capabilities** where possible
File diff suppressed because it is too large. Load diff