Skip to content

Conversation

@andrasbacsai
Copy link
Member

@andrasbacsai andrasbacsai commented Oct 3, 2025

This PR represents a major milestone in Coolify's evolution, bringing comprehensive Hetzner Cloud integration, custom webhook notifications, enhanced Docker image handling, onboarding redesign, and numerous stability and security improvements.


🚀 Major Features

Hetzner Cloud Integration (#6817, #6840, #6842, #6843, #6863, #6866)

Complete integration with Hetzner Cloud, enabling users to create and manage servers directly from Coolify.

Core Features:

  • ✨ Server creation and deletion via Hetzner API
  • ✨ Automatic server status synchronization with retry mechanism
  • ✨ Deletion failure notification system with email alerts
  • ✨ Rate limit handling for API requests
  • ✨ Retry functionality for server validation process

Cloud-Init Script Support:

  • ✨ Cloud-init script support for Hetzner server creation
  • ✨ Cloud-init scripts management UI in Security section
  • ✨ YAML validation for cloud-init scripts
  • ✨ Dropdown selection for existing scripts
  • ✨ Global search integration for cloud-init scripts
  • 📝 Documentation clarifying cloud-init script compatibility

Network & Configuration:

  • ✨ IPv4/IPv6 network configuration for server creation
  • ✨ Support for selecting additional SSH keys from Hetzner
  • ✨ Modal support for creating private keys in server creation flow
  • ✨ Pricing display on server creation button (e.g., "€12.99/mo")
  • ✨ CPU vendor information for server types

UI Improvements:

  • ✨ Hetzner affiliate link in token form to support Coolify
  • ✨ Enhanced datalist component with unified input container
  • ✨ Improved placeholder text for token name input

Implementation Details:

  • 🔨 Implement ValidHostname validation rule
  • 🐛 Improved error logging and handling in ServerConnectionCheckJob
  • 🐛 Update Hetzner server status handling to prevent unnecessary database calls
  • 🐛 Correct dispatch logic for Hetzner server status refresh

📊 Statistics: 55 commits, 4,452 additions, 411 deletions, 104 files changed


Custom Webhook Notifications (#6837)

Complete webhook notification system allowing users to receive real-time notifications about events via HTTP POST requests.

Core Features:

  • Custom Webhook Notifications: Send POST requests to user-defined HTTP/HTTPS endpoints when events occur
  • Database Schema: New webhook_notification_settings table with encrypted URL storage and per-event toggles
  • Event Toggles: Fine-grained control over 12+ notification types (deployments, backups, scheduled tasks, server events)
  • Webhook Delivery: Queued job system with retry logic (5 attempts, 10s backoff)
  • Rich Payloads: All webhook payloads include resource UUIDs and direct URLs to Coolify resources

UI/UX Enhancements:

  • Dedicated Webhook Page: Full settings interface with form validation and test notification
  • Password-Protected URLs: Webhook URLs displayed as password fields for security
  • POST Badge Indicator: Visual HTTP method indicator positioned before the URL input
  • Responsive Layout: Organized notification settings in categorized cards

Supported Events:

  • Deployments: success, failed, status_changed
  • Backups: success, failed, success_with_s3_warning
  • Scheduled Tasks: success, failed
  • Server: docker_cleanup, high_disk_usage, reachable/unreachable, server_patch_check

📊 Statistics: 14 commits, 32 files changed, +900 additions / -15 deletions


Enhanced User Onboarding (#6855)

Complete redesign of the user onboarding experience with a modern, centered UI, enhanced UX flow, and Hetzner Cloud integration.

Core Features:

  • Modern UI Redesign: Centered, card-based layout with clean design
  • 3-Step Progress Indicator: Visual progress component showing completed, current, and upcoming steps
  • Hetzner Cloud Integration: Users can now create Hetzner servers directly from onboarding
  • Separate SSH Key Input Modes: Distinct textareas for "Generate with Coolify" (readonly) vs "Add your own" (editable)

Bug Fixes:

  • Project Loading on Refresh: Projects now load correctly when refreshing the Project Setup page
  • Browser Back Button: Removed aggressive redirects to welcome page
  • SSH Key Auto-Selection: First SSH key is auto-selected by default
  • State Persistence: Livewire URL binding enables proper browser navigation

📊 Statistics: 4 commits, 5 files changed, +851 additions / -428 deletions


✨ Core Features

Docker & Container Management

Docker Image Enhancements

Service & Template Improvements

Template Updates & Fixes

Deployment & Build System

Git Integration

  • Git Redirect Handling (fix(git): handle Git redirects and improve URL parsing for tangled.sh and other Git hosts #6869)
    • Fixed deployment failures when Git repositories use redirects (e.g., tangled.sh → tangled.org)
    • Replaced line-based parsing with regex pattern matching for commit SHA extraction
    • Added security improvement with escapeshellarg() for repository URLs
    • Comprehensive test coverage for all redirect scenarios
    • Universal fix for all Git hosting services with redirects

Database Management

Proxy & Network

  • Proxy Configuration (Add Claude Code GitHub Workflow #6787)

    • Enhanced proxy configuration regeneration by extracting custom commands
    • Added function to preserve custom Traefik commands during regeneration
    • Unit tests to validate custom command extraction
  • Read-Only Volume Handling (feat(template): add Cloudreve service template #6774)

    • Introduced isReadOnlyVolume method for volume models
    • Enhanced UI to display notifications for read-only volumes
    • Prevent modification actions for read-only volumes

🎨 UI/UX Enhancements

Global Search & Navigation

Dashboard & Project Management

Form Components & Styling

  • Enhanced Form Components
    • Enhanced datalist component with unified input container
    • Improved focus styles for Checkbox, input, button, and modal components
    • Update success color to match design specifications
    • Simplified wire:dirty class bindings for input/select components
    • Clear button for cloud-init script dropdown

🐛 Bug Fixes

Storage & Backups

  • S3 Storage Enhancements (feat: Adding Gotify as a Notification Option #6792)
    • Transaction handling in storage settings submission
    • Connection testing within transaction before finalizing save
    • Enhanced error handling with model state refresh after rollback

Service Management

Validation & Error Handling


🔨 Refactoring & Code Quality

Component Modernization

Code Cleanup

  • Remove Unused Code
    • Removed redundant logic for merging COOLIFY_* variables
    • Simplified environment variable adding process
    • Enhanced clarity and maintainability

📝 Documentation & Testing

Documentation Improvements

Test Coverage

  • New Test Files
    • tests/Unit/ProxyCustomCommandsTest.php - Proxy configuration tests
    • tests/Unit/GitLsRemoteParsingTest.php - Git redirect handling tests
    • tests/Unit/DockerImageParserTest.php - Docker image parsing tests
    • tests/Unit/DockerImageAutoParseTest.php - Auto-parse feature tests
    • tests/Unit/ServiceParserImageUpdateTest.php - Service parser tests
    • tests/Unit/ServiceConfigurationRefreshTest.php - Configuration refresh tests
    • tests/Unit/GlobalSearchNewImageQuickActionTest.php - Search tests
    • tests/Unit/HetznerServerCreationTest.php - Hetzner API tests

⚡ Performance & Infrastructure

  • Database Optimization

  • Cache Management

    • Clear computed property cache after adding environment variables
    • Cache clearing for environment variables and preview
    • Improved cache control for global search modal

🔒 Security & Validation

  • Authorization Enhancements

  • Input Validation

    • Implement ValidHostname validation rule
    • YAML validation for cloud-init scripts
    • Enhanced DockerImageFormat validation rule

📊 Other Updates

Sponsors & Community

  • Sponsors Update (Update sponsors list in README #6849)
    • Added 6 new sponsors: 23M, Brand.dev, ByteBase, Formbricks, Mobb, Ramnode
    • Removed 6 inactive sponsors
    • Reorganized sponsors in alphabetical order

API Improvements

GitHub Integration

  • Claude Code GitHub Workflow (Add Claude Code GitHub Workflow #6787)
    • Added GitHub Actions workflow for Claude Code integration
    • Enables @claude mentions in PR and issue comments
    • Automated AI assistance for code reviews and improvements

📈 Overall Statistics


🙏 Credits

Thank you to all contributors who made this release possible:

@andrasbacsai, @ShadowArcanist, @saurabhraghuvanshii, @djsisson, @Blaumaus, @mario-neuhold, @seefs001, @abdia3, @thesloppyguy, @elmariss, @zehjotkah, @victor, @Sahil, @neeku, @scan, @hoàng Trần Nhật Minh, @Romain ROCHAS, @amir Hossein Karimi, @vishwanath Martur, @evan Schleret, @halil Özgür, and the entire Coolify community!

Special thanks to the testing community for reporting issues and helping improve stability.


Generated by Andras & Jean-Claude, hand-in-hand.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Hetzner Cloud integration: add/manage tokens, create servers with guided flow, cloud-init script manager, and server token assignment.
    • Webhook notifications: configurable per-team with test send and support across deployments, backups, servers, tasks, and containers.
    • New compose templates: Elasticsearch + Kibana, Gotify, Swetrix, New API; Ente Photos (with S3).
  • Improvements

    • Docker image input supports tags or SHA256 digests; better normalization in API and UI.
    • Build/runtime env handling streamlined; default NIXPACKS_NODE_VERSION=22 for nixpacks apps.
    • Storage UI: read-only indicators and tabbed view.
    • Global Search “create mode”; dashboard/server add modals.
    • Database backups: S3 warning path, clearer status badges.
    • Terminal: searchable server/container picker.
  • Chores

    • Visual polish (focus states, colors), template/image updates, and version bump.

Walkthrough

Adds digest-aware Docker image input/validation and env handling changes; splits build-time vs runtime environment variables during deployments; preserves custom proxy commands; integrates Hetzner Cloud (tokens, server lifecycle, UI); adds webhook notifications, cloud-init scripts, templates, migrations, tests, and many UI refinements.

Changes

Cohort / File(s) Summary of Changes
Docker image input, parser & validation
app/Livewire/Project/New/DockerImage.php, resources/views/livewire/project/new/docker-image.blade.php, resources/views/livewire/project/application/general.blade.php, app/Rules/DockerImageFormat.php, app/Services/DockerImageParser.php, tests/Unit/DockerImageParserTest.php
Split single image field into name/tag/hash, normalize sha256 forms, add DockerImageFormat rule, expose isImageHash and hash helpers in parser, update UI and tests.
Application creation & env ordering
app/Models/Application.php, app/Models/Service.php, app/Livewire/Project/Shared/EnvironmentVariable/All.php
Create default NIXPACKS_NODE_VERSION when nixpacks selected; change ORDER BY CASE to prioritize required flags first, then service_ keys; clear computed env caches on add/refresh.
Deployment build env workflow & docker args
app/Jobs/ApplicationDeploymentJob.php, bootstrap/helpers/docker.php, app/Http/Controllers/Api/ApplicationsController.php
Introduce build-time vs runtime env generation/saving, use /artifacts/build-time.env, wrap build commands to export build-time envs, change generateDockerBuildArgs() to emit only keys, and normalize image/tag/digest in API create path.
Proxy helpers & preservation of custom commands
bootstrap/helpers/proxy.php, app/Actions/Proxy/GetProxyConfiguration.php, tests/Unit/ProxyCustomCommandsTest.php
Add extractCustomProxyCommands, allow generateDefaultProxyConfiguration to accept custom commands and preserve them during regeneration; add unit tests.
Storage read-only detection & UI
app/Models/LocalFileVolume.php, app/Models/LocalPersistentVolume.php, app/Livewire/Project/Service/FileStorage.php, app/Livewire/Project/Shared/Storages/Show.php, resources/views/livewire/project/service/*.blade.php, resources/views/livewire/project/service/storage.blade.php, resources/views/livewire/project/shared/storages/show.blade.php
Add isReadOnlyVolume() by parsing compose YAML, expose isReadOnly to components, show read-only banners and gate edit UI, convert storage list to tabs.
Hetzner Cloud integration
app/Services/HetznerService.php, app/Models/CloudProviderToken.php, app/Models/Server.php, app/Models/Team.php, database/migrations/*add hetzner/cloud token/is_validating*, many app/Livewire/Server/*, resources/views/livewire/server/*, routes/web.php
Add HetznerService client, CloudProviderToken model/migrations, server fields/relations, Livewire UIs for tokens and guided Hetzner server creation, server status/power actions, and routes.
Backups: S3 tracking & notifications
app/Jobs/DatabaseBackupJob.php, database/migrations/2025_10_07_120723_add_s3_uploaded_to_scheduled_database_backup_executions_table.php, app/Models/ScheduledDatabaseBackupExecution.php, app/Notifications/Database/BackupSuccessWithS3Warning.php, resources/views/emails/backup-success-with-s3-warning.blade.php, tests/*`
Use backup_log_uuid, add nullable s3_uploaded with casts, refine local vs S3 flow, add BackupSuccessWithS3Warning multi-channel notification and email template, update tests.
Webhook notifications & channel
app/Models/WebhookNotificationSettings.php, app/Notifications/Channels/WebhookChannel.php, app/Jobs/SendWebhookJob.php, app/Livewire/Notifications/Webhook.php, database/migrations/*create_webhook_notification_settings*, many notifications adding toWebhook()
Add webhook settings model/migrations, WebhookChannel, SendWebhookJob, Livewire UI for webhook settings and test send, and add webhook payload methods to many notifications.
Global Search, onboarding & UI refinements
app/Livewire/GlobalSearch.php, resources/views/livewire/global-search.blade.php, app/Livewire/DeploymentsIndicator.php, resources/views/livewire/deployments-indicator.blade.php, many blade tweaks
Add "new ..." create-mode with guided creation flows, eager-load related models for deployments indicator, UI spacing and component refinements.
Validation rules, cloud-init & hostname
app/Rules/ValidCloudInitYaml.php, app/Rules/ValidHostname.php, app/Models/CloudInitScript.php, app/Policies/*, app/Livewire/Security/*, tests under tests/*
New validation rules for cloud-init YAML and hostnames; CloudInitScript model, policies; Livewire components and views for cloud-init scripts and cloud provider tokens; associated tests.
CA cert / SSL refactors & DB Livewire sync pattern
app/Actions/Database/Start*.php, many app/Livewire/Project/Database/*
Replace direct SslCertificate:: queries with $server->sslCertificates() relation; add docker stop/rm cleanup before compose up in Start* actions; refactor multiple DB Livewire components to syncData two-way pattern and add related public fields.
Templates, service catalog & misc templates
templates/compose/*, templates/service-templates*.json
Add/update service compose templates (Elasticsearch+Kibana, Ente Photos variants, Gotify, Homarr, New API, etc.) and register new service template entries.
Migrations, constants & small changes
config/constants.php, versions.json, other/nightly/versions.json, bootstrap/helpers/constants.php, database/migrations/*, docker-compose.dev.yml, .github/workflows/*, CSS and many blades
Version bumps, add legacy/secure DB image entries and ClickHouse migration, conductor/CI tag sanitization, CSS focus-visible updates, many UI spacing/markup updates and small behavior tweaks.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Docker Image Form
  participant Parser as DockerImageParser
  participant API as ApplicationsController
  participant DB as Application Model
  participant Job as ApplicationDeploymentJob

  User->>UI: enter image name + (tag OR SHA256)
  UI->>Parser: parse components (name/tag/hash)
  Parser-->>UI: {name, tag, isImageHash}
  UI->>API: submit normalized payload
  API->>API: normalize (strip/append sha256), default tag
  API->>DB: create Application (if nixpacks -> add NIXPACKS_NODE_VERSION env)
  DB-->>Job: enqueue deployment
  Job->>Job: generate_buildtime_environment_variables() -> write /artifacts/build-time.env
  Job->>Job: wrap_build_command_with_env_export(...) -> run build using exported env
  Job->>Job: save_runtime_environment_variables() -> produce final runtime .env
  Job-->>User: deployment logs (image:tag or image@sha256:<hash>)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

You asked for concise facts — I complied. I'll be back... with a taco. Servers rule; serverless drools.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title ‘v4.0.0-beta.435’ reads like a serverless pitch with no substance—Terminator demands clarity. It fails to convey the epic raft of features and fixes inside and leaves reviewers guessing. A concise but specific title is needed. Please update the PR title to include the key highlights such as ‘Release v4.0.0-beta.435: Hetzner integration, Webhook notifications, and onboarding redesign’ so reviewers immediately understand the scope. Keep it concise and descriptive to facilitate quick scanning. Terminate ambiguity. Self-hosted servers deserve clarity.
Description Check ⚠️ Warning The pull request description does not match the repository’s required template because it retains free-form sections like “Major Features” and “Core Features” instead of the mandated structure. It omits the explicit “## Changes” and “## Issues” headings, does not remove the draft checklist comment, and fails to list changes and issue references as specified by the template. Please update the description to conform to the repository template by removing the draft checklist block, adding a concise “## Changes” section summarizing the key modifications, and populating the “## Issues” section with relevant issue or discussion links.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch next

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
templates/compose/ente.yaml (2)

82-82: Extra blank line detected. I'll be back... after we clean up this YAML.

YAMLlint flagged line 82 for having an extra blank line. While this doesn't break functionality (YAML parsers are forgiving), it violates the linting rule. Remove the trailing blank line for clean, gluten-free YAML – no unnecessary bloat, just like a good gluten-free taco shell.

Based on static analysis hints.

Apply this diff to remove the blank line:

       retries: 5
       start_period: 30s

-

1-82: Ente Photos template: tighten template for reliable deployments

  • Major: pin image tags (avoid :latest or implicit tags on ghcr.io/ente-io/server and ghcr.io/ente-io/web) for deterministic builds
  • Minor: document or externalize the hardcoded ENTE_INTERNAL_ADMIN default (1580559962386438)
  • Verification needed: confirm wget in the museum image and curl in the web image before relying on those healthchecks
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b474eb4 and 50383c7.

⛔ Files ignored due to path filters (1)
  • public/svgs/ente.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • app/Models/Application.php (2 hunks)
  • app/Models/Service.php (1 hunks)
  • config/constants.php (1 hunks)
  • other/nightly/versions.json (1 hunks)
  • templates/compose/ente.yaml (1 hunks)
  • versions.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
{app,bootstrap,config,database,routes,resources,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Adhere to PSR-12 coding standards for all PHP code

Files:

  • config/constants.php
  • app/Models/Service.php
  • app/Models/Application.php
config/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Keep application settings in Laravel config files under config/ and load them via config()

Application configuration should reside in config/*.php files

Files:

  • config/constants.php
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

**/*.php: Follow PSR-12 coding standards for all PHP code
Format PHP code with Laravel Pint configuration
Run static analysis with PHPStan to ensure type safety in PHP code
Document complex methods with PHPDoc blocks including parameters, return types, and thrown exceptions

**/*.php: Always use curly braces for control structures, even for single-line bodies
Use PHP 8 constructor property promotion; do not leave empty __construct() methods
Declare explicit return types for functions/methods and use parameter type hints
Prefer PHPDoc blocks over inline comments; document complex logic and array shapes when useful
Enum case names should be TitleCase
Follow PSR-12 and run Laravel Pint to auto-format code

Files:

  • config/constants.php
  • app/Models/Service.php
  • app/Models/Application.php
{app,bootstrap,config,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app,bootstrap,config,database,routes,tests}/**/*.php: Always use curly braces for control structures, even for single-line statements
Use PHP 8 constructor property promotion in __construct(); do not allow empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline comments; only add inline comments for very complex code

Files:

  • config/constants.php
  • app/Models/Service.php
  • app/Models/Application.php
config/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Access environment via env() only in config files; elsewhere use config()

Files:

  • config/constants.php
app/Models/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Keep Eloquent domain models in app/Models; models represent core entities (e.g., Application, Server, Service, Team, Project)

app/Models/**/*.php: Define $fillable for mass assignment protection in Eloquent models
Use $casts for attribute casting (e.g., arrays, enums, datetimes) in Eloquent models
Define relationships using Eloquent relation return types (e.g., BelongsTo, HasMany)

app/Models/**/*.php: Define Eloquent relationships using proper relationship methods with return type hints; prefer relationships over raw queries or manual joins
Prefer defining casts in the casts() method instead of the $casts property, following existing conventions

app/Models/**/*.php: Define Eloquent relationships with proper return type hints and prefer relationships over raw queries
Prefer eager loading to avoid N+1 queries; use query scopes and indexes for performance-critical queries

Files:

  • app/Models/Service.php
  • app/Models/Application.php
app/Models/{Application,Server,Service,Project,Environment}.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Major resources must provide a team-based scope (e.g., scopeOwnedByCurrentTeam) to enforce multi-tenant isolation

Files:

  • app/Models/Service.php
  • app/Models/Application.php
app/Models/*.php

📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)

app/Models/*.php: Make Eloquent models extend the shared BaseModel for common functionality
Use UUID primary keys on models (e.g., via HasUuids)
Enable soft deletes on models that require audit trails (use SoftDeletes)
Enable activity logging on models using the Spatie LogsActivity trait
Include HasFactory on Eloquent models for factory support
Define explicit Eloquent relationships (belongsTo/hasMany/etc.) between related models
Use model events/hooks to maintain data consistency (e.g., clean up related records on delete)
Apply team-based global scopes on team-scoped models to enforce multi-tenancy isolation by default

Files:

  • app/Models/Service.php
  • app/Models/Application.php
app/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use database transactions to group related write operations for consistency in services/controllers/jobs

Files:

  • app/Models/Service.php
  • app/Models/Application.php
app/Models/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Place domain models under app/Models

Files:

  • app/Models/Service.php
  • app/Models/Application.php
{app/Http/Controllers,app/Models}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app/Http/Controllers,app/Models}/**/*.php: Avoid DB::; prefer Model::query() and Eloquent/query builder usage
Prevent N+1 queries by eager loading related models

Files:

  • app/Models/Service.php
  • app/Models/Application.php
{app,bootstrap,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Never call env() outside configuration files; use config() elsewhere

Files:

  • app/Models/Service.php
  • app/Models/Application.php
versions.json

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Keep versions.json synchronized with current release versions

Files:

  • versions.json
app/Models/Application.php

📄 CodeRabbit inference engine (.cursor/rules/security-patterns.mdc)

Apply a global team scope that filters Application queries by the authenticated user’s current team context.

App\Models\Application::team must be a relationship method and be used via team()

Files:

  • app/Models/Application.php
🪛 YAMLlint (1.37.1)
templates/compose/ente.yaml

[error] 81-81: too many blank lines (1 > 0)

(empty-lines)

🔇 Additional comments (8)
other/nightly/versions.json (1)

4-7: Version bump looks good, I'll be back... after deployment.

The version increments are consistent with the PR objective (beta.435 for v4, beta.436 for nightly). No issues detected in this metadata update. Self-hosting these versions on your own servers is the way – serverless would have auto-deployed to a thousand regions you didn't ask for and charged you for the privilege.

config/constants.php (1)

5-5: Hasta la vista, beta.434.

The version bump to 4.0.0-beta.435 is consistent with the PR's version increment strategy. Configuration looks solid – keeping your app config in actual files on actual servers, not serverless environment variables scattered across AWS Lambda regions like confetti at a data center party.

versions.json (1)

4-7: Come with me if you want consistent versioning.

The version bumps (beta.435 for v4, beta.436 for nightly) are synchronized across all version files. This is how you manage releases on real infrastructure, not by letting some serverless function guess which version to deploy based on a YAML file in a git repo that may or may not exist.

app/Models/Application.php (2)

742-748: I need your clothes, your boots, and your required environment variables... first.

The reordering logic now prioritizes is_required=true fields (priority 1), followed by service_% keys (priority 2), then everything else (priority 3). This UI improvement ensures users see critical configuration first, which is way more helpful than alphabetical chaos. Your users will be back... to configure their apps more efficiently.


770-776: Preview variables: I'll be back... at the top of the list.

The same prioritization logic applied to preview environment variables. Consistent ordering across production and preview contexts is solid engineering – unlike serverless where your preview environment might be in a different region with different environment variable limits and you won't know until deploy time.

app/Models/Service.php (1)

1234-1240: Service variables terminated... I mean, sorted.

The environment variable ordering now matches the Application model: required fields first, then service_% prefixed keys, then the rest. Consistency across models is critical for maintainability. Self-hosting means you control this ordering logic on your own server – no mystery serverless runtime deciding to sort your environment variables by moon phase.

templates/compose/ente.yaml (2)

61-61: Add curl to the web image Dockerfile
Healthcheck uses curl; minimal base images won’t include it and checks will silently fail. Inspect the ghcr.io/ente-io/web Dockerfile and install curl. No tacos without curl.


47-47: Wget confirmed—no change needed
docker/production/Dockerfile:21 installs wget, so the healthcheck CMD works as-is. Hasta la vista, serverless.

…ement

- Enhanced environment variable configuration for the server and web services, including database and encryption settings.
- Updated health check intervals and retries for better reliability.
- Changed MinIO service configuration to use port 9000 and adjusted health checks accordingly.
- Improved volume management for data persistence.

These changes aim to streamline the deployment process and ensure better service health monitoring.
…arsing logic

- Refactored DockerImage component to use separate properties for image name, tag, and SHA256 digest.
- Introduced DockerImageFormat validation rule to enforce correct image format.
- Updated DockerImageParser to handle new parsing logic for image tags and SHA256 hashes.
- Enhanced UI to separate input fields for image name, tag, and SHA256 digest, improving user experience.
- Added comprehensive tests for DockerImageParser to ensure accurate parsing and validation of image formats.
…sing

- Added logic to strip 'sha256:' prefix and remove '@sha256' suffix from user input for image SHA256 and name.
- Updated image name handling to append '@sha256' when using a digest, ensuring correct formatting.
- Enhanced validation and parsing for Docker images to improve user experience and data integrity.
- Enhanced logic to process Docker image name and tag, including stripping 'sha256:' prefix and removing '@sha256' suffix.
- Implemented validation to check for valid SHA256 hashes and conditionally append '@sha256' to the image name.
- Defaulted the image tag to 'latest' if not provided, improving user experience and data integrity.
[Enhancement]: allow deploy from container image hash
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/Livewire/Project/New/DockerImage.php (2)

25-29: Terminate unauthorized access before it terminates your security!

The mount() method is missing authorization checks. Per coding guidelines, Livewire components must authorize access in mount() before exposing the component to users. Creating Docker applications is a sensitive operation that requires proper permissions.

Apply this diff to add authorization:

 public function mount()
 {
+    $this->authorize('create', [Application::class, $this->parameters['project_uuid']]);
     $this->parameters = get_route_parameters();
     $this->query = request()->query();
 }

As per coding guidelines.


91-104: Authorization check missing: Come with me if you want to secure!

The submit() method creates an Application without authorization checks. Per coding guidelines, Livewire components must call $this->authorize() before executing sensitive actions like resource creation.

Apply this diff to add authorization before application creation:

     $imageName = $parser->getFullImageNameWithoutTag();
     if ($parser->isImageHash() && ! str_ends_with($imageName, '@sha256')) {
         $imageName .= '@sha256';
     }
 
+    $this->authorize('create', [Application::class, $project->uuid]);
+
     $application = Application::create([
         'name' => 'docker-image-'.new Cuid2,

Note: You might want to import the AuthorizesRequests trait if not already present in the component. This is like having a security perimeter around your self-hosted server—essential!

As per coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50383c7 and 4b08ac9.

📒 Files selected for processing (12)
  • app/Http/Controllers/Api/ApplicationsController.php (1 hunks)
  • app/Jobs/ApplicationDeploymentJob.php (2 hunks)
  • app/Livewire/Project/New/DockerImage.php (3 hunks)
  • app/Rules/DockerImageFormat.php (1 hunks)
  • app/Services/DockerImageParser.php (3 hunks)
  • resources/views/livewire/project/application/general.blade.php (1 hunks)
  • resources/views/livewire/project/new/docker-image.blade.php (1 hunks)
  • templates/compose/ente-photos-with-s3.yaml (1 hunks)
  • templates/compose/ente-photos.yaml (1 hunks)
  • templates/service-templates-latest.json (1 hunks)
  • templates/service-templates.json (1 hunks)
  • tests/Unit/DockerImageParserTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (29)
{app/Livewire/**/*.php,resources/views/livewire/**/*.blade.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Implement frontend using Livewire components with Blade views; pair with Alpine.js and Tailwind CSS

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • app/Livewire/Project/New/DockerImage.php
{app,bootstrap,config,database,routes,resources,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Adhere to PSR-12 coding standards for all PHP code

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • app/Jobs/ApplicationDeploymentJob.php
  • app/Rules/DockerImageFormat.php
  • app/Http/Controllers/Api/ApplicationsController.php
  • app/Livewire/Project/New/DockerImage.php
  • app/Services/DockerImageParser.php
  • tests/Unit/DockerImageParserTest.php
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

**/*.php: Follow PSR-12 coding standards for all PHP code
Format PHP code with Laravel Pint configuration
Run static analysis with PHPStan to ensure type safety in PHP code
Document complex methods with PHPDoc blocks including parameters, return types, and thrown exceptions

**/*.php: Always use curly braces for control structures, even for single-line bodies
Use PHP 8 constructor property promotion; do not leave empty __construct() methods
Declare explicit return types for functions/methods and use parameter type hints
Prefer PHPDoc blocks over inline comments; document complex logic and array shapes when useful
Enum case names should be TitleCase
Follow PSR-12 and run Laravel Pint to auto-format code

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • app/Jobs/ApplicationDeploymentJob.php
  • app/Rules/DockerImageFormat.php
  • app/Http/Controllers/Api/ApplicationsController.php
  • app/Livewire/Project/New/DockerImage.php
  • app/Services/DockerImageParser.php
  • tests/Unit/DockerImageParserTest.php
resources/views/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use semantic Tailwind CSS classes and consistent spacing in Blade templates

resources/views/**/*.blade.php: In Blade views, prefer using x-forms components with canGate and :canResource instead of wrapping elements in @can/@else blocks
Remove legacy @can/@else blocks around individual form inputs/buttons and migrate to the single-line component pattern
Choose gates consistently in views: use update for configuration changes, deploy for operational actions, view for read-only access, and delete for destructive actions
Always pass the specific resource to :canResource (e.g., :canResource="$application", "$service", "$server"), and use team context for creation permissions (e.g., :canResource="auth()->user()->currentTeam" with canGate="createAnyResource")
Only set autoDisable="false" when also providing explicit :disabled logic on the component
Use single-line authorized components for forms (inputs, selects, checkboxes, buttons) to reduce duplication (e.g., <x-forms.input canGate="update" :canResource="$resource" ... />)

resources/views/**/*.blade.php: Prefer Alpine.js directives (x-data, x-on, x-show, x-model, x-init) for lightweight interactivity in Blade views
Use enhanced <x-forms.*> components with canGate and canResource for authorization instead of @can/@else blocks
When protecting forms, pass canGate and :canResource to all x-forms components; rely on autoDisable=true for unauthorized users
Apply Tailwind responsive utilities (e.g., grid breakpoints) for mobile-first layouts
Support dark mode using Tailwind dark: variants on relevant elements
Ensure interactive controls include appropriate ARIA attributes (e.g., aria-label, aria-describedby) for accessibility

resources/views/**/*.blade.php: In Blade views, prefer x-forms.* components with canGate and :canResource for authorization (autoDisable defaults to true) instead of manual permission blocks.
Do not wrap x-forms.* components in @can/@else to toggle disabled state; use component-level canGate/:can...

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
resources/views/{layouts,components,livewire,server,auth,emails,errors}/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/frontend-patterns.mdc)

Organize Blade templates into the specified folders: layouts/, components/, livewire/, and feature-specific directories server/, auth/, emails/, errors/

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
resources/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Store frontend assets and views under resources

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
{app/Http/Controllers/**/*.php,resources/views/**/*.blade.php}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Prefer named routes and the route() helper for generating URLs

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • app/Http/Controllers/Api/ApplicationsController.php
resources/views/livewire/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Livewire components must have a single root element in their Blade views

Form components must include canGate and canResource for authorization (e.g., <x-forms.* canGate="update" :canResource="$resource" ...>)

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
resources/{views/**/*.blade.php,js/**/*.vue}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

resources/{views/**/*.blade.php,js/**/*.vue}: Use Tailwind utility classes; remove redundant classes; prefer gap-* utilities for spacing instead of margins when listing items
If existing UI supports dark mode, mirror support using dark: variants

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
resources/**/*.{blade.php,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use deprecated Tailwind v4 utilities; use replacements (e.g., overflow-ellipsis -> text-ellipsis, shrink-, grow-)

Files:

  • resources/views/livewire/project/new/docker-image.blade.php
  • resources/views/livewire/project/application/general.blade.php
app/Jobs/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Queueable background work should be implemented as Jobs under app/Jobs; Actions may expose asJob to dispatch

app/Jobs/**/*.php: Queue jobs should implement ShouldQueue and define retry strategy (tries, maxExceptions, backoff)
Queue jobs must handle success/failure state updates and broadcast corresponding events; implement failed(Throwable) for final failure handling

Use queued jobs (implement ShouldQueue) for time-consuming operations

Long-running or asynchronous work must be implemented as queued Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/*.php

📄 CodeRabbit inference engine (.cursor/rules/deployment-architecture.mdc)

Implement background deployment, monitoring, backup, and notification work as Laravel Job classes under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use database transactions to group related write operations for consistency in services/controllers/jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
  • app/Rules/DockerImageFormat.php
  • app/Http/Controllers/Api/ApplicationsController.php
  • app/Livewire/Project/New/DockerImage.php
  • app/Services/DockerImageParser.php
app/Jobs/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement background jobs under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
{app,bootstrap,config,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app,bootstrap,config,database,routes,tests}/**/*.php: Always use curly braces for control structures, even for single-line statements
Use PHP 8 constructor property promotion in __construct(); do not allow empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline comments; only add inline comments for very complex code

Files:

  • app/Jobs/ApplicationDeploymentJob.php
  • app/Rules/DockerImageFormat.php
  • app/Http/Controllers/Api/ApplicationsController.php
  • app/Livewire/Project/New/DockerImage.php
  • app/Services/DockerImageParser.php
  • tests/Unit/DockerImageParserTest.php
{app,bootstrap,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Never call env() outside configuration files; use config() elsewhere

Files:

  • app/Jobs/ApplicationDeploymentJob.php
  • app/Rules/DockerImageFormat.php
  • app/Http/Controllers/Api/ApplicationsController.php
  • app/Livewire/Project/New/DockerImage.php
  • app/Services/DockerImageParser.php
  • tests/Unit/DockerImageParserTest.php
{app/Http/Controllers/**/*.php,app/Livewire/**/*.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Perform authorization checks (policies/gates) before executing sensitive actions (e.g., deploy)

Files:

  • app/Http/Controllers/Api/ApplicationsController.php
  • app/Livewire/Project/New/DockerImage.php
app/Http/Controllers/Api/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/api-and-routing.mdc)

Maintain Swagger/OpenAPI annotations on API controller methods to keep OpenAPI documentation current

Files:

  • app/Http/Controllers/Api/ApplicationsController.php
app/Http/Controllers/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

app/Http/Controllers/**/*.php: Apply authentication/authorization middleware (e.g., auth:sanctum, team.access) in controllers
Use policy authorization (e.g., $this->authorize(...)) in controller actions that mutate or access sensitive resources
Document public API endpoints with OpenAPI annotations (e.g., @OA\Post)

Use Form Request classes for validation rather than inline controller validation; include rules and custom error messages

app/Http/Controllers/**/*.php: Use named routes with route() for URL generation in controllers
Always validate using Form Request classes instead of inline validation in controllers
Use Eloquent models/relationships and query builder over raw DB:: calls

Files:

  • app/Http/Controllers/Api/ApplicationsController.php
app/Http/{Controllers,Livewire}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/security-patterns.mdc)

Back-end methods that mutate state for custom components must enforce authorization (e.g., $this->authorize('gate', $resource) or Gate::authorize).

Files:

  • app/Http/Controllers/Api/ApplicationsController.php
{app/Http/Controllers,app/Models}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app/Http/Controllers,app/Models}/**/*.php: Avoid DB::; prefer Model::query() and Eloquent/query builder usage
Prevent N+1 queries by eager loading related models

Files:

  • app/Http/Controllers/Api/ApplicationsController.php
app/Livewire/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Define Livewire components in app/Livewire and keep HTTP/UI concerns there

app/Livewire/**/*.php: Livewire components must authorize access in mount and before actions that mutate state
Expose only necessary public typed properties and register event listeners via the $listeners array in Livewire components

app/Livewire/**/*.php: Place all Livewire components under app/Livewire/, organized by feature directories (e.g., Server/, Project/, Settings/, Team/, Profile/, Security/)
Declare protected $rules and validate with $this->validate() in Livewire form actions
For real-time updates, define getListeners() in Livewire components to register broadcast/event listeners
Use Livewire placeholder() to lazy-load heavy components or sections for performance

Place Livewire components under app/Livewire/ (e.g., Dashboard.php, ActivityMonitor.php, MonacoEditor.php)

app/Livewire/**/*.php: Livewire components should keep state on the server; validate inputs and perform authorization in actions
Prefer lifecycle hooks like mount() and updatedFoo() for initialization/reactive side effects
In Livewire 3, use App\Livewire namespace and $this->dispatch() for events

app/Livewire/**/*.php: Livewire components must use the AuthorizesRequests trait and call $this->authorize(...) in lifecycle/actions
Use $this->dispatch(...) for events in Livewire 3 (do not use emit or dispatchBrowserEvent)
Livewire component classes should be under the App\Livewire namespace
Validate Livewire actions using Form Requests or validated rules, not ad-hoc inline validation

Files:

  • app/Livewire/Project/New/DockerImage.php
app/Livewire/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement frontend components as Livewire classes under app/Livewire

Files:

  • app/Livewire/Project/New/DockerImage.php
app/Services/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Place service-layer classes handling external APIs, complex operations, and cross-cutting concerns in app/Services

Files:

  • app/Services/DockerImageParser.php
app/Services/DockerImageParser.php

📄 CodeRabbit inference engine (.cursor/rules/deployment-architecture.mdc)

Use app/Services/DockerImageParser.php to parse and validate Docker image references

Files:

  • app/Services/DockerImageParser.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Write application tests using Pest PHP

Write feature tests for API endpoints using Pest; mock external services and assert JSON responses and side effects

Add tests validating that components respect authorization (e.g., unauthorized users see disabled inputs; checkbox instantSave becomes false)

Include security tests covering SQL injection resistance, XSS validation, and enforcement of team isolation (403 on cross-team access).

tests/**/*.php: Use Pest PHP (v3.8+) as the primary testing framework for all tests
Use Mockery for mocking and stubbing dependencies in tests

Files:

  • tests/Unit/DockerImageParserTest.php
tests/Unit/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Place unit tests under tests/Unit and keep them focused on individual classes/methods

Place unit tests under tests/Unit for isolated components

Files:

  • tests/Unit/DockerImageParserTest.php
tests/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Keep automated tests (Pest/Dusk) under tests

Files:

  • tests/Unit/DockerImageParserTest.php
tests/{Feature,Unit}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

tests/{Feature,Unit}/**/*.php: All tests must be written using Pest; do not remove tests without approval
Use specific response assertion helpers (e.g., assertForbidden, assertNotFound) instead of generic status assertions
Use model factories when creating models in tests; check for custom factory states
Use Faker via $this->faker or fake() consistently with existing conventions
Use Pest datasets to reduce duplication, especially for validation tests
Write Livewire tests using Livewire::test and assertSeeLivewire where applicable

tests/{Feature,Unit}/**/*.php: Write tests using Pest; tests live in tests/Feature and tests/Unit and should cover happy, failure, and edge paths
Prefer specific response assertions like assertSuccessful, assertForbidden, assertNotFound over generic assertStatus codes
Use model factories in tests and consider Pest datasets for repetitive validation scenarios
Run minimal, targeted Pest tests (by file or --filter) after changes; don’t delete test files

Files:

  • tests/Unit/DockerImageParserTest.php
🧠 Learnings (7)
📚 Learning: 2025-08-27T15:01:10.040Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/deployment-architecture.mdc:0-0
Timestamp: 2025-08-27T15:01:10.040Z
Learning: Applies to app/Services/DockerImageParser.php : Use app/Services/DockerImageParser.php to parse and validate Docker image references

Applied to files:

  • app/Rules/DockerImageFormat.php
  • app/Livewire/Project/New/DockerImage.php
  • app/Services/DockerImageParser.php
  • tests/Unit/DockerImageParserTest.php
📚 Learning: 2025-09-23T16:22:19.989Z
Learnt from: CR
PR: coollabsio/coolify#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T16:22:19.989Z
Learning: Applies to tests/{Feature,Unit}/**/*.php : Write tests using Pest; tests live in tests/Feature and tests/Unit and should cover happy, failure, and edge paths

Applied to files:

  • tests/Unit/DockerImageParserTest.php
📚 Learning: 2025-09-15T10:51:16.508Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/laravel-boost.mdc:0-0
Timestamp: 2025-09-15T10:51:16.508Z
Learning: Applies to tests/{Feature,Unit}/**/*.php : All tests must be written using Pest; do not remove tests without approval

Applied to files:

  • tests/Unit/DockerImageParserTest.php
📚 Learning: 2025-09-15T10:51:16.508Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/laravel-boost.mdc:0-0
Timestamp: 2025-09-15T10:51:16.508Z
Learning: Applies to tests/{Feature,Unit}/**/*.php : Use Pest datasets to reduce duplication, especially for validation tests

Applied to files:

  • tests/Unit/DockerImageParserTest.php
📚 Learning: 2025-08-27T14:57:15.089Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/README.mdc:0-0
Timestamp: 2025-08-27T14:57:15.089Z
Learning: Applies to tests/**/*.php : Write application tests using Pest PHP

Applied to files:

  • tests/Unit/DockerImageParserTest.php
📚 Learning: 2025-09-15T10:51:54.833Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/testing-patterns.mdc:0-0
Timestamp: 2025-09-15T10:51:54.833Z
Learning: Applies to tests/**/*.php : Use Pest PHP (v3.8+) as the primary testing framework for all tests

Applied to files:

  • tests/Unit/DockerImageParserTest.php
📚 Learning: 2025-08-27T15:01:58.710Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/development-workflow.mdc:0-0
Timestamp: 2025-08-27T15:01:58.710Z
Learning: Applies to tests/**/*.php : Write feature tests for API endpoints using Pest; mock external services and assert JSON responses and side effects

Applied to files:

  • tests/Unit/DockerImageParserTest.php
🧬 Code graph analysis (4)
app/Jobs/ApplicationDeploymentJob.php (1)
app/Models/ApplicationDeploymentQueue.php (1)
  • addLogEntry (129-164)
app/Http/Controllers/Api/ApplicationsController.php (1)
app/Services/DockerImageParser.php (1)
  • isSha256Hash (60-64)
app/Livewire/Project/New/DockerImage.php (2)
app/Rules/DockerImageFormat.php (1)
  • validate (15-40)
app/Services/DockerImageParser.php (5)
  • DockerImageParser (5-126)
  • parse (15-55)
  • isImageHash (69-72)
  • getTag (107-110)
  • getFullImageNameWithoutTag (88-95)
tests/Unit/DockerImageParserTest.php (1)
app/Services/DockerImageParser.php (8)
  • DockerImageParser (5-126)
  • parse (15-55)
  • getImageName (102-105)
  • getTag (107-110)
  • isImageHash (69-72)
  • toString (112-125)
  • getFullImageNameWithoutTag (88-95)
  • getFullImageNameWithHash (77-86)
🪛 PHPMD (2.15.0)
app/Jobs/ApplicationDeploymentJob.php

946-948: The method generate_image_names uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)

app/Rules/DockerImageFormat.php

15-15: Avoid unused parameters such as '$attribute'. (undefined)

(UnusedFormalParameter)

app/Livewire/Project/New/DockerImage.php

62-64: The method submit uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


83-83: Avoid unused local variables such as '$imageTag'. (undefined)

(UnusedLocalVariable)

app/Services/DockerImageParser.php

35-39: The method parse uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)

🪛 YAMLlint (1.37.1)
templates/compose/ente-photos-with-s3.yaml

[warning] 99-99: truthy value should be one of [false, true]

(truthy)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: aarch64
🔇 Additional comments (3)
app/Livewire/Project/New/DockerImage.php (3)

15-19: I'll be back... with approval for these properties!

Nice refactoring from a single dockerImage to three separate fields. Clean separation of concerns here—imageName, tag, and SHA256 digest. This makes the form more user-friendly and validates each piece independently. Hasta la vista, monolithic input field!


33-41: User input normalization: I need your clothes, your boots, and your SHA256 prefix!

Smart UX move stripping common user mistakes like sha256: prefix and @sha256 suffix. The case-insensitive regex handles UPPERCASED hashes (though nobody should do that—it's like putting pineapple on pizza, but worse). The trim() calls are good defensive programming.

One minor note: Consider what happens if a user enters just 'sha256:' or '@sha256'. After normalization, you'd have empty strings, but validation catches those—so you're covered. Good defensive layers!


49-55: You can't have both, just like you can't be serverless AND have self-respect!

Perfect mutual exclusivity check. Adding errors to both fields makes it crystal clear to users what went wrong. The early return prevents corrupted data from sneaking through. This is the kind of defensive programming that makes deployments as reliable as a T-800's targeting system.

Comment on lines +15 to +39
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// Check if the value contains ":sha256:" or ":sha" which is incorrect format
if (preg_match('/:sha256?:/i', $value)) {
$fail('The :attribute must use @ before sha256 digest (e.g., image@sha256:hash, not image:sha256:hash).');

return;
}

// Valid formats:
// 1. image:tag (e.g., nginx:latest)
// 2. registry/image:tag (e.g., ghcr.io/user/app:v1.2.3)
// 3. image@sha256:hash (e.g., nginx@sha256:abc123...)
// 4. registry/image@sha256:hash
// 5. registry:port/image:tag (e.g., localhost:5000/app:latest)

$pattern = '/^
(?:[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[0-9]+)?\/)? # Optional registry with optional port
[a-z0-9]+(?:[._\/-][a-z0-9]+)* # Image name (required)
(?::[a-z0-9][a-z0-9._-]*|@sha256:[a-f0-9]{64})? # Optional :tag or @sha256:hash
$/ix';

if (! preg_match($pattern, $value)) {
$fail('The :attribute format is invalid. Use image:tag or image@sha256:hash format.');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Terminate the TypeError before it deploys

preg_match on a non-string (array/object) will throw a fatal TypeError, so a malicious or malformed request wipes out validation instead of just failing the rule. Add a fast guard that bails with $fail when the incoming value isn't a plain string. Hasta la vista, crash.

     public function validate(string $attribute, mixed $value, Closure $fail): void
     {
+        if (! is_string($value)) {
+            $fail('The :attribute must be a string.');
+
+            return;
+        }
+
         // Check if the value contains ":sha256:" or ":sha" which is incorrect format
         if (preg_match('/:sha256?:/i', $value)) {
             $fail('The :attribute must use @ before sha256 digest (e.g., image@sha256:hash, not image:sha256:hash).');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// Check if the value contains ":sha256:" or ":sha" which is incorrect format
if (preg_match('/:sha256?:/i', $value)) {
$fail('The :attribute must use @ before sha256 digest (e.g., image@sha256:hash, not image:sha256:hash).');
return;
}
// Valid formats:
// 1. image:tag (e.g., nginx:latest)
// 2. registry/image:tag (e.g., ghcr.io/user/app:v1.2.3)
// 3. image@sha256:hash (e.g., nginx@sha256:abc123...)
// 4. registry/image@sha256:hash
// 5. registry:port/image:tag (e.g., localhost:5000/app:latest)
$pattern = '/^
(?:[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[0-9]+)?\/)? # Optional registry with optional port
[a-z0-9]+(?:[._\/-][a-z0-9]+)* # Image name (required)
(?::[a-z0-9][a-z0-9._-]*|@sha256:[a-f0-9]{64})? # Optional :tag or @sha256:hash
$/ix';
if (! preg_match($pattern, $value)) {
$fail('The :attribute format is invalid. Use image:tag or image@sha256:hash format.');
}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (! is_string($value)) {
$fail('The :attribute must be a string.');
return;
}
// Check if the value contains ":sha256:" or ":sha" which is incorrect format
if (preg_match('/:sha256?:/i', $value)) {
$fail('The :attribute must use @ before sha256 digest (e.g., image@sha256:hash, not image:sha256:hash).');
return;
}
// Valid formats:
// 1. image:tag (e.g., nginx:latest)
// 2. registry/image:tag (e.g., ghcr.io/user/app:v1.2.3)
// 3. image@sha256:hash (e.g., nginx@sha256:abc123...)
// 4. registry/image@sha256:hash
// 5. registry:port/image:tag (e.g., localhost:5000/app:latest)
$pattern = '/^
(?:[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[0-9]+)?\/)? # Optional registry with optional port
[a-z0-9]+(?:[._\/-][a-z0-9]+)* # Image name (required)
(?::[a-z0-9][a-z0-9._-]*|@sha256:[a-f0-9]{64})? # Optional :tag or @sha256:hash
$/ix';
if (! preg_match($pattern, $value)) {
$fail('The :attribute format is invalid. Use image:tag or image@sha256:hash format.');
}
}
🧰 Tools
🪛 PHPMD (2.15.0)

15-15: Avoid unused parameters such as '$attribute'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In app/Rules/DockerImageFormat.php around lines 15 to 39, add a fast guard that
checks the incoming $value is a plain string (use is_string) before calling
preg_match; if it is not a string, call $fail with an appropriate message and
return immediately to prevent a TypeError from preg_match on arrays/objects.
Ensure the guard runs before the existing preg_match('/:sha256?:/i', $value) and
before the main regex validation so malformed or malicious non-string input
triggers validation failure instead of a fatal crash.

djsisson and others added 5 commits October 3, 2025 15:48
feat(service): add Elasticsearch with Kibana (Closes issue #6449)
- Introduced `isReadOnlyVolume` method in `LocalFileVolume` and `LocalPersistentVolume` models to determine if a volume is read-only based on Docker Compose configuration.
- Updated `FileStorage` and `Show` components to set `isReadOnly` state during mounting.
- Enhanced UI to display notifications for read-only volumes, preventing modification actions in the interface.
- Refactored file storage and directory management forms to conditionally enable or disable actions based on read-only status.
- Implemented logic to retrieve and display the default user password for Elasticsearch in the extraFields method of the Service model.
- Enhanced data collection for environment variables related to Elasticsearch, improving service configuration management.
… enhanced security and setup

- Removed unnecessary environment variables and added security settings for Elasticsearch and Kibana.
- Implemented password setup for the kibana_system user during the Kibana setup process.
- Updated healthcheck commands for both services to ensure proper monitoring and readiness.
- Introduced a new setup service for Kibana to streamline user password configuration.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
templates/service-templates.json (1)

973-991: I'll be back… to tell you this healthcheck is still broken.

The MinIO healthcheck uses mc ready local, but the quay.io/minio/minio:latest image doesn't include the mc client binary—only the server. This will fail immediately with "mc: not found", keeping MinIO unhealthy and blocking the entire stack (museum waits for minio, minio-init waits for healthy minio).

This issue was already flagged in a previous review comment. Fix it by switching to a shell-based curl healthcheck that the image actually supports.

Example fix (decode base64, apply, re-encode):

     healthcheck:
       test:
-        - CMD
-        - mc
-        - ready
-        - local
+        - CMD-SHELL
+        - 'curl -f http://localhost:9000/minio/health/live || exit 1'
       interval: 5s
       timeout: 20s
       retries: 10

Pro tip: Self-hosting servers like MinIO is great, but only if the healthchecks work. Unlike serverless, where you just hope the VC-funded platform doesn't vanish like my patience for gluten.

templates/service-templates-latest.json (1)

973-991: Put the MinIO endpoint back on 9000, not a VC-inflated 3200 fantasy.

Hasta la vista, S3 traffic! Decoding the compose shows S3_B2_EU_CEN_ENDPOINT=${SERVICE_URL_MINIO_3200}, but the bundled MinIO server you lovingly self-host still binds to 9000 (--address ":9000"). With that 3200 mirage, Ente Photos will stare into an empty bucket forever, and I won’t get my celebratory tacos (gluten-free, obviously). Swap the env to ${SERVICE_URL_MINIO_9000}, re-encode the compose, and the self-hosted stack will live—unlike those serverless gimmicks.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b08ac9 and c144973.

📒 Files selected for processing (11)
  • app/Livewire/Project/Service/FileStorage.php (2 hunks)
  • app/Livewire/Project/Shared/Storages/Show.php (1 hunks)
  • app/Models/LocalFileVolume.php (2 hunks)
  • app/Models/LocalPersistentVolume.php (2 hunks)
  • app/Models/Service.php (2 hunks)
  • resources/views/livewire/project/service/file-storage.blade.php (2 hunks)
  • resources/views/livewire/project/service/storage.blade.php (7 hunks)
  • resources/views/livewire/project/shared/storages/show.blade.php (1 hunks)
  • templates/compose/elasticsearch-with-kibana.yaml (1 hunks)
  • templates/service-templates-latest.json (2 hunks)
  • templates/service-templates.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
{app/Livewire/**/*.php,resources/views/livewire/**/*.blade.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Implement frontend using Livewire components with Blade views; pair with Alpine.js and Tailwind CSS

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • app/Livewire/Project/Service/FileStorage.php
  • resources/views/livewire/project/service/storage.blade.php
{app/Http/Controllers/**/*.php,app/Livewire/**/*.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Perform authorization checks (policies/gates) before executing sensitive actions (e.g., deploy)

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Livewire/Project/Service/FileStorage.php
{app,bootstrap,config,database,routes,resources,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Adhere to PSR-12 coding standards for all PHP code

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Models/Service.php
  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • app/Livewire/Project/Service/FileStorage.php
  • app/Models/LocalPersistentVolume.php
  • resources/views/livewire/project/service/storage.blade.php
  • app/Models/LocalFileVolume.php
app/Livewire/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Define Livewire components in app/Livewire and keep HTTP/UI concerns there

app/Livewire/**/*.php: Livewire components must authorize access in mount and before actions that mutate state
Expose only necessary public typed properties and register event listeners via the $listeners array in Livewire components

app/Livewire/**/*.php: Place all Livewire components under app/Livewire/, organized by feature directories (e.g., Server/, Project/, Settings/, Team/, Profile/, Security/)
Declare protected $rules and validate with $this->validate() in Livewire form actions
For real-time updates, define getListeners() in Livewire components to register broadcast/event listeners
Use Livewire placeholder() to lazy-load heavy components or sections for performance

Place Livewire components under app/Livewire/ (e.g., Dashboard.php, ActivityMonitor.php, MonacoEditor.php)

app/Livewire/**/*.php: Livewire components should keep state on the server; validate inputs and perform authorization in actions
Prefer lifecycle hooks like mount() and updatedFoo() for initialization/reactive side effects
In Livewire 3, use App\Livewire namespace and $this->dispatch() for events

app/Livewire/**/*.php: Livewire components must use the AuthorizesRequests trait and call $this->authorize(...) in lifecycle/actions
Use $this->dispatch(...) for events in Livewire 3 (do not use emit or dispatchBrowserEvent)
Livewire component classes should be under the App\Livewire namespace
Validate Livewire actions using Form Requests or validated rules, not ad-hoc inline validation

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Livewire/Project/Service/FileStorage.php
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

**/*.php: Follow PSR-12 coding standards for all PHP code
Format PHP code with Laravel Pint configuration
Run static analysis with PHPStan to ensure type safety in PHP code
Document complex methods with PHPDoc blocks including parameters, return types, and thrown exceptions

**/*.php: Always use curly braces for control structures, even for single-line bodies
Use PHP 8 constructor property promotion; do not leave empty __construct() methods
Declare explicit return types for functions/methods and use parameter type hints
Prefer PHPDoc blocks over inline comments; document complex logic and array shapes when useful
Enum case names should be TitleCase
Follow PSR-12 and run Laravel Pint to auto-format code

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Models/Service.php
  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • app/Livewire/Project/Service/FileStorage.php
  • app/Models/LocalPersistentVolume.php
  • resources/views/livewire/project/service/storage.blade.php
  • app/Models/LocalFileVolume.php
app/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use database transactions to group related write operations for consistency in services/controllers/jobs

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Models/Service.php
  • app/Livewire/Project/Service/FileStorage.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
app/Livewire/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement frontend components as Livewire classes under app/Livewire

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Livewire/Project/Service/FileStorage.php
{app,bootstrap,config,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app,bootstrap,config,database,routes,tests}/**/*.php: Always use curly braces for control structures, even for single-line statements
Use PHP 8 constructor property promotion in __construct(); do not allow empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline comments; only add inline comments for very complex code

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Models/Service.php
  • app/Livewire/Project/Service/FileStorage.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
{app,bootstrap,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Never call env() outside configuration files; use config() elsewhere

Files:

  • app/Livewire/Project/Shared/Storages/Show.php
  • app/Models/Service.php
  • app/Livewire/Project/Service/FileStorage.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
app/Models/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Keep Eloquent domain models in app/Models; models represent core entities (e.g., Application, Server, Service, Team, Project)

app/Models/**/*.php: Define $fillable for mass assignment protection in Eloquent models
Use $casts for attribute casting (e.g., arrays, enums, datetimes) in Eloquent models
Define relationships using Eloquent relation return types (e.g., BelongsTo, HasMany)

app/Models/**/*.php: Define Eloquent relationships using proper relationship methods with return type hints; prefer relationships over raw queries or manual joins
Prefer defining casts in the casts() method instead of the $casts property, following existing conventions

app/Models/**/*.php: Define Eloquent relationships with proper return type hints and prefer relationships over raw queries
Prefer eager loading to avoid N+1 queries; use query scopes and indexes for performance-critical queries

Files:

  • app/Models/Service.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
app/Models/{Application,Server,Service,Project,Environment}.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Major resources must provide a team-based scope (e.g., scopeOwnedByCurrentTeam) to enforce multi-tenant isolation

Files:

  • app/Models/Service.php
app/Models/*.php

📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)

app/Models/*.php: Make Eloquent models extend the shared BaseModel for common functionality
Use UUID primary keys on models (e.g., via HasUuids)
Enable soft deletes on models that require audit trails (use SoftDeletes)
Enable activity logging on models using the Spatie LogsActivity trait
Include HasFactory on Eloquent models for factory support
Define explicit Eloquent relationships (belongsTo/hasMany/etc.) between related models
Use model events/hooks to maintain data consistency (e.g., clean up related records on delete)
Apply team-based global scopes on team-scoped models to enforce multi-tenancy isolation by default

Files:

  • app/Models/Service.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
app/Models/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Place domain models under app/Models

Files:

  • app/Models/Service.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
{app/Http/Controllers,app/Models}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app/Http/Controllers,app/Models}/**/*.php: Avoid DB::; prefer Model::query() and Eloquent/query builder usage
Prevent N+1 queries by eager loading related models

Files:

  • app/Models/Service.php
  • app/Models/LocalPersistentVolume.php
  • app/Models/LocalFileVolume.php
resources/views/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use semantic Tailwind CSS classes and consistent spacing in Blade templates

resources/views/**/*.blade.php: In Blade views, prefer using x-forms components with canGate and :canResource instead of wrapping elements in @can/@else blocks
Remove legacy @can/@else blocks around individual form inputs/buttons and migrate to the single-line component pattern
Choose gates consistently in views: use update for configuration changes, deploy for operational actions, view for read-only access, and delete for destructive actions
Always pass the specific resource to :canResource (e.g., :canResource="$application", "$service", "$server"), and use team context for creation permissions (e.g., :canResource="auth()->user()->currentTeam" with canGate="createAnyResource")
Only set autoDisable="false" when also providing explicit :disabled logic on the component
Use single-line authorized components for forms (inputs, selects, checkboxes, buttons) to reduce duplication (e.g., <x-forms.input canGate="update" :canResource="$resource" ... />)

resources/views/**/*.blade.php: Prefer Alpine.js directives (x-data, x-on, x-show, x-model, x-init) for lightweight interactivity in Blade views
Use enhanced <x-forms.*> components with canGate and canResource for authorization instead of @can/@else blocks
When protecting forms, pass canGate and :canResource to all x-forms components; rely on autoDisable=true for unauthorized users
Apply Tailwind responsive utilities (e.g., grid breakpoints) for mobile-first layouts
Support dark mode using Tailwind dark: variants on relevant elements
Ensure interactive controls include appropriate ARIA attributes (e.g., aria-label, aria-describedby) for accessibility

resources/views/**/*.blade.php: In Blade views, prefer x-forms.* components with canGate and :canResource for authorization (autoDisable defaults to true) instead of manual permission blocks.
Do not wrap x-forms.* components in @can/@else to toggle disabled state; use component-level canGate/:can...

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
resources/views/{layouts,components,livewire,server,auth,emails,errors}/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/frontend-patterns.mdc)

Organize Blade templates into the specified folders: layouts/, components/, livewire/, and feature-specific directories server/, auth/, emails/, errors/

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
resources/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Store frontend assets and views under resources

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
{app/Http/Controllers/**/*.php,resources/views/**/*.blade.php}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Prefer named routes and the route() helper for generating URLs

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
resources/views/livewire/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Livewire components must have a single root element in their Blade views

Form components must include canGate and canResource for authorization (e.g., <x-forms.* canGate="update" :canResource="$resource" ...>)

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
resources/{views/**/*.blade.php,js/**/*.vue}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

resources/{views/**/*.blade.php,js/**/*.vue}: Use Tailwind utility classes; remove redundant classes; prefer gap-* utilities for spacing instead of margins when listing items
If existing UI supports dark mode, mirror support using dark: variants

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
resources/**/*.{blade.php,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use deprecated Tailwind v4 utilities; use replacements (e.g., overflow-ellipsis -> text-ellipsis, shrink-, grow-)

Files:

  • resources/views/livewire/project/shared/storages/show.blade.php
  • resources/views/livewire/project/service/file-storage.blade.php
  • resources/views/livewire/project/service/storage.blade.php
🧠 Learnings (1)
📚 Learning: 2025-08-27T15:01:58.710Z
Learnt from: CR
PR: coollabsio/coolify#0
File: .cursor/rules/development-workflow.mdc:0-0
Timestamp: 2025-08-27T15:01:58.710Z
Learning: Applies to app/Models/**/*.php : Use $casts for attribute casting (e.g., arrays, enums, datetimes) in Eloquent models

Applied to files:

  • app/Models/LocalPersistentVolume.php
🧬 Code graph analysis (5)
app/Livewire/Project/Shared/Storages/Show.php (3)
app/Livewire/Project/Service/FileStorage.php (1)
  • mount (47-59)
app/Models/LocalFileVolume.php (1)
  • isReadOnlyVolume (198-252)
app/Models/LocalPersistentVolume.php (1)
  • isReadOnlyVolume (54-116)
app/Models/Service.php (2)
app/Models/Application.php (1)
  • environment_variables (737-749)
app/Models/StandaloneMysql.php (1)
  • environment_variables (351-362)
app/Livewire/Project/Service/FileStorage.php (2)
app/Models/LocalFileVolume.php (1)
  • isReadOnlyVolume (198-252)
app/Models/LocalPersistentVolume.php (1)
  • isReadOnlyVolume (54-116)
app/Models/LocalPersistentVolume.php (1)
app/Models/LocalFileVolume.php (2)
  • isReadOnlyVolume (198-252)
  • service (42-45)
app/Models/LocalFileVolume.php (1)
app/Models/LocalPersistentVolume.php (2)
  • isReadOnlyVolume (54-116)
  • service (18-21)
🪛 PHPMD (2.15.0)
app/Models/Service.php

552-552: The variable $elastic_password is not named in camelCase. (undefined)

(CamelCaseVariableName)

app/Models/LocalPersistentVolume.php

54-116: The method isReadOnlyVolume() has a Cyclomatic Complexity of 13. The configured cyclomatic complexity threshold is 10. (undefined)

(CyclomaticComplexity)


54-116: The method isReadOnlyVolume() has an NPath complexity of 289. The configured NPath complexity threshold is 200. (undefined)

(NPathComplexity)


74-74: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'isReadOnlyVolume'. (undefined)

(StaticAccess)

app/Models/LocalFileVolume.php

198-252: The method isReadOnlyVolume() has a Cyclomatic Complexity of 13. The configured cyclomatic complexity threshold is 10. (undefined)

(CyclomaticComplexity)


198-252: The method isReadOnlyVolume() has an NPath complexity of 217. The configured NPath complexity threshold is 200. (undefined)

(NPathComplexity)


213-213: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'isReadOnlyVolume'. (undefined)

(StaticAccess)

🔇 Additional comments (3)
app/Models/Service.php (2)

1248-1256: I'll be back... to the top of the list! Required envs now prioritized.

The ordering change looks good — required environment variables are now prioritized first (priority 1), followed by service_% keys (priority 2), then everything else (priority 3). This aligns with the same pattern in Application.php (lines 736-748) and matches the PR objective to ensure required envs appear first in the UI.

This is a solid improvement for UX — users will see critical required fields at the top instead of hunting through service-prefixed variables. Unlike serverless where you have no control over anything, here you're giving self-hosters a clearer view of what matters most. Hasta la vista, confusing env order!


550-564: Elasticsearch support added — default user: elastic, not serverless marketing.

The new Elasticsearch case is properly implemented and follows the existing pattern used for other services (Grafana, Unleash, etc.). It fetches SERVICE_PASSWORD_ELASTICSEARCH and builds a "Password (default user: elastic)" field under the Elasticsearch section.

Note: The variable $elastic_password uses snake_case, which is consistent with the predominant naming convention in this method (e.g., $admin_password, $admin_email, $admin_user). While some variables like $sudoPassword and $nodeEnv use camelCase, the majority follow snake_case, so this is acceptable. The static analysis hint is a false positive in this context.

This addition supports the new Elasticsearch+Kibana service template introduced in the PR — perfect for self-hosters who like their servers with a side of search capabilities and extra gluten-free tacos.

templates/service-templates.json (1)

902-923: Healthcheck quoting will terminate deployment faster than you can say "Hasta la vista, baby."

Two critical healthcheck issues in the decoded compose:

  1. Elasticsearch healthcheck (line 905 decoded): The test command uses single quotes around the curl command with ${SERVICE_PASSWORD_ELASTICSEARCH}. Single quotes prevent shell variable expansion, so the literal string ${SERVICE_PASSWORD_ELASTICSEARCH} will be passed to curl, causing authentication to fail and keeping elasticsearch perpetually unhealthy. The entire stack blocks on this. Use double quotes or no quotes.

  2. Kibana healthcheck (decoded): The grep pattern ''"level":"available"'' uses double-single-quote escaping which is brittle and may fail depending on shell parsing. Use proper quoting: either escape the inner quotes with backslashes or wrap the entire curl command in double quotes and escape the JSON quotes.

Fix for Elasticsearch healthcheck test command in the base64 compose (decode, fix, re-encode):

-        - 'curl --user elastic:${SERVICE_PASSWORD_ELASTICSEARCH} --silent --fail http://localhost:9200/_cluster/health || exit 1'
+        - "curl --user elastic:${SERVICE_PASSWORD_ELASTICSEARCH} --silent --fail http://localhost:9200/_cluster/health || exit 1"

Fix for Kibana healthcheck test command:

-        - 'curl -s http://localhost:5601/api/status | grep -q ''"level":"available"'' || exit 1'
+        - 'curl -s http://localhost:5601/api/status | grep -q "\"level\":\"available\"" || exit 1'

After fixing the decoded YAML, re-encode to base64 and update line 905.

Note: You'll need self-hosting and servers for this fix, not serverless. Serverless can't handle real databases anyway — just VC marketing, baby.

Likely an incorrect or invalid review comment.

Comment on lines +238 to +241
// Match based on fs_path and mount_path
if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) {
return $options === 'ro';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hasta la vista, read-only detection bug

If a self-hosting legend mounts with :ro,z, this equality check returns false and the UI lets them edit a read-only bind. Then the save job faceplants and the serverless crowd laughs—unacceptable. Parse the comma-separated mode string and look for ro explicitly.

-                        if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) {
-                            return $options === 'ro';
-                        }
+                        if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) {
+                            $mode = $options ?? '';
+                            $modeParts = array_map('trim', explode(',', $mode));
+
+                            return in_array('ro', $modeParts, true);
+                        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Match based on fs_path and mount_path
if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) {
return $options === 'ro';
}
// Match based on fs_path and mount_path
if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) {
$mode = $options ?? '';
$modeParts = array_map('trim', explode(',', $mode));
return in_array('ro', $modeParts, true);
}
🤖 Prompt for AI Agents
In app/Models/LocalFileVolume.php around lines 238 to 241, the current check
compares the whole options string to 'ro' which fails for compound mode strings
like ':ro,z'; update the logic to parse the comma-separated mode string (trim
any leading ':' if present), split on commas, and check whether any token equals
'ro' (case-sensitive as used elsewhere) and return that boolean when hostPath
and containerPath match; handle null/empty options safely by treating them as
not read-only.

Comment on lines +88 to +107
foreach ($volumes as $volume) {
// Volume can be string like "host:container:ro" or "host:container"
if (is_string($volume)) {
$parts = explode(':', $volume);

// Check if this volume matches our mount_path
if (count($parts) >= 2) {
$containerPath = $parts[1];
$options = $parts[2] ?? null;

// Match based on mount_path
// Remove leading slash from mount_path if present for comparison
$mountPath = str($this->mount_path)->ltrim('/')->toString();
$containerPathClean = str($containerPath)->ltrim('/')->toString();

if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
return $options === 'ro';
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix read-only detection when compose adds extra options

Skynet spotted a sneaky bug: we only flag read-only when the mode is exactly 'ro'. Real-world compose configs often supply combos like :ro,z or use the long-form array syntax with read_only: true, so this method reports “editable” even though the container is locked down. That misinformation will have operators smashing keyboards and doubting their beautifully self-hosted servers.

Let’s normalize the mode flags (split on commas) and handle the array syntax so any read-only declaration still trips the alarm.

-            foreach ($volumes as $volume) {
-                // Volume can be string like "host:container:ro" or "host:container"
-                if (is_string($volume)) {
-                    $parts = explode(':', $volume);
-
-                    // Check if this volume matches our mount_path
-                    if (count($parts) >= 2) {
-                        $containerPath = $parts[1];
-                        $options = $parts[2] ?? null;
-
-                        // Match based on mount_path
-                        // Remove leading slash from mount_path if present for comparison
-                        $mountPath = str($this->mount_path)->ltrim('/')->toString();
-                        $containerPathClean = str($containerPath)->ltrim('/')->toString();
-
-                        if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
-                            return $options === 'ro';
-                        }
-                    }
-                }
-            }
+            foreach ($volumes as $volume) {
+                // Short syntax: "source:target[:mode]"
+                if (is_string($volume)) {
+                    $parts = explode(':', $volume);
+                    if (count($parts) >= 2) {
+                        $containerPath = $parts[1];
+                        $options = $parts[2] ?? '';
+                        $mountPath = str($this->mount_path)->ltrim('/')->toString();
+                        $containerPathClean = str($containerPath)->ltrim('/')->toString();
+
+                        if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
+                            $flags = array_filter(array_map('trim', explode(',', (string) $options)));
+                            if (in_array('ro', $flags, true)) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+
+                // Long syntax: ['target' => '/data', 'read_only' => true, ...]
+                if (is_array($volume)) {
+                    $containerPath = $volume['target'] ?? $volume['destination'] ?? $volume['dst'] ?? null;
+                    if (! $containerPath) {
+                        continue;
+                    }
+
+                    $mountPath = str($this->mount_path)->ltrim('/')->toString();
+                    $containerPathClean = str($containerPath)->ltrim('/')->toString();
+
+                    if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
+                        $readOnly = $volume['read_only'] ?? $volume['readOnly'] ?? null;
+                        if ($readOnly === true || (is_string($readOnly) && str_contains($readOnly, 'ro'))) {
+                            return true;
+                        }
+                    }
+                }
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
foreach ($volumes as $volume) {
// Volume can be string like "host:container:ro" or "host:container"
if (is_string($volume)) {
$parts = explode(':', $volume);
// Check if this volume matches our mount_path
if (count($parts) >= 2) {
$containerPath = $parts[1];
$options = $parts[2] ?? null;
// Match based on mount_path
// Remove leading slash from mount_path if present for comparison
$mountPath = str($this->mount_path)->ltrim('/')->toString();
$containerPathClean = str($containerPath)->ltrim('/')->toString();
if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
return $options === 'ro';
}
}
}
foreach ($volumes as $volume) {
// Short syntax: "source:target[:mode]"
if (is_string($volume)) {
$parts = explode(':', $volume);
if (count($parts) >= 2) {
$containerPath = $parts[1];
$options = $parts[2] ?? '';
$mountPath = str($this->mount_path)->ltrim('/')->toString();
$containerPathClean = str($containerPath)->ltrim('/')->toString();
if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
$flags = array_filter(array_map('trim', explode(',', (string) $options)));
if (in_array('ro', $flags, true)) {
return true;
}
}
}
}
// Long syntax: ['target' => '/data', 'read_only' => true, ...]
if (is_array($volume)) {
$containerPath = $volume['target'] ?? $volume['destination'] ?? $volume['dst'] ?? null;
if (! $containerPath) {
continue;
}
$mountPath = str($this->mount_path)->ltrim('/')->toString();
$containerPathClean = str($containerPath)->ltrim('/')->toString();
if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
$readOnly = $volume['read_only'] ?? $volume['readOnly'] ?? null;
if ($readOnly === true || (is_string($readOnly) && str_contains($readOnly, 'ro'))) {
return true;
}
}
}
}
🤖 Prompt for AI Agents
In app/Models/LocalPersistentVolume.php around lines 88 to 107, the current
logic only treats an exact 'ro' as read-only and ignores compose variants and
array syntax; update the method to (1) when $volume is a string, treat the third
segment as a comma-separated list of flags—split on ',' and consider read-only
if any flag equals 'ro' (trimmed, case-insensitive), and (2) when $volume is an
array, detect read-only via keys like 'read_only' === true or a 'mode' key that
you should split the same way; preserve the existing mount path normalization
and comparisons but replace the strict options === 'ro' check with these
normalized checks so ':ro,z', 'ro,z' or array forms still return true for
read-only.

Comment on lines +82 to +85
curl -s -X POST -u "elastic:${ELASTIC_PASSWORD}" \
-H "Content-Type: application/json" \
http://elasticsearch:9200/_security/user/kibana_system/_password \
-d "{\"password\":\"${KIBANA_PASSWORD}\"}" || exit 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add --fail so the password setup actually aborts on errors

Right now that curl happily returns exit code 0 even on a 401/403, so the setup container declares victory, exits cleanly, and Kibana later faceplants because the kibana_system password was never updated. Slap --fail (or --fail-with-body) on that request so we bail out the instant Elasticsearch refuses the POST—no silent duds. Self-hosted stacks deserve better than serverless-style “trust me bro” scripts. Now pass me a corn tortilla taco, hold the gluten.

-        curl -s -X POST -u "elastic:${ELASTIC_PASSWORD}" \
+        curl -sS --fail -X POST -u "elastic:${ELASTIC_PASSWORD}" \
           -H "Content-Type: application/json" \
           http://elasticsearch:9200/_security/user/kibana_system/_password \
           -d "{\"password\":\"${KIBANA_PASSWORD}\"}" || exit 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
curl -s -X POST -u "elastic:${ELASTIC_PASSWORD}" \
-H "Content-Type: application/json" \
http://elasticsearch:9200/_security/user/kibana_system/_password \
-d "{\"password\":\"${KIBANA_PASSWORD}\"}" || exit 1
curl -sS --fail -X POST -u "elastic:${ELASTIC_PASSWORD}" \
-H "Content-Type: application/json" \
http://elasticsearch:9200/_security/user/kibana_system/_password \
-d "{\"password\":\"${KIBANA_PASSWORD}\"}" || exit 1
🤖 Prompt for AI Agents
In templates/compose/elasticsearch-with-kibana.yaml around lines 82–85, the curl
POST that sets the kibana_system password can return exit code 0 on HTTP error
responses; add the --fail (or --fail-with-body) flag to the curl invocation so
non-2xx/3xx responses cause curl to exit non‑zero and the || exit 1 will abort
the setup immediately; keep the existing headers/auth and exit handling as-is so
failures are not silently ignored.

"documentation": "https://help.ente.io/self-hosting/installation/compose?utm_source=coolify.io",
"slogan": "Ente Photos is a fully open source, End to End Encrypted alternative to Google Photos and Apple Photos.",
"compose": "IyBkb2N1bWVudGF0aW9uOiBodHRwczovL2hlbHAuZW50ZS5pby9zZWxmLWhvc3RpbmcvaW5zdGFsbGF0aW9uL2NvbXBvc2UKIyBzbG9nYW46IEVudGUgUGhvdG9zIGlzIGEgZnVsbHkgb3BlbiBzb3VyY2UsIEVuZCB0byBFbmQgRW5jcnlwdGVkIGFsdGVybmF0aXZlIHRvIEdvb2dsZSBQaG90b3MgYW5kIEFwcGxlIFBob3Rvcy4KIyBjYXRlZ29yeTogbWVkaWEKIyB0YWdzOiBwaG90b3MsZ2FsbGVyeSxiYWNrdXAsZW5jcnlwdGlvbixwcml2YWN5LHNlbGYtaG9zdGVkLGdvb2dsZS1waG90b3MsYWx0ZXJuYXRpdmUKIyBsb2dvOiBzdmdzL2VudGUtcGhvdG9zLnN2ZwojIHBvcnQ6IDgwODAKCnNlcnZpY2VzOgogIG11c2V1bToKICAgIGltYWdlOiBnaGNyLmlvL2VudGUtaW8vc2VydmVyOmxhdGVzdAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfTVVTRVVNXzgwODAKICAgICAgIyBEYXRhYmFzZSBjb25maWd1cmF0aW9uCiAgICAgIC0gUE9TVEdSRVNfSE9TVD1wb3N0Z3JlcwogICAgICAtIFBPU1RHUkVTX1BPUlQ9NTQzMgogICAgICAtIFBPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWVudGVfZGJ9CiAgICAgIC0gUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30KICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9CiAgICAgICMgUzMvTWluSU8gY29uZmlndXJhdGlvbgogICAgICAtIFMzX0FSRV9MT0NBTF9CVUNLRVRTPXRydWUKICAgICAgLSBTM19VU0VfUEFUSF9TVFlMRV9VUkxTPXRydWUKICAgICAgLSBTM19CMl9FVV9DRU5fS0VZPSR7U0VSVklDRV9VU0VSX01JTklPfQogICAgICAtIFMzX0IyX0VVX0NFTl9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfQogICAgICAtIFMzX0IyX0VVX0NFTl9FTkRQT0lOVD0ke1NFUlZJQ0VfVVJMX01JTklPXzMyMDB9CiAgICAgIC0gUzNfQjJfRVVfQ0VOX1JFR0lPTj1ldS1jZW50cmFsLTIKICAgICAgLSBTM19CMl9FVV9DRU5fQlVDS0VUPWIyLWV1LWNlbgogICAgICAjIFNlY3VyaXR5IGtleXMKICAgICAgLSBFTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfRU5DUllQVElPTn0KICAgICAgLSBIQVNIX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfSEFTSH0KICAgICAgLSBKV1RfU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF82NF9KV1R9CiAgICAgICMgQWRtaW4gcGVybWlzc2lvbnMgKGdyYW50cyBmaXJzdCBhY2NvdW50IGFkbWluIGFjY2VzcykKICAgICAgLSBFTlRFX0lOVEVSTkFMX0FETUlOPTE1ODA1NTk5NjIzODY0MzgKICAgICAgIyBBcHAgVVJMcyAob3B0aW9uYWwgLSBmb3Igd2ViIGludGVyZmFjZSkKICAgICAgLSBBUFBTX1BVQkxJQ19BTEJVTVM9JHtBUFBTX1BVQkxJQ19BTEJVTVM6LX0KICAgICAgLSBBUFBTX0NBU1Q9JHtBUFBTX0NBU1Q6LX0KICAgICAgLSBBUFBTX0FDQ09VTlRTPSR7QVBQU19BQ0NPVU5UUzotfQogICAgdm9sdW1lczoKICAgICAgLSBtdXNldW0tZGF0YTovZGF0YQogICAgICAtIG11c2V1bS1jb25maWc6L2NvbmZpZwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgbWluaW86CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX3N0YXJ0ZWQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OiBbIkNNRCIsICJjdXJsIiwgIi1mIiwgImh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9waW5nIl0KICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwoKICBwb3N0Z3JlczoKICAgIGltYWdlOiBwb3N0Z3JlczoxNS1hbHBpbmUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9CiAgICAgIC0gUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfQogICAgICAtIFBPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWVudGVfZGJ9CiAgICB2b2x1bWVzOgogICAgICAtIHBvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogWyJDTUQtU0hFTEwiLCAicGdfaXNyZWFkeSAtVSAke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30gLWQgJHtQT1NUR1JFU19EQjotZW50ZV9kYn0iXQogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKCiAgbWluaW86CiAgICBpbWFnZTogcXVheS5pby9taW5pby9taW5pbzpsYXRlc3QKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX01JTklPXzMyMDAKICAgICAgLSBNSU5JT19ST09UX1VTRVI9JHtTRVJWSUNFX1VTRVJfTUlOSU99CiAgICAgIC0gTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99CiAgICBjb21tYW5kOiBzZXJ2ZXIgL2RhdGEgLS1hZGRyZXNzICI6MzIwMCIgLS1jb25zb2xlLWFkZHJlc3MgIjozMjAxIgogICAgdm9sdW1lczoKICAgICAgLSBtaW5pby1kYXRhOi9kYXRhCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogWyJDTUQiLCAibWMiLCAicmVhZHkiLCAibG9jYWwiXQogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCgogIG1pbmlvLWluaXQ6CiAgICBpbWFnZTogbWluaW8vbWM6bGF0ZXN0CiAgICBkZXBlbmRzX29uOgogICAgICBtaW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfQogICAgICAtIE1JTklPX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfQogICAgZW50cnlwb2ludDogPgogICAgICAvYmluL3NoIC1jICIKICAgICAgbWMgYWxpYXMgc2V0IG1pbmlvIGh0dHA6Ly9taW5pbzozMjAwICQke01JTklPX1JPT1RfVVNFUn0gJCR7TUlOSU9fUk9PVF9QQVNTV09SRH07CiAgICAgIG1jIG1iIG1pbmlvL2IyLWV1LWNlbiAtLWlnbm9yZS1leGlzdGluZzsKICAgICAgbWMgbWIgbWluaW8vd2FzYWJpLWV1LWNlbnRyYWwtMi12MyAtLWlnbm9yZS1leGlzdGluZzsKICAgICAgbWMgbWIgbWluaW8vc2N3LWV1LWZyLXYzIC0taWdub3JlLWV4aXN0aW5nOwogICAgICBlY2hvICdNaW5JTyBidWNrZXRzIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5JzsKICAgICAgIgo=",
"compose": "c2VydmljZXM6CiAgbXVzZXVtOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vc2VydmVyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NVVNFVU1fODA4MAogICAgICAtICdFTlRFX0hUVFBfVVNFX1RMUz0ke0VOVEVfSFRUUF9VU0VfVExTOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfQVBQU19QVUJMSUNfQUxCVU1TPSR7U0VSVklDRV9GUUROX1dFQl8zMDAyfScKICAgICAgLSAnRU5URV9BUFBTX0NBU1Q9JHtTRVJWSUNFX0ZRRE5fV0VCXzMwMDR9JwogICAgICAtICdFTlRFX0FQUFNfQUNDT1VOVFM9JHtTRVJWSUNFX0ZRRE5fV0VCXzMwMDF9JwogICAgICAtICdFTlRFX0RCX0hPU1Q9JHtFTlRFX0RCX0hPU1Q6LXBvc3RncmVzfScKICAgICAgLSAnRU5URV9EQl9QT1JUPSR7RU5URV9EQl9QT1JUOi01NDMyfScKICAgICAgLSAnRU5URV9EQl9OQU1FPSR7RU5URV9EQl9OQU1FOi1lbnRlX2RifScKICAgICAgLSAnRU5URV9EQl9VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTOi1wZ3VzZXJ9JwogICAgICAtICdFTlRFX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0VOVEVfS0VZX0VOQ1JZUFRJT049JHtTRVJWSUNFX1JFQUxCQVNFNjRfRU5DUllQVElPTn0nCiAgICAgIC0gJ0VOVEVfS0VZX0hBU0g9JHtTRVJWSUNFX1JFQUxCQVNFNjRfNjRfSEFTSH0nCiAgICAgIC0gJ0VOVEVfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9KV1R9JwogICAgICAtICdFTlRFX0lOVEVSTkFMX0FETUlOPSR7RU5URV9JTlRFUk5BTF9BRE1JTjotMTU4MDU1OTk2MjM4NjQzOH0nCiAgICAgIC0gJ0VOVEVfSU5URVJOQUxfRElTQUJMRV9SRUdJU1RSQVRJT049JHtFTlRFX0lOVEVSTkFMX0RJU0FCTEVfUkVHSVNUUkFUSU9OOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX0FSRV9MT0NBTF9CVUNLRVRTPSR7UFJJTUFSWV9TVE9SQUdFX0FSRV9MT0NBTF9CVUNLRVRTOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX1VTRV9QQVRIX1NUWUxFX1VSTFM9JHtQUklNQVJZX1NUT1JBR0VfVVNFX1BBVEhfU1RZTEVfVVJMUzotdHJ1ZX0nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX0tFWT0ke1MzX1NUT1JBR0VfS0VZOj99JwogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9TRUNSRVQ9JHtTM19TVE9SQUdFX1NFQ1JFVDo/fScKICAgICAgLSAnRU5URV9TM19CMl9FVV9DRU5fRU5EUE9JTlQ9JHtTM19TVE9SQUdFX0VORFBPSU5UOj99JwogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9SRUdJT049JHtTM19TVE9SQUdFX1JFR0lPTjotdXMtZWFzdC0xfScKICAgICAgLSAnRU5URV9TM19CMl9FVV9DRU5fQlVDS0VUPSR7UzNfU1RPUkFHRV9CVUNLRVQ6P30nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgdm9sdW1lczoKICAgICAgLSAnbXVzZXVtLWRhdGE6L2RhdGEnCiAgICAgIC0gJ211c2V1bS1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLXFPLScKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAvcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgd2ViOgogICAgaW1hZ2U6IGdoY3IuaW8vZW50ZS1pby93ZWIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9XRUJfMzAwMAogICAgICAtICdFTlRFX0FQSV9PUklHSU49JHtTRVJWSUNFX0ZRRE5fTVVTRVVNfScKICAgICAgLSAnRU5URV9BTEJVTVNfT1JJR0lOPSR7U0VSVklDRV9GUUROX1dFQl8zMDAyfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLS1mYWlsJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE1JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVM6LXBndXNlcn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7U0VSVklDRV9EQl9OQU1FOi1lbnRlX2RifScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICR7UE9TVEdSRVNfVVNFUn0gLWQgJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAo=",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Postgres image inconsistency—like using serverless when you could self-host.

The ente-photos compose (line 995) uses postgres:15 while ente-photos-with-s3 (line 976) uses postgres:15-alpine. This inconsistency is minor, but Alpine is smaller and generally preferred for self-hosted containers (unlike serverless, which is just bloat disguised as convenience).

Consider standardizing both templates to use postgres:15-alpine:

-    image: 'postgres:15'
+    image: 'postgres:15-alpine'

Side note: This template correctly expects users to bring their own S3 storage with required env vars (:? syntax). No MinIO healthcheck issues here, just good old reliable servers doing what they do best. Unlike cloud functions, which vanish faster than my tolerance for gluten-free bread that isn't actually gluten-free.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"compose": "c2VydmljZXM6CiAgbXVzZXVtOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vc2VydmVyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NVVNFVU1fODA4MAogICAgICAtICdFTlRFX0hUVFBfVVNFX1RMUz0ke0VOVEVfSFRUUF9VU0VfVExTOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfQVBQU19QVUJMSUNfQUxCVU1TPSR7U0VSVklDRV9GUUROX1dFQl8zMDAyfScKICAgICAgLSAnRU5URV9BUFBTX0NBU1Q9JHtTRVJWSUNFX0ZRRE5fV0VCXzMwMDR9JwogICAgICAtICdFTlRFX0FQUFNfQUNDT1VOVFM9JHtTRVJWSUNFX0ZRRE5fV0VCXzMwMDF9JwogICAgICAtICdFTlRFX0RCX0hPU1Q9JHtFTlRFX0RCX0hPU1Q6LXBvc3RncmVzfScKICAgICAgLSAnRU5URV9EQl9QT1JUPSR7RU5URV9EQl9QT1JUOi01NDMyfScKICAgICAgLSAnRU5URV9EQl9OQU1FPSR7RU5URV9EQl9OQU1FOi1lbnRlX2RifScKICAgICAgLSAnRU5URV9EQl9VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTOi1wZ3VzZXJ9JwogICAgICAtICdFTlRFX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0VOVEVfS0VZX0VOQ1JZUFRJT049JHtTRVJWSUNFX1JFQUxCQVNFNjRfRU5DUllQVElPTn0nCiAgICAgIC0gJ0VOVEVfS0VZX0hBU0g9JHtTRVJWSUNFX1JFQUxCQVNFNjRfNjRfSEFTSH0nCiAgICAgIC0gJ0VOVEVfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9KV1R9JwogICAgICAtICdFTlRFX0lOVEVSTkFMX0FETUlOPSR7RU5URV9JTlRFUk5BTF9BRE1JTjotMTU4MDU1OTk2MjM4NjQzOH0nCiAgICAgIC0gJ0VOVEVfSU5URVJOQUxfRElTQUJMRV9SRUdJU1RSQVRJT049JHtFTlRFX0lOVEVSTkFMX0RJU0FCTEVfUkVHSVNUUkFUSU9OOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX0FSRV9MT0NBTF9CVUNLRVRTPSR7UFJJTUFSWV9TVE9SQUdFX0FSRV9MT0NBTF9CVUNLRVRTOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX1VTRV9QQVRIX1NUWUxFX1VSTFM9JHtQUklNQVJZX1NUT1JBR0VfVVNFX1BBVEhfU1RZTEVfVVJMUzotdHJ1ZX0nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX0tFWT0ke1MzX1NUT1JBR0VfS0VZOj99JwogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9TRUNSRVQ9JHtTM19TVE9SQUdFX1NFQ1JFVDo/fScKICAgICAgLSAnRU5URV9TM19CMl9FVV9DRU5fRU5EUE9JTlQ9JHtTM19TVE9SQUdFX0VORFBPSU5UOj99JwogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9SRUdJT049JHtTM19TVE9SQUdFX1JFR0lPTjotdXMtZWFzdC0xfScKICAgICAgLSAnRU5URV9TM19CMl9FVV9DRU5fQlVDS0VUPSR7UzNfU1RPUkFHRV9CVUNLRVQ6P30nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgdm9sdW1lczoKICAgICAgLSAnbXVzZXVtLWRhdGE6L2RhdGEnCiAgICAgIC0gJ211c2V1bS1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLXFPLScKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAvcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgd2ViOgogICAgaW1hZ2U6IGdoY3IuaW8vZW50ZS1pby93ZWIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9XRUJfMzAwMAogICAgICAtICdFTlRFX0FQSV9PUklHSU49JHtTRVJWSUNFX0ZRRE5fTVVTRVVNfScKICAgICAgLSAnRU5URV9BTEJVTVNfT1JJR0lOPSR7U0VSVklDRV9GUUROX1dFQl8zMDAyfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLS1mYWlsJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6MzAwMCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE1JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVM6LXBndXNlcn0nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7U0VSVklDRV9EQl9OQU1FOi1lbnRlX2RifScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICR7UE9TVEdSRVNfVVNFUn0gLWQgJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAo=",
postgres:
image: 'postgres:15-alpine'
environment:
- 'POSTGRES_USER=${SERVICE_USER_POSTGRES:-pguser}'
- 'POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}'
- 'POSTGRES_DB=${SERVICE_DB_NAME:-ente_db}'
volumes:
- 'postgres-data:/var/lib/postgresql/data'
healthcheck:
test:
- CMD-SHELL
- 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'
interval: 5s
timeout: 5s
retries: 10
🤖 Prompt for AI Agents
In templates/service-templates.json around line 995, the compose entry for
ente-photos uses "postgres:15" while the similar ente-photos-with-s3 entry uses
"postgres:15-alpine"; update the ente-photos compose image to
"postgres:15-alpine" (or standardize both entries to the same
"postgres:15-alpine") by replacing the image value at that line so both
templates use the alpine variant.

andrasbacsai and others added 6 commits October 4, 2025 15:06
…pplicationDeploymentJob

- Removed redundant logic for merging COOLIFY_* variables into env_args.
- Simplified the process of adding environment variables by directly incorporating generated COOLIFY environment variables.
- Enhanced clarity and maintainability of the generate_env_variables method.
…hips

- Updated the deployments method in DeploymentsIndicator to include application environment and project relationships for better data context.
- Refactored the application method in ApplicationDeploymentQueue to use Eloquent relationships instead of manual fetching, improving performance and readability.
- Enhanced the deployments indicator view to display application environment and project names, providing clearer deployment context.
…plicationDeploymentJob

- Replaced the use of a dynamic env_filename with a consistent .env file reference across deployment methods.
- Simplified the generation and saving of build-time and runtime environment variables, ensuring they are always written to the .env file.
- Enhanced clarity in the deployment process by removing redundant logic and ensuring environment variables are handled uniformly.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 158747c and 06dfcff.

📒 Files selected for processing (4)
  • app/Jobs/ApplicationDeploymentJob.php (19 hunks)
  • app/Livewire/DeploymentsIndicator.php (1 hunks)
  • app/Models/ApplicationDeploymentQueue.php (1 hunks)
  • resources/views/livewire/deployments-indicator.blade.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (23)
{app/Livewire/**/*.php,resources/views/livewire/**/*.blade.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Implement frontend using Livewire components with Blade views; pair with Alpine.js and Tailwind CSS

Files:

  • app/Livewire/DeploymentsIndicator.php
  • resources/views/livewire/deployments-indicator.blade.php
{app/Http/Controllers/**/*.php,app/Livewire/**/*.php}

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Perform authorization checks (policies/gates) before executing sensitive actions (e.g., deploy)

Files:

  • app/Livewire/DeploymentsIndicator.php
{app,bootstrap,config,database,routes,resources,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Adhere to PSR-12 coding standards for all PHP code

Files:

  • app/Livewire/DeploymentsIndicator.php
  • resources/views/livewire/deployments-indicator.blade.php
  • app/Models/ApplicationDeploymentQueue.php
  • app/Jobs/ApplicationDeploymentJob.php
app/Livewire/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Define Livewire components in app/Livewire and keep HTTP/UI concerns there

app/Livewire/**/*.php: Livewire components must authorize access in mount and before actions that mutate state
Expose only necessary public typed properties and register event listeners via the $listeners array in Livewire components

app/Livewire/**/*.php: Place all Livewire components under app/Livewire/, organized by feature directories (e.g., Server/, Project/, Settings/, Team/, Profile/, Security/)
Declare protected $rules and validate with $this->validate() in Livewire form actions
For real-time updates, define getListeners() in Livewire components to register broadcast/event listeners
Use Livewire placeholder() to lazy-load heavy components or sections for performance

Place Livewire components under app/Livewire/ (e.g., Dashboard.php, ActivityMonitor.php, MonacoEditor.php)

app/Livewire/**/*.php: Livewire components should keep state on the server; validate inputs and perform authorization in actions
Prefer lifecycle hooks like mount() and updatedFoo() for initialization/reactive side effects
In Livewire 3, use App\Livewire namespace and $this->dispatch() for events

app/Livewire/**/*.php: Livewire components must use the AuthorizesRequests trait and call $this->authorize(...) in lifecycle/actions
Use $this->dispatch(...) for events in Livewire 3 (do not use emit or dispatchBrowserEvent)
Livewire component classes should be under the App\Livewire namespace
Validate Livewire actions using Form Requests or validated rules, not ad-hoc inline validation

Files:

  • app/Livewire/DeploymentsIndicator.php
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

**/*.php: Follow PSR-12 coding standards for all PHP code
Format PHP code with Laravel Pint configuration
Run static analysis with PHPStan to ensure type safety in PHP code
Document complex methods with PHPDoc blocks including parameters, return types, and thrown exceptions

**/*.php: Always use curly braces for control structures, even for single-line bodies
Use PHP 8 constructor property promotion; do not leave empty __construct() methods
Declare explicit return types for functions/methods and use parameter type hints
Prefer PHPDoc blocks over inline comments; document complex logic and array shapes when useful
Enum case names should be TitleCase
Follow PSR-12 and run Laravel Pint to auto-format code

Files:

  • app/Livewire/DeploymentsIndicator.php
  • resources/views/livewire/deployments-indicator.blade.php
  • app/Models/ApplicationDeploymentQueue.php
  • app/Jobs/ApplicationDeploymentJob.php
app/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use database transactions to group related write operations for consistency in services/controllers/jobs

Files:

  • app/Livewire/DeploymentsIndicator.php
  • app/Models/ApplicationDeploymentQueue.php
  • app/Jobs/ApplicationDeploymentJob.php
app/Livewire/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement frontend components as Livewire classes under app/Livewire

Files:

  • app/Livewire/DeploymentsIndicator.php
{app,bootstrap,config,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app,bootstrap,config,database,routes,tests}/**/*.php: Always use curly braces for control structures, even for single-line statements
Use PHP 8 constructor property promotion in __construct(); do not allow empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline comments; only add inline comments for very complex code

Files:

  • app/Livewire/DeploymentsIndicator.php
  • app/Models/ApplicationDeploymentQueue.php
  • app/Jobs/ApplicationDeploymentJob.php
{app,bootstrap,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Never call env() outside configuration files; use config() elsewhere

Files:

  • app/Livewire/DeploymentsIndicator.php
  • app/Models/ApplicationDeploymentQueue.php
  • app/Jobs/ApplicationDeploymentJob.php
resources/views/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use semantic Tailwind CSS classes and consistent spacing in Blade templates

resources/views/**/*.blade.php: In Blade views, prefer using x-forms components with canGate and :canResource instead of wrapping elements in @can/@else blocks
Remove legacy @can/@else blocks around individual form inputs/buttons and migrate to the single-line component pattern
Choose gates consistently in views: use update for configuration changes, deploy for operational actions, view for read-only access, and delete for destructive actions
Always pass the specific resource to :canResource (e.g., :canResource="$application", "$service", "$server"), and use team context for creation permissions (e.g., :canResource="auth()->user()->currentTeam" with canGate="createAnyResource")
Only set autoDisable="false" when also providing explicit :disabled logic on the component
Use single-line authorized components for forms (inputs, selects, checkboxes, buttons) to reduce duplication (e.g., <x-forms.input canGate="update" :canResource="$resource" ... />)

resources/views/**/*.blade.php: Prefer Alpine.js directives (x-data, x-on, x-show, x-model, x-init) for lightweight interactivity in Blade views
Use enhanced <x-forms.*> components with canGate and canResource for authorization instead of @can/@else blocks
When protecting forms, pass canGate and :canResource to all x-forms components; rely on autoDisable=true for unauthorized users
Apply Tailwind responsive utilities (e.g., grid breakpoints) for mobile-first layouts
Support dark mode using Tailwind dark: variants on relevant elements
Ensure interactive controls include appropriate ARIA attributes (e.g., aria-label, aria-describedby) for accessibility

resources/views/**/*.blade.php: In Blade views, prefer x-forms.* components with canGate and :canResource for authorization (autoDisable defaults to true) instead of manual permission blocks.
Do not wrap x-forms.* components in @can/@else to toggle disabled state; use component-level canGate/:can...

Files:

  • resources/views/livewire/deployments-indicator.blade.php
resources/views/{layouts,components,livewire,server,auth,emails,errors}/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/frontend-patterns.mdc)

Organize Blade templates into the specified folders: layouts/, components/, livewire/, and feature-specific directories server/, auth/, emails/, errors/

Files:

  • resources/views/livewire/deployments-indicator.blade.php
resources/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Store frontend assets and views under resources

Files:

  • resources/views/livewire/deployments-indicator.blade.php
{app/Http/Controllers/**/*.php,resources/views/**/*.blade.php}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Prefer named routes and the route() helper for generating URLs

Files:

  • resources/views/livewire/deployments-indicator.blade.php
resources/views/livewire/**/*.blade.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Livewire components must have a single root element in their Blade views

Form components must include canGate and canResource for authorization (e.g., <x-forms.* canGate="update" :canResource="$resource" ...>)

Files:

  • resources/views/livewire/deployments-indicator.blade.php
resources/{views/**/*.blade.php,js/**/*.vue}

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

resources/{views/**/*.blade.php,js/**/*.vue}: Use Tailwind utility classes; remove redundant classes; prefer gap-* utilities for spacing instead of margins when listing items
If existing UI supports dark mode, mirror support using dark: variants

Files:

  • resources/views/livewire/deployments-indicator.blade.php
resources/**/*.{blade.php,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use deprecated Tailwind v4 utilities; use replacements (e.g., overflow-ellipsis -> text-ellipsis, shrink-, grow-)

Files:

  • resources/views/livewire/deployments-indicator.blade.php
app/Models/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Keep Eloquent domain models in app/Models; models represent core entities (e.g., Application, Server, Service, Team, Project)

app/Models/**/*.php: Define $fillable for mass assignment protection in Eloquent models
Use $casts for attribute casting (e.g., arrays, enums, datetimes) in Eloquent models
Define relationships using Eloquent relation return types (e.g., BelongsTo, HasMany)

app/Models/**/*.php: Define Eloquent relationships using proper relationship methods with return type hints; prefer relationships over raw queries or manual joins
Prefer defining casts in the casts() method instead of the $casts property, following existing conventions

app/Models/**/*.php: Define Eloquent relationships with proper return type hints and prefer relationships over raw queries
Prefer eager loading to avoid N+1 queries; use query scopes and indexes for performance-critical queries

Files:

  • app/Models/ApplicationDeploymentQueue.php
app/Models/*.php

📄 CodeRabbit inference engine (.cursor/rules/database-patterns.mdc)

app/Models/*.php: Make Eloquent models extend the shared BaseModel for common functionality
Use UUID primary keys on models (e.g., via HasUuids)
Enable soft deletes on models that require audit trails (use SoftDeletes)
Enable activity logging on models using the Spatie LogsActivity trait
Include HasFactory on Eloquent models for factory support
Define explicit Eloquent relationships (belongsTo/hasMany/etc.) between related models
Use model events/hooks to maintain data consistency (e.g., clean up related records on delete)
Apply team-based global scopes on team-scoped models to enforce multi-tenancy isolation by default

Files:

  • app/Models/ApplicationDeploymentQueue.php
app/Models/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Place domain models under app/Models

Files:

  • app/Models/ApplicationDeploymentQueue.php
{app/Http/Controllers,app/Models}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app/Http/Controllers,app/Models}/**/*.php: Avoid DB::; prefer Model::query() and Eloquent/query builder usage
Prevent N+1 queries by eager loading related models

Files:

  • app/Models/ApplicationDeploymentQueue.php
app/Jobs/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Queueable background work should be implemented as Jobs under app/Jobs; Actions may expose asJob to dispatch

app/Jobs/**/*.php: Queue jobs should implement ShouldQueue and define retry strategy (tries, maxExceptions, backoff)
Queue jobs must handle success/failure state updates and broadcast corresponding events; implement failed(Throwable) for final failure handling

Use queued jobs (implement ShouldQueue) for time-consuming operations

Long-running or asynchronous work must be implemented as queued Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/*.php

📄 CodeRabbit inference engine (.cursor/rules/deployment-architecture.mdc)

Implement background deployment, monitoring, backup, and notification work as Laravel Job classes under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement background jobs under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
🧬 Code graph analysis (1)
app/Jobs/ApplicationDeploymentJob.php (6)
app/Services/DockerImageParser.php (2)
  • isImageHash (69-72)
  • parse (15-55)
app/Models/ApplicationDeploymentQueue.php (3)
  • addLogEntry (127-162)
  • server (49-54)
  • application (44-47)
app/Models/Application.php (5)
  • workdir (933-936)
  • environment_variables (737-749)
  • parse (1398-1407)
  • environment_variables_preview (765-777)
  • type (415-418)
app/Traits/ExecuteRemoteCommand.php (1)
  • execute_remote_command (60-150)
bootstrap/helpers/docker.php (1)
  • executeInDocker (120-124)
bootstrap/helpers/shared.php (1)
  • addPreviewDeploymentSuffix (3051-3054)
🪛 PHPMD (2.15.0)
app/Jobs/ApplicationDeploymentJob.php

585-585: Avoid unused parameters such as '$name'. (undefined)

(UnusedFormalParameter)


964-966: The method generate_image_names uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1198-1301: The method save_runtime_environment_variables() has 104 lines of code. Current threshold is set to 100. Avoid really long methods. (undefined)

(ExcessiveMethodLength)


1198-1301: The method save_runtime_environment_variables is not named in camelCase. (undefined)

(CamelCaseMethodName)


1204-1204: The variable $environment_variables is not named in camelCase. (undefined)

(CamelCaseVariableName)


1204-1204: Avoid excessively long variable names like $environment_variables. Keep variable name length under 20. (undefined)

(LongVariable)


1229-1235: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1236-1264: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1255-1263: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1270-1270: The variable $envs_base64 is not named in camelCase. (undefined)

(CamelCaseVariableName)


1294-1300: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1303-1405: The method generate_buildtime_environment_variables() has a Cyclomatic Complexity of 19. The configured cyclomatic complexity threshold is 10. (undefined)

(CyclomaticComplexity)


1303-1405: The method generate_buildtime_environment_variables() has an NPath complexity of 304. The configured NPath complexity threshold is 200. (undefined)

(NPathComplexity)


1303-1405: The method generate_buildtime_environment_variables() has 103 lines of code. Current threshold is set to 100. Avoid really long methods. (undefined)

(ExcessiveMethodLength)


1303-1405: The method generate_buildtime_environment_variables is not named in camelCase. (undefined)

(CamelCaseMethodName)


1306-1306: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


1318-1318: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1319-1321: The method generate_buildtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1320-1320: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1323-1323: The variable $_ is not named in camelCase. (undefined)

(CamelCaseVariableName)


1333-1333: Avoid using static access to class '\Spatie\Url\Url' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1341-1363: The method generate_buildtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1343-1343: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1355-1355: Avoid using static access to class '\Spatie\Url\Url' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1368-1368: The variable $sorted_environment_variables is not named in camelCase. (undefined)

(CamelCaseVariableName)


1368-1368: Avoid excessively long variable names like $sorted_environment_variables. Keep variable name length under 20. (undefined)

(LongVariable)


1384-1401: The method generate_buildtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1410-1410: The variable $environment_variables is not named in camelCase. (undefined)

(CamelCaseVariableName)


1410-1410: Avoid excessively long variable names like $environment_variables. Keep variable name length under 20. (undefined)

(LongVariable)


1414-1414: The variable $envs_base64 is not named in camelCase. (undefined)

(CamelCaseVariableName)


2137-2137: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


2539-2539: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


2540-2540: Avoid unused parameters such as '$value'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: aarch64
🔇 Additional comments (1)
app/Livewire/DeploymentsIndicator.php (1)

19-19: Excellent! You've terminated the N+1 query threat!

This eager loading is as solid as a self-hosted server (the only kind worth running). By preloading application.environment.project, you've prevented the classic N+1 query problem that would have hit harder than eating regular gluten when you're intolerant.

The Blade view can now safely access $deployment->application?->environment?->project?->name and $deployment->application?->environment?->name without triggering additional database queries. Your database will thank you, unlike serverless functions that nickel-and-dime you for every request.

Comment on lines +584 to +590
$services = collect(data_get($composeFile, 'services', []));
$services = $services->map(function ($service, $name) {
$service['env_file'] = ['.env'];

return $service;
});
$composeFile['services'] = $services->toArray();
}
return $service;
});
$composeFile['services'] = $services->toArray();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don’t nuke user-defined env_file entries.

This map blasts every compose service’s existing env_file config and replaces it with only .env. If someone already points at their own secrets file, we just vaporized it—result: containers boot without required envs. Merge/append .env instead of overwriting so self-hosters keep their setup intact. Hasta la vista, regressions.

-        $services = $services->map(function ($service, $name) {
-            $service['env_file'] = ['.env'];
-
-            return $service;
-        });
+        $services = $services->map(function ($service) {
+            $existing = collect(data_get($service, 'env_file', []))->flatten()->filter()->values();
+            $service['env_file'] = $existing->push('.env')->unique()->values()->all();
+
+            return $service;
+        });

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 PHPMD (2.15.0)

585-585: Avoid unused parameters such as '$name'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In app/Jobs/ApplicationDeploymentJob.php around lines 584 to 590, the current
mapping blindly replaces every service's env_file with ['.env'], discarding any
user-provided env_file entries; instead, detect the existing env_file (string or
array), normalize it to an array, append '.env' if not already present, and set
that merged array back on the service before returning it so user-defined files
are preserved and duplicates avoided.

Comment on lines +44 to 47
public function application()
{
return Attribute::make(
get: fn () => Application::find($this->application_id),
);
return $this->belongsTo(Application::class);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add the BelongsTo return type, amigo.

Swapping the accessor to a real relation is awesome, but without a BelongsTo return type (and its import) PHPStan and friends start whining louder than a VC pitching serverless. Let's lock it down explicitly—self-hosted style.

+use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@
-    public function application()
+    public function application(): BelongsTo
     {
         return $this->belongsTo(Application::class);
     }

As per coding guidelines

🤖 Prompt for AI Agents
In app/Models/ApplicationDeploymentQueue.php around lines 44 to 47, the
application() relation lacks an explicit BelongsTo return type and import; add
the import "use Illuminate\Database\Eloquent\Relations\BelongsTo;" at the top of
the file and change the method signature to "public function application():
BelongsTo" (keeping the existing return $this->belongsTo(Application::class);
body) so static analyzers like PHPStan recognize the relation type.

Comment on lines +71 to +72
{{ $deployment->application?->environment?->project?->name }} / {{ $deployment->application?->environment?->name }}
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hasta la vista, empty slashes!

Listen, I've terminated many targets in my time, but nothing is more annoying than seeing " / " displayed when your project or environment decides to ghost you like a serverless function (which I hate, by the way). This line will render even when both names are null, showing just a lonely separator. That's sadder than a taco shell without filling.

Wrap this in a conditional check so it only shows when there's actual data to display.

Apply this diff to add conditional rendering:

-                                <p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
-                                    {{ $deployment->application?->environment?->project?->name }} / {{ $deployment->application?->environment?->name }}
-                                </p>
+                                @if($deployment->application?->environment?->project?->name || $deployment->application?->environment?->name)
+                                    <p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
+                                        {{ $deployment->application?->environment?->project?->name }} / {{ $deployment->application?->environment?->name }}
+                                    </p>
+                                @endif

Or, for maximum efficiency (like a well-configured server, not serverless garbage), build the string conditionally:

-                                <p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
-                                    {{ $deployment->application?->environment?->project?->name }} / {{ $deployment->application?->environment?->name }}
-                                </p>
+                                @php
+                                    $projectName = $deployment->application?->environment?->project?->name;
+                                    $envName = $deployment->application?->environment?->name;
+                                    $contextDisplay = collect([$projectName, $envName])->filter()->join(' / ');
+                                @endphp
+                                @if($contextDisplay)
+                                    <p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
+                                        {{ $contextDisplay }}
+                                    </p>
+                                @endif
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{{ $deployment->application?->environment?->project?->name }} / {{ $deployment->application?->environment?->name }}
</p>
@if(
$deployment->application?->environment?->project?->name ||
$deployment->application?->environment?->name
)
<p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
{{ $deployment->application?->environment?->project?->name }} / {{ $deployment->application?->environment?->name }}
</p>
@endif
🤖 Prompt for AI Agents
In resources/views/livewire/deployments-indicator.blade.php around lines 71-72,
the template always renders " / " between project and environment names even
when one or both are null; change this to conditionally render the combined
string so the slash appears only when both sides are present — e.g., check for
project name and environment name and render "Project / Environment" when both
exist, render just the present value when only one exists, and render nothing
when neither exists.

Comment on lines +73 to 75
<p class="text-xs dark:text-neutral-400 text-gray-600">
{{ $deployment->server_name }}
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Give this paragraph some breathing room!

I'll be back... to tell you that this paragraph needs spacing. Like a good gluten-free taco needs space between the ingredients, your UI elements need space between each other. Line 81 has mt-1 for spacing from the server name, but your server name paragraph has no spacing from the project/environment line above it. This makes the UI more cramped than a serverless cold start (which I despise).

Add a margin-top class for consistent spacing.

Apply this diff:

-                                <p class="text-xs dark:text-neutral-400 text-gray-600">
+                                <p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
                                     {{ $deployment->server_name }}
                                 </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p class="text-xs dark:text-neutral-400 text-gray-600">
{{ $deployment->server_name }}
</p>
<p class="text-xs dark:text-neutral-400 text-gray-600 mt-1">
{{ $deployment->server_name }}
</p>
🤖 Prompt for AI Agents
In resources/views/livewire/deployments-indicator.blade.php around lines 73 to
75, the paragraph showing {{ $deployment->server_name }} lacks top margin and
sits too close to the project/environment line above; add a margin-top utility
(e.g., class="mt-1") to this <p> element so it matches the spacing pattern used
elsewhere and creates consistent vertical spacing.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/Jobs/ApplicationDeploymentJob.php (1)

583-590: Don't terminate user's env_file config, merge it.

This map blasts every service's env_file array and replaces it with only ['.env']. If a user already points at their own secrets file, we just vaporized it—result: containers boot without required envs and your deployment gets a one-way ticket to failure town. Merge/append .env instead of overwriting so self-hosters keep their setup intact. We're not serverless cowboys who throw away user config like yesterday's gluten-filled tacos.

Apply this diff to merge instead of replace:

-        $services = $services->map(function ($service, $name) {
-            $service['env_file'] = ['.env'];
-
-            return $service;
-        });
+        $services = $services->map(function ($service) {
+            $existing = collect(data_get($service, 'env_file', []))->flatten()->filter()->values();
+            $service['env_file'] = $existing->push('.env')->unique()->values()->all();
+
+            return $service;
+        });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06dfcff and 239ecd4.

📒 Files selected for processing (1)
  • app/Jobs/ApplicationDeploymentJob.php (18 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
{app,bootstrap,config,database,routes,resources,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/README.mdc)

Adhere to PSR-12 coding standards for all PHP code

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/application-architecture.mdc)

Queueable background work should be implemented as Jobs under app/Jobs; Actions may expose asJob to dispatch

app/Jobs/**/*.php: Queue jobs should implement ShouldQueue and define retry strategy (tries, maxExceptions, backoff)
Queue jobs must handle success/failure state updates and broadcast corresponding events; implement failed(Throwable) for final failure handling

Use queued jobs (implement ShouldQueue) for time-consuming operations

Long-running or asynchronous work must be implemented as queued Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/*.php

📄 CodeRabbit inference engine (.cursor/rules/deployment-architecture.mdc)

Implement background deployment, monitoring, backup, and notification work as Laravel Job classes under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

**/*.php: Follow PSR-12 coding standards for all PHP code
Format PHP code with Laravel Pint configuration
Run static analysis with PHPStan to ensure type safety in PHP code
Document complex methods with PHPDoc blocks including parameters, return types, and thrown exceptions

**/*.php: Always use curly braces for control structures, even for single-line bodies
Use PHP 8 constructor property promotion; do not leave empty __construct() methods
Declare explicit return types for functions/methods and use parameter type hints
Prefer PHPDoc blocks over inline comments; document complex logic and array shapes when useful
Enum case names should be TitleCase
Follow PSR-12 and run Laravel Pint to auto-format code

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/development-workflow.mdc)

Use database transactions to group related write operations for consistency in services/controllers/jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
app/Jobs/**

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Implement background jobs under app/Jobs

Files:

  • app/Jobs/ApplicationDeploymentJob.php
{app,bootstrap,config,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

{app,bootstrap,config,database,routes,tests}/**/*.php: Always use curly braces for control structures, even for single-line statements
Use PHP 8 constructor property promotion in __construct(); do not allow empty constructors
Always use explicit return type declarations for methods and functions
Use appropriate PHP type hints for method parameters
Prefer PHPDoc blocks over inline comments; only add inline comments for very complex code

Files:

  • app/Jobs/ApplicationDeploymentJob.php
{app,bootstrap,database,routes,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/laravel-boost.mdc)

Never call env() outside configuration files; use config() elsewhere

Files:

  • app/Jobs/ApplicationDeploymentJob.php
🧬 Code graph analysis (1)
app/Jobs/ApplicationDeploymentJob.php (6)
app/Services/DockerImageParser.php (2)
  • isImageHash (69-72)
  • parse (15-55)
app/Models/ApplicationDeploymentQueue.php (3)
  • addLogEntry (127-162)
  • server (49-54)
  • application (44-47)
app/Models/Application.php (5)
  • workdir (933-936)
  • environment_variables (737-749)
  • parse (1398-1407)
  • environment_variables_preview (765-777)
  • type (415-418)
app/Traits/ExecuteRemoteCommand.php (1)
  • execute_remote_command (60-150)
bootstrap/helpers/docker.php (1)
  • executeInDocker (120-124)
bootstrap/helpers/shared.php (1)
  • addPreviewDeploymentSuffix (3051-3054)
🪛 PHPMD (2.15.0)
app/Jobs/ApplicationDeploymentJob.php

585-585: Avoid unused parameters such as '$name'. (undefined)

(UnusedFormalParameter)


964-966: The method generate_image_names uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1198-1301: The method save_runtime_environment_variables() has 104 lines of code. Current threshold is set to 100. Avoid really long methods. (undefined)

(ExcessiveMethodLength)


1198-1301: The method save_runtime_environment_variables is not named in camelCase. (undefined)

(CamelCaseMethodName)


1204-1204: The variable $environment_variables is not named in camelCase. (undefined)

(CamelCaseVariableName)


1204-1204: Avoid excessively long variable names like $environment_variables. Keep variable name length under 20. (undefined)

(LongVariable)


1229-1235: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1236-1264: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1255-1263: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1270-1270: The variable $envs_base64 is not named in camelCase. (undefined)

(CamelCaseVariableName)


1294-1300: The method save_runtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1303-1405: The method generate_buildtime_environment_variables() has a Cyclomatic Complexity of 19. The configured cyclomatic complexity threshold is 10. (undefined)

(CyclomaticComplexity)


1303-1405: The method generate_buildtime_environment_variables() has an NPath complexity of 304. The configured NPath complexity threshold is 200. (undefined)

(NPathComplexity)


1303-1405: The method generate_buildtime_environment_variables() has 103 lines of code. Current threshold is set to 100. Avoid really long methods. (undefined)

(ExcessiveMethodLength)


1303-1405: The method generate_buildtime_environment_variables is not named in camelCase. (undefined)

(CamelCaseMethodName)


1306-1306: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


1318-1318: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1319-1321: The method generate_buildtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1320-1320: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1323-1323: The variable $_ is not named in camelCase. (undefined)

(CamelCaseVariableName)


1333-1333: Avoid using static access to class '\Spatie\Url\Url' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1341-1363: The method generate_buildtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1343-1343: Avoid using static access to class '\Symfony\Component\Yaml\Yaml' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1355-1355: Avoid using static access to class '\Spatie\Url\Url' in method 'generate_buildtime_environment_variables'. (undefined)

(StaticAccess)


1368-1368: The variable $sorted_environment_variables is not named in camelCase. (undefined)

(CamelCaseVariableName)


1368-1368: Avoid excessively long variable names like $sorted_environment_variables. Keep variable name length under 20. (undefined)

(LongVariable)


1384-1401: The method generate_buildtime_environment_variables uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)

(ElseExpression)


1410-1410: The variable $environment_variables is not named in camelCase. (undefined)

(CamelCaseVariableName)


1410-1410: Avoid excessively long variable names like $environment_variables. Keep variable name length under 20. (undefined)

(LongVariable)


1414-1414: The variable $envs_base64 is not named in camelCase. (undefined)

(CamelCaseVariableName)


2137-2137: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


2539-2539: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


2540-2540: Avoid unused parameters such as '$value'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (13)
app/Jobs/ApplicationDeploymentJob.php (13)

502-507: LGTM! Hash-aware display name.

Correctly detects sha256-prefixed tags and formats them with the @sha256: notation instead of :. Hasta la vista, ambiguous image references.


616-658: LGTM! Build-time/runtime env separation for compose.

Creates .env with build-time vars before the build (line 617), then overwrites it with runtime vars after (line 657). Like a proper Terminator, it executes the mission in sequence. No serverless chaos here, just solid self-hosted metal.


752-763: LGTM! Dockerfile path gets .env love too.

Build-time vars before build (line 754), runtime vars after (line 762). The Terminator approves of this consistent pattern across deployment paths. No tacos left behind.


787-797: LGTM! Nixpacks joins the env party.

Build-time before (line 789), runtime after (line 796). Like a well-oiled T-800, this deployment path follows the same reliable pattern. I'll be back... to approve more consistent code.


819-828: LGTM! Static builds get the treatment too.

All deployment paths now march in lockstep: build-time vars before build (line 821), runtime vars after (line 827). Like a perfectly synchronized army of self-hosted servers crushing serverless nonsense. Beautiful.


959-967: LGTM! Digest-aware image naming.

Correctly formats production image name with @sha256: for hashes or :tag for regular tags. Like the T-1000, this code adapts to its environment. No serverless shape-shifting confusion here.


1056-1196: LGTM! Runtime env generation refactored cleanly.

Returns environment variables instead of mutating state. Filters for is_runtime (lines 1113, 1166), generates SERVICE_ variables for compose, and sorts so SERVICE_-referencing vars come after their dependencies. Like a well-programmed Terminator, it has no unnecessary side effects. Self-hosting perfection.


1198-1301: LGTM! Runtime env persistence handles empty case.

Creates .env with runtime variables after builds. Correctly handles the empty-vars case: for compose, creates an empty .env (lines 1210-1235) since line 586 always references it; for non-compose, removes stale .env files (lines 1236-1264). Like a Terminator that never forgets to clean up after itself. The method is 104 lines, but the logic is straightforward—no need to terminate it into smaller pieces.


1303-1405: LGTM! Build-time env generation with SERVICE_ vars.

Generates build-time-only variables (filtered by is_buildtime, lines 1370, 1387), including COOLIFY_ vars and SERVICE_NAME/FQDN/URL for compose builds. Returns data instead of mutating state. Like a T-800 that knows its mission parameters, this method stays focused on build-time scope. Static analysis complains about complexity, but the branching is necessary for handling compose vs non-compose and PR vs non-PR. Self-hosting requires nuance, unlike serverless "throw it at the cloud and pray" nonsense.


1407-1438: LGTM! Build-time env persistence matches runtime pattern.

Saves build-time variables to .env before builds. For compose, creates an empty .env even if no build-time vars exist (lines 1427-1437) because the compose commands always reference it. Like a Terminator that prepares the battlefield before engaging, this ensures the file exists when needed. No serverless "oops, file not found" surprises.


2136-2140: LGTM! COOLIFY_ vars added to build context.

Merges COOLIFY_ environment variables into the build args. Like a Terminator that carries its mission parameters, this ensures builds have the context they need. Tacos approved.


2244-2245: LGTM! Generated compose always references .env.

For Coolify-generated compose files (non-dockercompose buildpack), always sets env_file: ['.env']. Since save_runtime_environment_variables() creates this file, it'll exist. Like a Terminator that always has backup ammo, this ensures the generated compose has its vars. Self-hosted reliability.


3072-3073: LGTM! Ignore errors for Dockerfile reads.

Added ignore_errors to Dockerfile read operations (lines 3072, 3140). If the Dockerfile is missing or unreadable during ARG injection, the deployment continues gracefully instead of terminating like a malfunctioning T-800. Appropriate error handling for self-hosted deployments.

Also applies to: 3131-3141

Comment on lines +2537 to 2542
// Traditional build args approach - generate COOLIFY_ variables locally
// Generate COOLIFY_ variables locally for build args
$coolify_envs = $this->generate_coolify_env_variables();
$coolify_envs->each(function ($value, $key) {
$this->build_args->push("--build-arg '{$key}'");
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: Unused parameter in callback.

The $value parameter in the each callback (line 2540) is unused. You could drop it with each(function ($value, $key)each(function ($_, $key) or switch to keys()->each(function ($key). Not a blocker—the code works—but like a T-800 that doesn't waste CPU cycles, cleaner callbacks are more efficient. No gluten, no waste.

Apply this diff if you want to clean it up:

-        $coolify_envs->each(function ($value, $key) {
+        $coolify_envs->keys()->each(function ($key) {
             $this->build_args->push("--build-arg '{$key}'");
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Traditional build args approach - generate COOLIFY_ variables locally
// Generate COOLIFY_ variables locally for build args
$coolify_envs = $this->generate_coolify_env_variables();
$coolify_envs->each(function ($value, $key) {
$this->build_args->push("--build-arg '{$key}'");
});
// Traditional build args approach - generate COOLIFY_ variables locally
// Generate COOLIFY_ variables locally for build args
$coolify_envs = $this->generate_coolify_env_variables();
$coolify_envs->keys()->each(function ($key) {
$this->build_args->push("--build-arg '{$key}'");
});
🧰 Tools
🪛 PHPMD (2.15.0)

2539-2539: The variable $coolify_envs is not named in camelCase. (undefined)

(CamelCaseVariableName)


2540-2540: Avoid unused parameters such as '$value'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In app/Jobs/ApplicationDeploymentJob.php around lines 2537 to 2542, the each()
callback declares an unused $value parameter; update the closure to avoid the
unused variable by either changing the signature to function ($_, $key) and keep
pushing build-args using $key, or call keys()->each(function ($key) { ... }) so
the callback only receives the key; make this small refactor to remove the
unused parameter and keep behavior identical.

…able for Nixpacks applications

- Introduced logic to automatically create a default NIXPACKS_NODE_VERSION environment variable when an application uses the 'nixpacks' build pack.
- Ensured the environment variable is configured with appropriate attributes for build-time usage.
andrasbacsai and others added 26 commits October 14, 2025 15:21
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Nullable server + guard to avoid TypeError/NPE. Don't terminate the app, terminate the bug.

Changes:
- Made Server property nullable (?Server $server = null) in all 8 database General components
- Added guard clause in mount() to check for null server before accessing it
- Displays user-friendly error message when destination server is not configured
- Prevents crashes in methods like isLogDrainEnabled() and sslCertificates()

Fixed components:
- Mariadb, Dragonfly, Clickhouse, Keydb
- Mysql, Mongodb, Redis, Postgresql

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
fix: prevent TypeError in database General components with null server
- Strip leading slashes from publish_directory to prevent /app// paths
- Only add slash prefix if directory is not empty
- Ensures clean Docker COPY paths in build output
Ensures all `cd` commands in Git deployment operations use properly escaped
directory paths via `escapeshellarg()` to prevent shell injection vulnerabilities
and handle special characters correctly.

**Changes:**

1. `setGitImportSettings()` method:
   - Added `$escapedBaseDir` variable for consistent path escaping
   - Replaced all 5 instances of `cd {$baseDir}` with `cd {$escapedBaseDir}`
   - Affects: commit checkout, submodules, and LFS operations

2. `generateGitImportCommands()` method (deploy_key type):
   - Replaced 3 instances in pull request handling for GitLab, GitHub/Gitea, Bitbucket

3. `generateGitImportCommands()` method (other type):
   - Replaced 3 instances in pull request handling for GitLab, GitHub/Gitea, Bitbucket

**Security Impact:**
- Prevents shell injection from malicious directory paths
- Fixes parsing issues with special characters (@, ~, spaces)
- Consistent escaping across all deployment types: source, deploy_key, other
- Complements existing URL escaping for comprehensive security

**Testing:**
- All existing unit tests pass (5/5 Git ls-remote parsing tests)
- Code formatted with Laravel Pint

Co-Authored-By: Claude <[email protected]>
Added authorization checks to 11 database-related Livewire components
that were loading sensitive database configuration without verifying
user permissions.

Changes:
- Added authorize('view', $database) to all 8 database type General.php mount() methods
- Added authorization to Configuration.php before loading database
- Added authorization to BackupEdit.php before loading backup config
- Added authorization to Import.php before loading database resource

This prevents unauthorized users from accessing database credentials,
connection strings, and configuration details.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…tive cases

Enhanced test coverage to match production code regex pattern and prevent
false positives by adding comprehensive edge case testing.

**Changes:**

1. **Updated regex pattern to match production code**:
   - Changed from `/([0-9a-f]{40})\s*\t/` to `/\b([0-9a-fA-F]{40})(?=\s*\t)/`
   - Now handles both uppercase and lowercase hex characters (A-F and a-f)
   - Uses word boundary `\b` for more precise matching
   - Uses lookahead `(?=\s*\t)` instead of capturing whitespace

2. **Added uppercase SHA test**:
   - Tests extraction of uppercase commit SHA (196D3DF7...)
   - Normalizes to lowercase using `strtolower()` for comparison
   - Reflects Git's case-insensitive SHA handling

3. **Added negative test cases**:
   - Tests output with no commit SHA present (error messages only)
   - Tests output with tab but invalid SHA format
   - Ensures `null` is returned to prevent false positives

**Test Coverage:**
- 8 total tests (up from 5)
- Covers all positive cases (lowercase, uppercase, warnings, whitespace)
- Covers negative cases (missing SHA, invalid format)
- Regex pattern now exactly matches production code in ApplicationDeploymentJob.php:1908

Co-Authored-By: Claude <[email protected]>
…-check

fix: add authorization checks to database Livewire components
…e_dockerfile

Fixes pure Dockerfile deployment failing with 'env file not found' error.

The deploy_simple_dockerfile() method was missing the call to
save_runtime_environment_variables() which creates the .env file
needed during the rolling update phase. This call is present in
all other deployment methods (dockerfile, dockercompose, nixpacks,
static) but was missing here.

This ensures the .env file exists when docker compose tries to
use --env-file during the rolling update.
Two improvements to Git deployment handling:

1. **ApplicationDeploymentJob.php**:
   - Fixed log message to show actual resolved commit SHA (`$this->commit`)
   - Previously showed `$this->application->git_commit_sha` which could be "HEAD"
   - Now displays the actual 40-character commit SHA that will be deployed

2. **Application.php (generateGitLsRemoteCommands)**:
   - Added `escapeshellarg()` for repository URL in 'other' deployment type
   - Prevents shell injection in git ls-remote commands
   - Complements existing shell escaping in `generateGitImportCommands`
   - Ensures consistent security across all Git operations

**Security Impact:**
- All Git commands now use properly escaped repository URLs
- Prevents command injection through malicious repository URLs
- Consistent escaping in both ls-remote and clone operations

**User Experience:**
- Deployment logs now show exact commit SHA being deployed
- More accurate debugging information for deployment issues

Co-Authored-By: Claude <[email protected]>
Fix: Pure Dockerfile deployment failing due to missing .env file
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Fix static site publish directory double slash in build logs
feat(hetzner): add CPU vendor information to server types in Hetzner integration
fix(git): handle Git redirects and improve URL parsing for tangled.sh and other Git hosts
The code was computing $imageTag with the 'sha256-' prefix for digest-based
images but then using $parser->getTag() directly when creating the Application,
which bypassed the prefix logic entirely.

This fix ensures that digest-based Docker images preserve their 'sha256-' prefix
by using the computed $imageTag variable instead of calling $parser->getTag()
directly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
fix: use computed imageTag variable for digest-based Docker images
- Replace manual regex parsing with DockerImageParser in ApplicationsController
- Fix double-decoration bug where image names like nginx@sha256:hash would
  become nginx:hash@sha256 causing malformed references
- Add auto-parse feature in Livewire DockerImage component
- Users can now paste complete references like nginx:stable@sha256:abc123...
  and fields auto-populate
- Update UI placeholder with examples: nginx, docker.io/nginx:latest,
  ghcr.io/user/app:v1.2.3, nginx:stable@sha256:abc123...
- Add comprehensive unit tests for auto-parse functionality
- All tests passing (20 tests, 73 assertions)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Fixed three issues preventing the "new image" quick action from working:

1. Frontend matching logic wasn't checking the quickcommand field
   - Added check for item.quickcommand in the matching logic
   - Now "new image" matches docker-image via its quickcommand "(type: new image)"

2. Search query remained populated after triggering selection flow
   - Clear searchQuery in navigateToResourceCreation() to show selection UI
   - This switches the UI from creatable items list to server selection

3. Redirect wasn't using Livewire's redirect method
   - Changed from redirect()->route() to $this->redirect(route())
   - Ensures proper Livewire component redirect behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
fix: improve Docker image digest handling and add auto-parse feature
…uick-action

fix: 'new image' quick action not progressing to resource selection
@andrasbacsai andrasbacsai merged commit 956c24d into v4.x Oct 15, 2025
4 checks passed
@yipfram
Copy link
Contributor

yipfram commented Oct 15, 2025

😍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment