
- Introduced multiple new markdown files covering API and routing, application architecture, deployment architecture, database patterns, frontend patterns, and security practices. - Established guidelines for development workflows, testing strategies, and continuous improvement of rules. - Enhanced project overview and technology stack documentation to provide clarity on Coolify's features and architecture.
654 lines
17 KiB
Plaintext
654 lines
17 KiB
Plaintext
---
|
|
description:
|
|
globs:
|
|
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
|
|
<!-- 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
|