Coolify orchestrates Docker-based deployments across multiple servers with automated configuration generation, zero-downtime deployments, and comprehensive monitoring.
- Application.php - Main application entity with deployment configurations
- ApplicationDeploymentQueue.php - Deployment job orchestration
- Service.php - Multi-container service definitions
- Server.php - Target deployment infrastructure
- PrivateKey.php - SSH key management for secure server access
- StandaloneDocker.php - Single container deployments
- SwarmDocker.php - Docker Swarm orchestration
Git Repository → Webhook → Coolify → Build & Deploy
- GithubApp.php - GitHub integration and webhooks
- GitlabApp.php - GitLab CI/CD integration
- Git push to configured branches
- Manual deployment via UI
- Scheduled deployments via cron
- API-triggered deployments
Source Code → Docker Build → Image Registry → Deployment
- Dockerfile detection and custom Dockerfile support
- Buildpack integration for framework detection
- Multi-stage builds for optimization
- Cache layer management for faster builds
Queue Job → Configuration Generation → Container Deployment → Health Checks
- Application/ - Core application deployment logic
- Docker/ - Docker container management
- Service/ - Multi-container service orchestration
- Proxy/ - Reverse proxy configuration
- Database/ - Database deployment and management
- Automated backup scheduling
- Connection management and health checks
- Server/ - Server provisioning and configuration
- SSH connection establishment
- Docker daemon management
- ConfigurationGenerator.php - Generates deployment configurations
- ConfigurationRepository.php - Configuration management
# Generated docker-compose.yml structure
version: '3.8'
services:
app:
image: ${APP_IMAGE}
environment:
- ${ENV_VARIABLES}
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`${FQDN}`)
volumes:
- ${VOLUME_MAPPINGS}
networks:
- coolify- Reverse proxy setup
- SSL termination with automatic certificates
- Load balancing for multiple instances
- Custom headers and routing rules
- DockerImageParser.php - Parse and validate Docker images
- Container lifecycle management
- Resource allocation and limits
- Network isolation and communication
- LocalFileVolume.php - Persistent file storage
- LocalPersistentVolume.php - Data persistence
- Backup integration for volume data
- Custom Docker networks for isolation
- Service discovery between containers
- Port mapping and exposure
- SSL/TLS termination
- Environment.php - Development, staging, production environments
- EnvironmentVariable.php - Application-specific variables
- SharedEnvironmentVariable.php - Cross-application variables
Instance Settings → Server Settings → Project Settings → Application Settings
- ApplicationPreview.php - Preview environment management
- Automatic PR/MR previews for feature branches
- Isolated environments for testing
- Automatic cleanup after merge/close
Feature Branch → Auto-Deploy → Preview URL → Review → Cleanup
- SslCertificate.php - SSL certificate automation
- Let's Encrypt integration for free certificates
- Custom certificate upload support
- Automatic renewal and monitoring
- Private Docker networks for container isolation
- SSH key-based server authentication
- Environment variable encryption
- Access control via team permissions
- ScheduledDatabaseBackup.php - Automated database backups
- ScheduledDatabaseBackupExecution.php - Backup execution tracking
- S3-compatible storage for backup destinations
- Volume snapshots for persistent data
- Configuration export for disaster recovery
- Cross-region replication for high availability
- ActivityMonitor.php - Live deployment monitoring
- WebSocket-based log streaming
- Container health checks and alerts
- Resource usage tracking
- Build process logging
- Container startup logs
- Application runtime logs
- Error tracking and alerting
Location: app/Jobs/
- Deployment jobs for async processing
- Server monitoring jobs
- Backup scheduling jobs
- Notification delivery jobs
- Redis-backed job queues
- Laravel Horizon for queue monitoring
- Failed job retry mechanisms
- Queue worker auto-scaling
- Standalone servers - Single Docker host
- Docker Swarm - Multi-node orchestration
- Remote servers - SSH-based deployment
- Local development - Docker Desktop integration
- Traefik integration for automatic load balancing
- Health check based routing
- Blue-green deployments for zero downtime
- Rolling updates with configurable strategies
Old Container → New Container Build → Health Check → Traffic Switch → Old Container Cleanup
- Parallel environments for safe deployments
- Instant rollback capability
- Database migration handling
- Configuration synchronization
- Gradual instance replacement
- Configurable update strategy
- Automatic rollback on failure
- Health check validation
Routes: routes/api.php
- RESTful endpoints for deployment management
- Webhook receivers for CI/CD integration
- Status reporting endpoints
- Deployment triggering via API
- Laravel Sanctum API tokens
- Team-based access control
- Rate limiting for API calls
- Audit logging for API usage
- Automatic rollback on deployment failure
- Health check failure handling
- Container crash recovery
- Resource exhaustion protection
- Failed deployment notifications
- Resource threshold alerts
- SSL certificate expiry warnings
- Backup failure notifications
- Docker layer caching
- Multi-stage builds for smaller images
- Build artifact reuse
- Parallel build processing
Coolify provides settings to preserve Docker build cache across deployments, addressing cache invalidation issues.
By default, Coolify injects ARG statements into user Dockerfiles for build-time variables. This breaks Docker's cache mechanism because:
- ARG declarations invalidate cache - Any change in ARG values after the
ARGinstruction invalidates all subsequent layers - SOURCE_COMMIT changes every commit - Causes full rebuilds even when code changes are minimal
Two toggles in Advanced Settings control this behavior:
| Setting | Default | Description |
|---|---|---|
inject_build_args_to_dockerfile |
true |
Controls whether Coolify adds ARG statements to Dockerfile |
include_source_commit_in_build |
false |
Controls whether SOURCE_COMMIT is included in build context |
Database columns: application_settings.inject_build_args_to_dockerfile, application_settings.include_source_commit_in_build
| Build Pack | ARG Injection | Method |
|---|---|---|
| Dockerfile | ✅ Yes | add_build_env_variables_to_dockerfile() |
Docker Compose (with build:) |
✅ Yes | modify_dockerfiles_for_compose() |
| PR Deployments (Dockerfile only) | ✅ Yes | add_build_env_variables_to_dockerfile() |
| Nixpacks | ❌ No | Generates its own Dockerfile internally |
| Static | ❌ No | Uses internal Dockerfile |
| Docker Image | ❌ No | No build phase |
When inject_build_args_to_dockerfile is enabled (default):
# Coolify modifies your Dockerfile to add:
FROM node:20
ARG MY_VAR=value
ARG COOLIFY_URL=...
ARG SOURCE_COMMIT=abc123 # (if include_source_commit_in_build is true)
# ... rest of your DockerfileWhen inject_build_args_to_dockerfile is disabled:
- Coolify does NOT modify the Dockerfile
--build-argflags are still passed (harmless without matchingARGin Dockerfile)- User must manually add
ARGstatements for any build-time variables they need
When include_source_commit_in_build is disabled (default):
SOURCE_COMMITis NOT included in build-time variablesSOURCE_COMMITis still available at runtime (in container environment)- Docker cache preserved across different commits
| Use Case | inject_build_args | include_source_commit | Cache Behavior |
|---|---|---|---|
| Maximum cache preservation | false |
false |
Best cache retention |
| Need build-time vars, no commit | true |
false |
Cache breaks on var changes |
| Need commit at build-time | true |
true |
Cache breaks every commit |
| Manual ARG management | false |
true |
Cache preserved (no ARG in Dockerfile) |
Files:
app/Jobs/ApplicationDeploymentJob.php:set_coolify_variables()- Conditionally adds SOURCE_COMMIT to Docker build context based oninclude_source_commit_in_buildsettinggenerate_coolify_env_variables(bool $forBuildTime)- Distinguishes build-time vs. runtime variables; excludes cache-busting variables like SOURCE_COMMIT from build context unless explicitly enabledgenerate_env_variables()- Populates$this->env_argswith build-time ARG values, respectinginclude_source_commit_in_buildtoggleadd_build_env_variables_to_dockerfile()- Injects ARG statements into Dockerfiles after FROM instructions; skips injection ifinject_build_args_to_dockerfileis disabledmodify_dockerfiles_for_compose()- Applies ARG injection to Docker Compose service Dockerfiles; respectsinject_build_args_to_dockerfiletoggle
app/Models/ApplicationSetting.php- Definesinject_build_args_to_dockerfileandinclude_source_commit_in_buildboolean propertiesapp/Livewire/Project/Application/Advanced.php- Livewire component providing UI bindings for cache preservation togglesresources/views/livewire/project/application/advanced.blade.php- Checkbox UI elements for user-facing toggles
Note: Docker Compose services without a build: section (image-only) are automatically skipped.
- Container resource limits
- Auto-scaling based on metrics
- Connection pooling for databases
- CDN integration for static assets
- Deployment history tracking
- Configuration changes logging
- User action auditing
- Resource access monitoring
- Retention policies for backups
- Encryption at rest for sensitive data
- Cross-region backup replication
- Recovery testing automation
- GitHub Actions compatibility
- GitLab CI pipeline integration
- Custom webhook endpoints
- Build status reporting
- S3-compatible storage integration
- External database connections
- Third-party monitoring tools
- Custom notification channels
Coolify extends standard Docker Compose with custom fields (often called "magic fields") that provide Coolify-specific functionality. These extensions are processed during deployment and stripped before sending the final compose file to Docker, maintaining full compatibility with Docker's compose specification.
Why Custom Fields?
- Enable Coolify-specific features without breaking Docker Compose compatibility
- Simplify configuration by embedding content directly in compose files
- Allow fine-grained control over health check monitoring
- Reduce external file dependencies
Processing Flow:
- User defines compose file with custom fields
- Coolify parses and processes custom fields (creates files, stores settings)
- Custom fields are stripped from final compose sent to Docker
- Docker receives standard, valid compose file
Type: Boolean
Default: false
Purpose: Exclude specific services from health check monitoring while still showing their status
Example Usage:
services:
watchtower:
image: containrrr/watchtower
exclude_from_hc: true # Don't monitor this service's health
backup:
image: postgres:16
exclude_from_hc: true # Backup containers don't need monitoring
restart: alwaysBehavior:
- Container status is still calculated from Docker state (running, exited, etc.)
- Status displays with
:excludedsuffix (e.g.,running:healthy:excluded) - UI shows "Monitoring Disabled" indicator
- Functionally equivalent to
restart: nofor health check purposes - See Container Status with All Excluded for detailed status handling
Use Cases:
- Sidecar containers (watchtower, log collectors)
- Backup/maintenance containers
- One-time initialization containers
- Containers that intentionally restart frequently
Implementation:
- Parsed:
bootstrap/helpers/parsers.php - Status logic:
app/Traits/CalculatesExcludedStatus.php - Validation:
tests/Unit/ExcludeFromHealthCheckTest.php
Volume extensions only work with long syntax (array/object format), not short syntax (string format).
Type: String (supports multiline with | or >)
Purpose: Embed file content directly in compose file for automatic creation during deployment
Example Usage:
services:
app:
image: node:20
volumes:
# Inline entrypoint script
- type: bind
source: ./entrypoint.sh
target: /app/entrypoint.sh
content: |
#!/bin/sh
set -e
echo "Starting application..."
npm run migrate
exec "$@"
# Configuration file with environment variables
- type: bind
source: ./config.xml
target: /etc/app/config.xml
content: |
<?xml version='1.0' encoding='UTF-8'?>
<config>
<database>
<host>${DB_HOST}</host>
<port>${DB_PORT}</port>
</database>
</config>Behavior:
- Content is written to the host at
sourcepath before container starts - File is created with mode
644(readable by all, writable by owner) - Environment variables in content are interpolated at deployment time
- Content is stored in
LocalFileVolumemodel (encrypted at rest) - Original
docker_compose_rawretains content for editing
Use Cases:
- Entrypoint scripts
- Configuration files
- Environment-specific settings
- Small initialization scripts
- Templates that require dynamic content
Limitations:
- Not suitable for large files (use git repo or external storage instead)
- Binary files not supported
- Changes require redeployment
Real-World Examples:
templates/compose/traccar.yaml- XML configuration filetemplates/compose/supabase.yaml- Multiple config filestemplates/compose/chaskiq.yaml- Entrypoint script
Implementation:
- Parsed:
bootstrap/helpers/parsers.phpinparseCompose()function (handlescontentfield extraction) - Storage:
app/Models/LocalFileVolume.php - Validation:
tests/Unit/StripCoolifyCustomFieldsTest.php
Type: Boolean
Default: true (if neither content nor explicit flag provided)
Purpose: Indicate whether bind mount source should be created as directory or file
Example Usage:
services:
app:
volumes:
# Explicit file
- type: bind
source: ./config.json
target: /app/config.json
is_directory: false # Create as file
# Explicit directory
- type: bind
source: ./logs
target: /var/log/app
is_directory: true # Create as directory
# Auto-detected as file (has content)
- type: bind
source: ./script.sh
target: /entrypoint.sh
content: |
#!/bin/sh
echo "Hello"
# is_directory: false implied by content presenceBehavior:
- If
is_directory: true→ Creates directory withmkdir -p - If
is_directory: false→ Creates empty file withtouch - If
contentprovided → Impliesis_directory: false - If neither specified → Defaults to
true(directory)
Naming Conventions:
is_directory(snake_case) - Preferred, consistent with PHP/Laravel conventionsisDirectory(camelCase) - Legacy support, both work identically
Use Cases:
- Disambiguating files vs directories when no content provided
- Ensuring correct bind mount type for Docker
- Pre-creating mount points before container starts
Implementation:
- Parsed:
bootstrap/helpers/parsers.phpinparseCompose()function (handlesis_directory/isDirectoryfield extraction) - Storage:
app/Models/LocalFileVolume.php(is_directorycolumn) - Validation:
tests/Unit/StripCoolifyCustomFieldsTest.php
Function: stripCoolifyCustomFields() in bootstrap/helpers/docker.php
All custom fields are removed before the compose file is sent to Docker. This happens in two contexts:
1. Validation (User-Triggered)
// In validateComposeFile() - Edit Docker Compose modal
$yaml_compose = Yaml::parse($compose);
$yaml_compose = stripCoolifyCustomFields($yaml_compose); // Strip custom fields
// Send to docker compose config for validation2. Deployment (Automatic)
// In Service::parse() - During deployment
$docker_compose = parseCompose($docker_compose_raw);
// Custom fields are processed and then stripped
// Final compose sent to Docker has no custom fieldsWhat Gets Stripped:
- Service-level:
exclude_from_hc - Volume-level:
content,isDirectory,is_directory
What's Preserved:
- All standard Docker Compose fields
- Environment variables
- Standard volume definitions (after custom fields removed)
✅ Long Syntax (Works with Custom Fields):
volumes:
- type: bind
source: ./data
target: /app/data
content: "Hello" # ✅ Custom fields work here❌ Short Syntax (Custom Fields Ignored):
volumes:
- "./data:/app/data" # ❌ Cannot add custom fields to stringsCustom fields are Coolify-specific and won't work with standalone docker compose CLI:
# ❌ Won't work - Docker doesn't recognize custom fields
docker compose -f compose.yaml up
# ✅ Works - Use Coolify's deployment (strips custom fields first)
# Deploy through Coolify UI or APIWhen editing in "Edit Docker Compose" modal:
- Custom fields are preserved in the editor
- "Validate" button strips them temporarily for Docker validation
- "Save" button preserves them in
docker_compose_raw - They're processed again on next deployment
See these templates for real-world usage:
Service Exclusions:
templates/compose/budibase.yaml- Excludes watchtower from monitoringtemplates/compose/pgbackweb.yaml- Excludes backup servicetemplates/compose/elasticsearch-with-kibana.yaml- Excludes elasticsearch
Inline Content:
templates/compose/traccar.yaml- XML configuration (multiline)templates/compose/supabase.yaml- Multiple config filestemplates/compose/searxng.yaml- Settings filetemplates/compose/invoice-ninja.yaml- Nginx config
Directory Flags:
templates/compose/paperless.yaml- Explicit directory creation
Unit Tests:
tests/Unit/StripCoolifyCustomFieldsTest.php- Custom field stripping logictests/Unit/ExcludeFromHealthCheckTest.php- Health check exclusion behaviortests/Unit/ContainerStatusAggregatorTest.php- Status aggregation with exclusions
Test Coverage:
- ✅ All custom fields (exclude_from_hc, content, isDirectory, is_directory)
- ✅ Multiline content (YAML
|syntax) - ✅ Short vs long volume syntax
- ✅ Field stripping without data loss
- ✅ Standard Docker Compose field preservation