16 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Note for AI Assistants: This file is specifically for Claude Code. All detailed documentation is in the
.ai/directory. Both Claude Code and Cursor IDE use the same source files in.ai/for consistency.Maintaining Instructions: When updating AI instructions, see .ai/meta/sync-guide.md and .ai/meta/maintaining-docs.md for guidelines.
Project Overview
Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization.
Git Worktree Shared Dependencies
This repository uses git worktrees for parallel development with automatic shared dependency setup via Conductor.
How It Works
The conductor.json setup script (scripts/conductor-setup.sh) automatically:
- Creates symlinks from worktree's
node_modulesandvendorto the main repository's directories - All worktrees share the same dependencies from the main repository
- This happens automatically when Conductor creates a new worktree
Benefits
- Save disk space: Only one copy of dependencies across all worktrees
- Faster setup: No need to run
npm installorcomposer installfor each worktree - Consistent versions: All worktrees use the same dependency versions
- Auto-configured: Handled by Conductor's setup script
- Simple: Uses the main repo's existing directories, no extra folders
Manual Setup (If Needed)
If you need to set up symlinks manually or for non-Conductor worktrees:
# From the worktree directory
rm -rf node_modules vendor
ln -sf ../../node_modules node_modules
ln -sf ../../vendor vendor
Important Notes
- Dependencies are shared from the main repository (
$CONDUCTOR_ROOT_PATH) - Run
npm installorcomposer installfrom the main repo or any worktree to update all - If different branches need different dependency versions, this won't work - remove symlinks and use separate directories
Development Commands
Frontend Development
npm run dev- Start Vite development server for frontend assetsnpm run build- Build frontend assets for production
Backend Development
Only run artisan commands inside "coolify" container when in development.
php artisan serve- Start Laravel development serverphp artisan migrate- Run database migrationsphp artisan queue:work- Start queue worker for background jobsphp artisan horizon- Start Laravel Horizon for queue monitoringphp artisan tinker- Start interactive PHP REPL
Code Quality
./vendor/bin/pint- Run Laravel Pint for code formatting./vendor/bin/phpstan- Run PHPStan for static analysis./vendor/bin/pest tests/Unit- Run unit tests only (no database, can run outside Docker)./vendor/bin/pest- Run ALL tests (includes Feature tests, may require database)
Running Tests
IMPORTANT: Tests that require database connections MUST be run inside the Docker container:
- Inside Docker:
docker exec coolify php artisan test(for feature tests requiring database) - Outside Docker:
./vendor/bin/pest tests/Unit(for pure unit tests without database dependencies) - Unit tests should use mocking and avoid database connections
- Feature tests that require database must be run in the
coolifycontainer
Architecture Overview
Technology Stack
- Backend: Laravel 12.4.1 (PHP 8.4.7)
- Frontend: Livewire 3.5.20 with Alpine.js and Tailwind CSS 4.1.4
- Database: PostgreSQL 15 (primary), Redis 7 (cache/queues)
- Real-time: Soketi (WebSocket server)
- Containerization: Docker & Docker Compose
- Queue Management: Laravel Horizon 5.30.3
Note
: For complete version information and all dependencies, see .ai/core/technology-stack.md
Key Components
Core Models
Application- Deployed applications with Git integration (74KB, highly complex)Server- Remote servers managed by Coolify (46KB, complex)Service- Docker Compose services (58KB, complex)Database- Standalone database instances (PostgreSQL, MySQL, MongoDB, Redis, etc.)Team- Multi-tenancy supportProject- Grouping of environments and resourcesEnvironment- Environment isolation (staging, production, etc.)
Job System
- Uses Laravel Horizon for queue management
- Key jobs:
ApplicationDeploymentJob,ServerCheckJob,DatabaseBackupJob ServerManagerJobandServerConnectionCheckJobhandle job scheduling
Deployment Flow
- Git webhook triggers deployment
ApplicationDeploymentJobhandles build and deployment- Docker containers are managed on target servers
- Proxy configuration (Nginx/Traefik) is updated
Server Management
- SSH-based server communication via
ExecuteRemoteCommandtrait - Docker installation and management
- Proxy configuration generation
- Resource monitoring and cleanup
Directory Structure
app/Actions/- Domain-specific actions (Application, Database, Server, etc.)app/Jobs/- Background queue jobsapp/Livewire/- Frontend components (full-stack with Livewire)app/Models/- Eloquent modelsapp/Rules/- Custom validation rulesapp/Http/Middleware/- HTTP middlewarebootstrap/helpers/- Helper functions for various domainsdatabase/migrations/- Database schema evolutionroutes/- Application routing (web.php, api.php, webhooks.php, channels.php)resources/views/livewire/- Livewire component viewstests/- Pest tests (Feature and Unit)
Development Guidelines
Frontend Philosophy
Coolify uses a server-side first approach with minimal JavaScript:
- Livewire for server-side rendering with reactive components
- Alpine.js for lightweight client-side interactions
- Tailwind CSS for utility-first styling with dark mode support
- Enhanced Form Components with built-in authorization system
- Real-time updates via WebSocket without page refreshes
Form Authorization Pattern
IMPORTANT: When creating or editing forms, ALWAYS include authorization:
For Form Components (Input, Select, Textarea, Checkbox, Button):
Use canGate and canResource attributes for automatic authorization:
<x-forms.input canGate="update" :canResource="$resource" id="name" label="Name" />
<x-forms.select canGate="update" :canResource="$resource" id="type" label="Type">...</x-forms.select>
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" id="enabled" label="Enabled" />
<x-forms.button canGate="update" :canResource="$resource" type="submit">Save</x-forms.button>
For Modal Components:
Wrap with @can directives:
@can('update', $resource)
<x-modal-confirmation title="Confirm Action?" buttonTitle="Confirm">...</x-modal-confirmation>
<x-modal-input buttonTitle="Edit" title="Edit Settings">...</x-modal-input>
@endcan
In Livewire Components:
Always add the AuthorizesRequests trait and check permissions:
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class MyComponent extends Component
{
use AuthorizesRequests;
public function mount()
{
$this->authorize('view', $this->resource);
}
public function update()
{
$this->authorize('update', $this->resource);
// ... update logic
}
}
Livewire Component Structure
- Components located in
app/Livewire/ - Views in
resources/views/livewire/ - State management handled on the server
- Use wire:model for two-way data binding
- Dispatch events for component communication
- CRITICAL: Livewire component views MUST have exactly ONE root element. ALL content must be contained within this single root element. Placing ANY elements (
<style>,<script>,<div>, comments, or any other HTML) outside the root element will break Livewire's component tracking and causewire:clickand other directives to fail silently.
Code Organization Patterns
- Actions Pattern: Use Actions for complex business logic (
app/Actions/) - Livewire Components: Handle UI and user interactions
- Jobs: Handle asynchronous operations
- Traits: Provide shared functionality (e.g.,
ExecuteRemoteCommand) - Helper Functions: Domain-specific helpers in
bootstrap/helpers/
Database Patterns
- Use Eloquent ORM for database interactions
- Implement relationships properly (HasMany, BelongsTo, etc.)
- Use database transactions for critical operations
- Leverage query scopes for reusable queries
- Apply indexes for performance-critical queries
- CRITICAL: When adding new database columns, ALWAYS update the model's
$fillablearray to allow mass assignment
Security Best Practices
- Authentication: Multi-provider auth via Laravel Fortify & Sanctum
- Authorization: Team-based access control with policies and enhanced form components
- Form Component Security: Built-in
canGateauthorization system for UI components - API Security: Token-based auth with IP allowlisting
- Secrets Management: Never log or expose sensitive data
- Input Validation: Always validate user input with Form Requests or Rules
- SQL Injection Prevention: Use Eloquent ORM or parameterized queries
API Development
- RESTful endpoints in
routes/api.php - Use API Resources for response formatting
- Implement rate limiting for public endpoints
- Version APIs when making breaking changes
- Document endpoints with clear examples
Testing Strategy
- Framework: Pest for expressive testing
- Structure: Feature tests for user flows, Unit tests for isolated logic
- Coverage: Test critical paths and edge cases
- Mocking: Use Laravel's built-in mocking for external services
- Database: Use RefreshDatabase trait for test isolation
Test Execution Environment
CRITICAL: Database-dependent tests MUST run inside Docker container:
- Unit Tests (
tests/Unit/): Should NOT use database. Use mocking. Run with./vendor/bin/pest tests/Unit - Feature Tests (
tests/Feature/): May use database. MUST run inside Docker withdocker exec coolify php artisan test - If a test needs database (factories, migrations, etc.), it belongs in
tests/Feature/ - Always mock external services and SSH connections in tests
Test Design Philosophy
PREFER MOCKING: When designing features and writing tests:
- Design for testability: Structure code so it can be tested without database (use dependency injection, interfaces)
- Mock by default: Unit tests should mock models and external dependencies using Mockery
- Avoid database when possible: If you can test the logic without database, write it as a Unit test
- Only use database when necessary: Feature tests should test integration points, not isolated logic
- Example: Instead of
Server::factory()->create(), useMockery::mock('App\Models\Server')in unit tests
Routing Conventions
- Group routes by middleware and prefix
- Use route model binding for cleaner controllers
- Name routes consistently (resource.action)
- Implement proper HTTP verbs (GET, POST, PUT, DELETE)
Error Handling
- Use
handleError()helper for consistent error handling - Log errors with appropriate context
- Return user-friendly error messages
- Implement proper HTTP status codes
Performance Considerations
- Use eager loading to prevent N+1 queries
- Implement caching for frequently accessed data
- Queue heavy operations
- Optimize database queries with proper indexes
- Use chunking for large data operations
- CRITICAL: Use
ownedByCurrentTeamCached()instead ofownedByCurrentTeam()->get()
Code Style
- Follow PSR-12 coding standards
- Use Laravel Pint for automatic formatting
- Write descriptive variable and method names
- Keep methods small and focused
- Document complex logic with clear comments
Cloud Instance Considerations
We have a cloud instance of Coolify (hosted version) with:
- 2 Horizon worker servers
- Thousands of connected servers
- Thousands of active users
- High-availability requirements
When developing features:
- Consider scalability implications
- Test with large datasets
- Implement efficient queries
- Use queues for heavy operations
- Consider rate limiting and resource constraints
- Implement proper error recovery mechanisms
Important Reminders
- Always run code formatting:
./vendor/bin/pint - Test your changes:
./vendor/bin/pest - Check for static analysis issues:
./vendor/bin/phpstan - Use existing patterns and helpers
- Follow the established directory structure
- Maintain backward compatibility
- Document breaking changes
- Consider performance impact on large-scale deployments
Additional Documentation
This file contains high-level guidelines for Claude Code. For more detailed, topic-specific documentation, refer to the .ai/ directory:
Documentation Hub: The
.ai/directory contains comprehensive, detailed documentation organized by topic. Start with .ai/README.md for navigation, then explore specific topics below.
Core Documentation
- Technology Stack - All versions, packages, and dependencies (single source of truth)
- Project Overview - What Coolify is and how it works
- Application Architecture - System design and component relationships
- Deployment Architecture - How deployments work end-to-end
Development Practices
- Development Workflow - Development setup, commands, and workflows
- Testing Patterns - Testing strategies and examples (Docker requirements!)
- Laravel Boost - Laravel-specific guidelines and best practices
Code Patterns
- Database Patterns - Eloquent, migrations, relationships
- Frontend Patterns - Livewire, Alpine.js, Tailwind CSS
- Security Patterns - Authentication, authorization, security
- Form Components - Enhanced form components with authorization
- API & Routing - API design and routing conventions
Meta Documentation
- Maintaining Docs - How to update and improve AI documentation
- Sync Guide - Keeping documentation synchronized
Laravel Boost Guidelines
Full Guidelines: See .ai/development/laravel-boost.md for complete Laravel Boost guidelines.
Essential Laravel Patterns
- Use PHP 8.4 constructor property promotion and typed properties
- Follow PSR-12 (run
./vendor/bin/pintbefore committing) - Use Eloquent ORM, avoid raw queries
- Use Form Request classes for validation
- Queue heavy operations with Laravel Horizon
- Never use
env()outside config files - Use named routes with
route()function - Laravel 12 with Laravel 10 structure (no bootstrap/app.php)
Testing Requirements
- Unit tests: No database, use mocking, run with
./vendor/bin/pest tests/Unit - Feature tests: Can use database, run with
docker exec coolify php artisan test - Every change must have tests
- Use Pest for all tests
Livewire & Frontend
- Livewire components require single root element
- Use
wire:model.livefor real-time updates - Alpine.js included with Livewire
- Tailwind CSS 4.1.4 (use new utilities, not deprecated ones)
- Use
gaputilities for spacing, not margins
Random other things you should remember:
- App\Models\Application::team must return a relationship instance., always use team()
- Always use
Model::ownedByCurrentTeamCached()instead ofModel::ownedByCurrentTeam()->get()for team-scoped queries to avoid duplicate database queries