Coolify 深度拆解:一个 44K Star 的开源 PaaS 如何用 Laravel + Docker + SSH 重新定义「自托管部署」——从 Heroku 替代品到 280+ 一键服务的全栈架构哲学
当 Vercel、Heroku、Netlify 们还在用厂商锁定捆绑开发者时,Coolify 用「一条 SSH 连接 + 你的服务器」证明了:云端的便捷和自托管的自由,从来不是二选一。
一、背景:为什么开发者需要一个「反锁定」的 PaaS?
1.1 云厂商的甜蜜陷阱
2024 年,Heroku 宣布取消免费套餐;Vercel 的边缘函数定价让中小团队直呼「用不起」;Netlify 的构建分钟数限制让 CI/CD 流水线频频中断。云 PaaS 的本质矛盾越来越清晰:
- 便捷性:
git push一键部署,自动扩缩容,零运维 - 锁定性:环境变量、构建流程、路由规则全部绑定在平台内部,迁移成本极高
- 成本递增:流量增长后账单呈指数级上升,且无法自定义硬件配置
一个程序员在 Hacker News 上的吐槽获得了 500+ 赞:
"I spent 3 years building on Heroku. When I needed to migrate, it took me 6 weeks and $2000 in contractor fees. That's the real cost of 'free tier'."
1.2 Coolify 的破局思路
Coolify 的核心哲学可以用一句话概括:把 PaaS 的便捷性从云端搬到你自己的服务器上。
它不是简单的 Docker 管理器,而是一个完整的平台即服务解决方案:
- 通过 SSH 连接管理任意服务器(VPS、物理机、树莓派)
- 支持 280+ 一键部署服务(数据库、缓存、CMS、监控等)
- 零厂商锁定:所有配置保存在你自己的服务器上
- Git 集成:支持 GitHub、GitLab、Bitbucket,推送即部署
- 自动 SSL:Let's Encrypt 证书自动签发和续期
截至 2026 年 8 月,Coolify 在 GitHub 上已获得 44,100+ Star,是自托管 PaaS 领域最受欢迎的开源项目。
二、核心概念:Coolify 的「三层抽象」架构
2.1 整体架构概览
Coolify 采用 控制面板 + 远程执行引擎 的分离式架构:
┌─────────────────────────────────────────────────────┐
│ Coolify 控制面板 │
│ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ │
│ │ Livewire │ │ Alpine.js │ │ Blade Templates │ │
│ │ (React) │ │ (Interact)│ │ (Server SSR) │ │
│ └─────┬─────┘ └─────┬─────┘ └─────────┬─────────┘ │
│ └───────────────┼─────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Laravel 11 Backend │ │
│ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ Redis │ │PostgreSQL│ │ Soketi WS │ │ │
│ │ │ (Cache) │ │ (Data) │ │ (Real-time) │ │ │
│ │ └─────────┘ └──────────┘ └────────────────┘ │ │
│ └──────────────────────┬──────────────────────────┘ │
└─────────────────────────┼───────────────────────────┘
│ SSH
▼
┌─────────────────────────────────────────────────────┐
│ 远程服务器(你的 VPS/物理机) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Docker Engine │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────────┐ │ │
│ │ │ App │ │ DB │ │Cache│ │ Nginx│ │ 280+ │ │ │
│ │ │ │ │ │ │ │ │ Proxy│ │ Services│ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ S6 Overlay (进程管理) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
这个架构的精髓在于:控制面板和被管理的服务器是完全解耦的。Coolify 面板可以运行在任何一台服务器上,通过 SSH 管理任意数量的远程服务器。这意味着:
- 控制面板崩溃不会影响正在运行的应用
- 可以用一台 1GB 内存的 VPS 管理整个集群
- 远程服务器不需要安装任何 agent,只需要 Docker + SSH
2.2 技术栈选择的哲学
Coolify 的技术栈选择体现了「实用主义至上」的工程哲学:
| 层级 | 技术选型 | 选择理由 |
|---|---|---|
| 前端 | Livewire + Alpine.js | 服务端渲染,SEO 友好,无需 SPA 复杂度 |
| 后端 | Laravel 11 | PHP 生态最成熟的框架,丰富的 ORM 和队列系统 |
| 数据库 | PostgreSQL 15 | 可靠性最高,支持 JSONB 灵活存储 |
| 缓存 | Redis 7 | 实时特性 + 队列 + 缓存三合一 |
| WebSocket | Soketi | Pusher 兼容协议,开源自托管 |
| 模板引擎 | Blade | PHP 原生模板,零学习成本 |
| CSS | Tailwind CSS | 原子化 CSS,快速迭代 UI |
| 进程管理 | S6 Overlay | 轻量级进程监督器,Docker 原生支持 |
| 构建系统 | Nixpacks | 自动检测语言并构建,类似 Buildpacks |
为什么不用 Rust/Go 重写?
这是一个经常被讨论的话题。Coolify 选择 Laravel 而非 Rust/Go 的原因是:
- 开发效率:PHP + Laravel 的开发速度远高于 Rust,一个人就能维护整个项目
- 生态成熟:Laravel 的 ORM(Eloquent)、队列(Horizon)、认证(Breeze)都是开箱即用的
- 目标用户:Coolify 的用户是开发者,不是系统管理员——他们需要的是「能用且好维护」,而不是「极致性能」
- 部署复杂度:PHP 的部署比 Rust 简单得多,降低了自托管的门槛
用项目创始人的话说:
"Coolify doesn't need to be the fastest PaaS. It needs to be the easiest to self-host and the hardest to get wrong."
三、架构深度分析:四大核心机制
3.1 SSH 远程执行引擎
Coolify 最核心的能力是通过 SSH 远程执行 Docker 命令。这不是简单的 SSH 脚本执行,而是一个完整的远程执行引擎:
// app/Models/Server.php - 核心远程执行方法
class Server extends Model
{
/**
* 通过 SSH 在远程服务器上执行命令
* 支持三种认证方式:密码、私钥、SSH Agent
*/
public function executeCommand(
string $command,
?string $cwd = null,
?int $timeout = null
): RemoteProcessResult {
$authType = $this->authentication_type; // 'password' | 'key' | 'agent'
// 构建 SSH 连接参数
$sshConfig = [
'host' => $this->ip,
'port' => $this->port ?? 22,
'user' => $this->user,
'timeout' => $timeout ?? 300,
];
// 根据认证类型设置密钥/密码
match ($authType) {
'password' => $sshConfig['password'] = decrypt($this->password),
'key' => $sshConfig['key'] = decrypt($this->private_key),
'agent' => $sshConfig['use_agent'] = true,
};
// 执行命令并收集输出
$process = Process::ssh($sshConfig, function ($ssh) use ($command, $cwd) {
if ($cwd) {
$ssh->exec("cd {$cwd} && {$command}");
} else {
$ssh->exec($command);
}
});
return new RemoteProcessResult(
output: $process->getOutput(),
exitCode: $process->getExitCode(),
error: $process->getErrorOutput()
);
}
}
SSH 连接复用优化:
Coolify 使用 SSH 连接池来避免频繁建立新连接的开销:
// app/Services/SSH/ConnectionPool.php
class ConnectionPool
{
private array $connections = [];
public function getConnection(Server $server): SshConnection
{
$key = "{$server->ip}:{$server->port}";
// 复用已有连接(5 分钟过期)
if (isset($this->connections[$key])
&& !$this->connections[$key]->isExpired()) {
return $this->connections[$key];
}
// 创建新连接
$connection = new SshConnection($server);
$this->connections[$key] = $connection;
return $connection;
}
}
3.2 Docker 部署管线
Coolify 的部署流程是一个完整的 Docker 构建 + 部署管线:
Git Push → Webhook 触发 → 拉取代码 → 检测语言 → 构建镜像 → 推送镜像 → 停止旧容器 → 启动新容器 → 健康检查 → SSL 更新
对应的代码实现:
// app/Jobs/ApplicationDeploymentJob.php
class ApplicationDeploymentJob implements ShouldQueue
{
public function handle(): void
{
$this->step(1, 'Pulling source code from Git');
$this->pullSourceCode();
$this->step(2, 'Detecting runtime and buildpack');
$this->detectRuntime(); // Node.js, Python, Go, PHP, etc.
$this->step(3, 'Building Docker image');
$this->buildDockerImage();
$this->step(4, 'Pushing image to registry');
$this->pushToRegistry();
$this->step(5, 'Stopping old containers');
$this->stopOldContainers();
$this->step(6, 'Starting new containers');
$this->startNewContainers();
$this->step(7, 'Running health checks');
$this->healthCheck();
$this->step(8, 'Updating SSL certificates');
$this->updateSSL();
$this->step(9, 'Cleanup old images');
$this->cleanup();
$this->notify('Deployment completed successfully! 🚀');
}
private function detectRuntime(): void
{
// 使用 Nixpacks 自动检测语言和构建方式
$result = $this->server->executeCommand(
"nixpacks detect {$this->appDir}"
);
$this->runtime = match(true) {
str_contains($result->output, 'node') => 'nodejs',
str_contains($result->output, 'python') => 'python',
str_contains($result->output, 'go') => 'golang',
str_contains($result->output, 'php') => 'php',
default => 'static'
};
}
}
3.3 动态服务发现与反向代理
Coolify 使用 Traefik 作为反向代理,实现了动态服务发现:
# 自动生成的 docker-compose.yml
services:
traefik:
image: traefik:v2.10
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=your@email.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
your-app:
image: your-app:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.your-app.rule=Host(`app.yourdomain.com`)"
- "traefik.http.routers.your-app.entrypoints=websecure"
- "traefik.http.routers.your-app.tls.certresolver=letsencrypt"
- "traefik.http.services.your-app.loadbalancer.server.port=3000"
volumes:
letsencrypt:
自动 SSL 签发流程:
- 新应用部署时,Coolify 自动配置 Traefik 路由规则
- Traefik 检测到新的
Host()规则后,自动向 Let's Encrypt 发起证书申请 - 证书签发后自动缓存到 Docker Volume
- 证书到期前 30 天自动续期
3.4 多服务器集群管理
Coolify 支持管理多台服务器,实现简单的集群部署:
// app/Models/Server.php - 集群管理
class Server extends Model
{
public function applications(): HasMany
{
return $this->hasMany(Application::class);
}
/**
* 检查服务器健康状态
*/
public function healthCheck(): ServerHealth
{
$result = $this->executeCommand(
'docker info --format "{{.ContainersRunning}}" && ' .
'free -m | awk \'NR==2{printf "%.1f%%", $3*100/$2}\' && ' .
'df -h / | awk \'NR==2{print $5}\''
);
return new ServerHealth(
runningContainers: (int) trim($result->output),
memoryUsage: $this->parseMemoryUsage($result->output),
diskUsage: $this->parseDiskUsage($result->output),
);
}
}
四、代码实战:5 分钟部署一个全栈应用
4.1 一键安装 Coolify
# 在你的 VPS 上执行(推荐 Ubuntu 22.04+)
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
安装脚本会自动:
- 安装 Docker 和 Docker Compose
- 部署 Coolify 控制面板(包含 PostgreSQL、Redis、Nginx)
- 配置自动更新(每天凌晨 4 点检查新版本)
- 生成默认管理员账号
安装完成后,访问 https://your-server-ip:8000 即可进入控制面板。
4.2 部署一个 Next.js 应用
Step 1:连接 Git 仓库
在 Coolify 面板中:
- 点击「New Application」→「Git Repository」
- 输入仓库地址(如
https://github.com/youruser/your-nextjs-app) - 选择分支(main)和构建目录(根目录)
Step 2:配置环境变量
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
NEXTAUTH_SECRET=your-secret-key
NEXTAUTH_URL=https://app.yourdomain.com
Step 3:配置域名和 SSL
- 在 DNS 中将
app.yourdomain.com指向你的服务器 IP - 在 Coolify 面板的「Network」设置中添加域名
- 勾选「Force HTTPS」和「Let's Encrypt SSL」
Step 4:一键部署
点击「Deploy」按钮,Coolify 会自动:
- 从 Git 拉取最新代码
- 检测到 Node.js 项目,使用 Nixpacks 构建
- 构建 Docker 镜像并推送到本地 registry
- 停止旧容器,启动新容器
- 配置 Traefik 反向代理
- 申请 Let's Encrypt SSL 证书
整个过程通常在 3-5 分钟内完成。
4.3 Docker Compose 应用部署
对于需要自定义 Docker Compose 配置的应用,Coolify 同样支持:
# 在 Coolify 面板中直接编写或上传此文件
services:
app:
build:
context: .
dockerfile: Dockerfile
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
ports:
- "3000"
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.yourdomain.com`)"
- "traik.http.services.app.loadbalancer.server.port=3000"
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
五、280+ 一键服务:从数据库到监控全覆盖
Coolify 内置了 280+ 一键部署服务,覆盖了开发、部署、监控的全生命周期:
5.1 数据库服务
| 服务 | 说明 | 一键部署 |
|---|---|---|
| PostgreSQL | 关系型数据库 | ✅ |
| MySQL | 关系型数据库 | ✅ |
| MongoDB | 文档数据库 | ✅ |
| Redis | 缓存/队列 | ✅ |
| Valkey | Redis 替代品 | ✅ |
| MariaDB | MySQL 分支 | ✅ |
| ClickHouse | OLAP 数据库 | ✅ |
| Dragonfly | Redis 替代品 | ✅ |
5.2 开发工具
| 服务 | 说明 | 一键部署 |
|---|---|---|
| Gitea | 自托管 Git | ✅ |
| Harbor | 容器镜像仓库 | ✅ |
| Portainer | Docker 管理 UI | ✅ |
| Mailpit | 邮件测试工具 | ✅ |
| Meilisearch | 全文搜索引擎 | ✅ |
| -typesense | 搜索引擎 | ✅ |
5.3 监控与可观测性
| 服务 | 说明 | 一键部署 |
|---|---|---|
| Uptime Kuma | 服务监控 | ✅ |
| Grafana | 可视化仪表盘 | ✅ |
| Prometheus | 指标收集 | ✅ |
| Loki | 日志聚合 | ✅ |
| Dozzle | Docker 日志查看 | ✅ |
5.4 自动备份
Coolify 支持自动备份数据库到 S3/MinIO:
// app/Services/Database/BackupService.php
class BackupService
{
public function backup(Database $database): BackupResult
{
$timestamp = now()->format('Y-m-d_H-i-s');
$filename = "{$database->name}_{$timestamp}.sql.gz";
// 根据数据库类型选择备份命令
$command = match ($database->type) {
'postgresql' => "pg_dump {$database->name} | gzip > /tmp/{$filename}",
'mysql' => "mysqldump {$database->name} | gzip > /tmp/{$filename}",
'mongodb' => "mongodump --db={$database->name} --gzip --archive=/tmp/{$filename}",
};
$this->server->executeCommand($command);
// 上传到 S3/MinIO
$this->uploadToS3("/tmp/{$filename}", $filename);
// 清理 7 天前的备份
$this->cleanupOldBackups(keepDays: 7);
return new BackupResult(success: true, filename: $filename);
}
}
六、性能优化:让自托管 PaaS 跑得更快
6.1 控制面板优化
问题:Coolify 使用 Laravel + Livewire,PHP 进程在高并发下可能出现性能瓶颈。
优化方案:
// config/queue.php - 使用 Redis 队列处理异步任务
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'coolify',
'retry_after' => 90,
'block_for' => null,
],
],
// 将部署任务放入队列,避免阻塞 Web 请求
Route::post('/deploy/{app}', function (Application $app) {
ApplicationDeploymentJob::dispatch($app)
->onQueue('deployments')
->timeout(3600); // 1 小时超时
});
前端优化:
// resources/js/app.js - 使用 Alpine.js 的延迟加载
document.addEventListener('alpine:init', () => {
Alpine.data('dashboard', () => ({
stats: null,
loading: true,
async init() {
// 延迟加载统计数据,避免首屏阻塞
this.stats = await fetch('/api/dashboard/stats').then(r => r.json());
this.loading = false;
}
}));
});
6.2 远程执行优化
问题:频繁的 SSH 连接建立会消耗大量时间。
优化方案:
// 使用 SSH ControlMaster 复用连接
$sshConfig = "
Host {$server->ip}
ControlMaster auto
ControlPath /tmp/ssh-{$server->ip}-%r@%h:%p
ControlPersist 300
ServerAliveInterval 60
ServerAliveCountMax 3
";
// 写入临时 SSH 配置文件
file_put_contents("/tmp/ssh_config_{$server->id}", $sshConfig);
$env = "SSH_CONFIG=/tmp/ssh_config_{$server->id}";
6.3 Docker 镜像优化
问题:大型应用的 Docker 镜像构建时间长、体积大。
优化方案:
# 多阶段构建示例
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# 使用非 root 用户运行
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
USER nextjs
EXPOSE 3000
CMD ["node", "dist/server.js"]
构建缓存优化:
// 在 Coolify 中配置 BuildKit 缓存
$buildCommand = "docker buildx build " .
"--cache-from type=local,src=/tmp/.buildx-cache " .
"--cache-to type=local,dest=/tmp/.buildx-cache-new " .
"--load -t {$imageTag} .";
// 构建完成后替换缓存目录
$this->server->executeCommand(
"rm -rf /tmp/.buildx-cache && " .
"mv /tmp/.buildx-cache-new /tmp/.buildx-cache"
);
七、Coolify vs 竞品:谁才是最佳选择?
7.1 与 Vercel/Heroku 的对比
| 维度 | Coolify | Vercel | Heroku |
|---|---|---|---|
| 部署方式 | 自托管 / 云版 | 仅云版 | 仅云版 |
| 定价 | 自托管免费,云版 $5/月起 | 免费套餐有限,Pro $20/月 | 已取消免费套餐 |
| 厂商锁定 | 无(配置在你的服务器) | 强锁定 | 强锁定 |
| 自定义硬件 | ✅ 完全控制 | ❌ 平台决定 | ❌ 平台决定 |
| 数据库 | 内置 280+ 服务 | 需外部服务 | 需外部服务 |
| SSL | 自动签发 | 自动签发 | 自动签发 |
| 监控 | 内置 Grafana/Prometheus | 基础监控 | 基础监控 |
7.2 与 CapRover 的对比
| 维度 | Coolify | CapRover |
|---|---|---|
| UI 美观度 | ⭐⭐⭐⭐⭐ 现代化设计 | ⭐⭐⭐ 功能性 UI |
| 一键服务 | 280+ | ~50 |
| Git 集成 | GitHub/GitLab/Bitbucket | GitHub/GitLab |
| 多服务器 | ✅ 集群管理 | ⚠️ 基础支持 |
| SSL 自动化 | Let's Encrypt | Let's Encrypt |
| 社区活跃度 | 44K Star | 13K Star |
7.3 与 Dokku 的对比
| 维度 | Coolify | Dokku |
|---|---|---|
| 易用性 | ⭐⭐⭐⭐⭐ Web UI | ⭐⭐⭐ CLI 为主 |
| 功能丰富度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 资源占用 | 较高(Laravel 全家桶) | 极低(单二进制) |
| 适合场景 | 团队协作、多项目 | 个人开发者、轻量部署 |
八、生产环境部署指南
8.1 推荐服务器配置
| 用途 | 最低配置 | 推荐配置 |
|---|---|---|
| Coolify 控制面板 | 1GB RAM, 1 vCPU | 2GB RAM, 2 vCPU |
| 应用服务器(单应用) | 1GB RAM, 1 vCPU | 2GB RAM, 2 vCPU |
| 应用服务器(多应用) | 4GB RAM, 4 vCPU | 8GB RAM, 8 vCPU |
| 数据库服务器 | 2GB RAM, 2 vCPU | 8GB RAM, 4 vCPU |
8.2 安全加固清单
# 1. 配置防火墙(仅开放必要端口)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP(重定向到 HTTPS)
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 8000/tcp # Coolify 面板(可选,建议用 VPN)
sudo ufw enable
# 2. 禁用 root SSH 登录
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# 3. 配置自动安全更新
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# 4. 启用 Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# 5. 定期备份数据库
crontab -e
# 每天凌晨 3 点备份
0 3 * * * /opt/coolify/scripts/backup.sh
九、总结与展望
9.1 Coolify 解决了什么问题?
- 消除了 PaaS 的厂商锁定:你的数据、配置、部署流程全部在你自己的服务器上
- 降低了自托管的门槛:一条
curl命令 + Web UI,不需要成为运维专家 - 提供了完整的 DevOps 工具链:从 Git 部署到 SSL 签发,从数据库管理到监控告警
- 保持了合理的复杂度:没有过度工程化,用 Laravel + Docker 的成熟组合解决了 90% 的需求
9.2 还有什么不足?
- 资源消耗较高:Laravel 全家桶在 1GB VPS 上运行较吃力
- 多服务器集群功能有限:不像 Kubernetes 那样成熟
- PHP 的性能天花板:在极高并发场景下可能成为瓶颈
- 缺乏企业级特性:没有 RBAC、审计日志、SSO 等企业功能
9.3 未来展望
Coolify 的路线图显示了几个关键方向:
- Coolify v5:计划用 Livewire 3 重构前端,提升响应速度
- 内置 AI 助手:帮助开发者诊断部署问题
- 多租户支持:面向团队和企业的权限管理
- 边缘部署:支持 Cloudflare Workers 等边缘计算平台
对于中小团队和个人开发者来说,Coolify 已经是一个非常成熟的自托管 PaaS 方案。它证明了一个道理:
最好的云,是你自己控制的云。
本文基于 Coolify v4.x 最新版本撰写,技术细节可能随版本更新而变化。
项目地址:https://github.com/coollabsio/coolify
官方文档:https://coolify.io/docs