--- description: Development setup, coding standards, contribution guidelines, and best practices globs: **/*.php, composer.json, package.json, *.md, .env.example alwaysApply: false --- # 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

Application Status

``` ## 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