Coolify vs CapRover vs Dokku: Self-Hosted PaaS Platforms (2026)
TL;DR
Coolify is the modern self-hosted PaaS — beautiful UI, one-click deployments, Docker and Docker Compose support, Git integration, automatic SSL, database management, built-in monitoring. CapRover is the lightweight self-hosted PaaS — one-click apps, Dockerfile and Docker Compose, cluster mode, web dashboard, Let's Encrypt SSL, simple and battle-tested. Dokku is the smallest PaaS — Heroku-like git push deployments, buildpacks, plugin ecosystem, CLI-first, the original self-hosted PaaS. In 2026: Coolify for the best UI and modern features, CapRover for simple Docker-based deployments, Dokku for Heroku-style CLI workflow.
Key Takeaways
- Coolify: 25K+ GitHub stars — modern UI, Docker/Compose, Git webhooks, databases
- CapRover: 13K+ GitHub stars — one-click apps, cluster mode, simple Docker deploys
- Dokku: 29K+ GitHub stars — Heroku-like, buildpacks, plugin ecosystem, CLI-first
- Coolify has the most polished web interface and widest deployment options
- CapRover provides the easiest one-click app marketplace
- Dokku offers the closest Heroku experience with
git pushworkflow
Coolify
Coolify — modern self-hosted PaaS:
Installation
# One-line install on any VPS (Ubuntu/Debian):
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
# Access the dashboard:
# https://your-server-ip:8000
Docker Compose deployment
# docker-compose.yml — deployed via Coolify UI or API
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:secret@db:5432/myapp
NODE_ENV: production
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:
API deployments
// Deploy via Coolify API:
const COOLIFY_URL = "https://coolify.example.com"
const COOLIFY_TOKEN = process.env.COOLIFY_API_TOKEN!
// List applications:
const apps = await fetch(`${COOLIFY_URL}/api/v1/applications`, {
headers: { Authorization: `Bearer ${COOLIFY_TOKEN}` },
}).then((r) => r.json())
// Trigger deployment:
const deploy = await fetch(
`${COOLIFY_URL}/api/v1/applications/${appId}/deploy`,
{
method: "POST",
headers: {
Authorization: `Bearer ${COOLIFY_TOKEN}`,
"Content-Type": "application/json",
},
}
).then((r) => r.json())
// Create a new database:
const database = await fetch(`${COOLIFY_URL}/api/v1/databases`, {
method: "POST",
headers: {
Authorization: `Bearer ${COOLIFY_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "postgresql",
name: "myapp-db",
version: "16",
server_id: serverId,
}),
}).then((r) => r.json())
// Get deployment logs:
const logs = await fetch(
`${COOLIFY_URL}/api/v1/deployments/${deployId}/logs`,
{
headers: { Authorization: `Bearer ${COOLIFY_TOKEN}` },
}
).then((r) => r.json())
GitHub Actions integration
# .github/workflows/deploy.yml
name: Deploy to Coolify
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger Coolify deployment
run: |
curl -X POST \
"${{ secrets.COOLIFY_URL }}/api/v1/applications/${{ secrets.COOLIFY_APP_ID }}/deploy" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \
-H "Content-Type: application/json"
Dockerfile (typical Node.js app)
# Dockerfile — Coolify auto-detects and builds
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
CapRover
CapRover — lightweight self-hosted PaaS:
Installation
# Install CapRover on a VPS:
docker run -p 80:80 -p 443:443 -p 3000:3000 \
-e ACCEPTED_TERMS=true \
-v /var/run/docker.sock:/var/run/docker.sock \
-v captain-data:/captain \
caprover/caprover
# Install CLI:
npm install -g caprover
# Setup (interactive):
caprover serversetup
# Enter IP, password, root domain, email for SSL
CLI deployments
# Login to CapRover:
caprover login
# Enter: URL, password
# Create an app:
caprover api --path "/user/apps/appDefinitions/register" \
--method POST \
--data '{"appName": "my-api", "hasPersistentData": false}'
# Deploy from current directory:
caprover deploy -a my-api
# Deploy with tarball:
tar -czf deploy.tar.gz --exclude node_modules --exclude .git .
caprover deploy -a my-api -t ./deploy.tar.gz
Captain Definition file
// captain-definition — CapRover deployment config
{
"schemaVersion": 2,
"dockerfileLines": [
"FROM node:20-alpine AS builder",
"WORKDIR /app",
"COPY package*.json ./",
"RUN npm ci",
"COPY . .",
"RUN npm run build",
"",
"FROM node:20-alpine",
"WORKDIR /app",
"COPY --from=builder /app/dist ./dist",
"COPY --from=builder /app/node_modules ./node_modules",
"COPY --from=builder /app/package.json ./",
"EXPOSE 3000",
"CMD [\"node\", \"dist/index.js\"]"
]
}
// captain-definition — using pre-built image
{
"schemaVersion": 2,
"imageName": "my-registry.com/my-api:latest"
}
// captain-definition — using Docker Compose
{
"schemaVersion": 2,
"dockerComposeFileContent": "version: '3.8'\nservices:\n app:\n build: .\n ports:\n - '80:3000'\n environment:\n NODE_ENV: production"
}
API automation
// CapRover API — automate deployments:
const CAPROVER_URL = "https://captain.example.com"
const CAPROVER_PASSWORD = process.env.CAPROVER_PASSWORD!
// Get auth token:
const { token } = await fetch(`${CAPROVER_URL}/api/v2/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: CAPROVER_PASSWORD }),
}).then((r) => r.json())
const headers = {
"Content-Type": "application/json",
"x-captain-auth": token,
"x-namespace": "captain",
}
// List apps:
const { data } = await fetch(
`${CAPROVER_URL}/api/v2/user/apps/appDefinitions`,
{ headers }
).then((r) => r.json())
console.log(data.appDefinitions.map((a: any) => a.appName))
// Create app:
await fetch(`${CAPROVER_URL}/api/v2/user/apps/appDefinitions/register`, {
method: "POST",
headers,
body: JSON.stringify({
appName: "my-worker",
hasPersistentData: false,
}),
})
// Set environment variables:
await fetch(`${CAPROVER_URL}/api/v2/user/apps/appDefinitions/update`, {
method: "POST",
headers,
body: JSON.stringify({
appName: "my-api",
envVars: [
{ key: "DATABASE_URL", value: "postgresql://..." },
{ key: "REDIS_URL", value: "redis://..." },
{ key: "NODE_ENV", value: "production" },
],
}),
})
// Enable SSL:
await fetch(`${CAPROVER_URL}/api/v2/user/apps/appDefinitions/enablessl`, {
method: "POST",
headers,
body: JSON.stringify({ appName: "my-api" }),
})
// Deploy from Docker image:
await fetch(
`${CAPROVER_URL}/api/v2/user/apps/appDefinitions/forcebuild`,
{
method: "POST",
headers,
body: JSON.stringify({
appName: "my-api",
captainDefinitionContent: JSON.stringify({
schemaVersion: 2,
imageName: "my-registry.com/my-api:v1.2.3",
}),
}),
}
)
One-click apps
# CapRover one-click apps — deploy from marketplace:
# PostgreSQL, Redis, MongoDB, MySQL, RabbitMQ, MinIO,
# WordPress, Ghost, Plausible, Umami, n8n, Gitea, etc.
# Via CLI — list one-click apps:
caprover api --path "/user/oneclick/template/list"
# Deploy one-click app via API:
curl -X POST "${CAPROVER_URL}/api/v2/user/oneclick/template/deploy" \
-H "x-captain-auth: ${TOKEN}" \
-H "x-namespace: captain" \
-H "Content-Type: application/json" \
-d '{
"templateId": "postgres",
"variables": {
"$$cap_postgres_password": "supersecret",
"$$cap_postgres_db": "myapp"
}
}'
Dokku
Dokku — the smallest PaaS:
Installation
# Install on Ubuntu 22.04+:
wget -NP . https://dokku.com/install/v0.34.x/bootstrap.sh
sudo DOKKU_TAG=v0.34.8 bash bootstrap.sh
# Set domain:
dokku domains:set-global example.com
# Add SSH key:
cat ~/.ssh/id_rsa.pub | dokku ssh-keys:add admin
Git push deployments
# Create app:
dokku apps:create my-api
# Set environment variables:
dokku config:set my-api \
DATABASE_URL="postgresql://postgres:secret@db:5432/myapp" \
NODE_ENV=production \
PORT=3000
# Add git remote and deploy:
git remote add dokku dokku@example.com:my-api
git push dokku main
# Dokku auto-detects language via buildpacks:
# - package.json → Node.js buildpack
# - requirements.txt → Python buildpack
# - Dockerfile → Docker build
Procfile and app configuration
# Procfile — define process types:
web: node dist/index.js
worker: node dist/worker.js
// app.json — Dokku app configuration:
{
"name": "my-api",
"description": "Package comparison API",
"scripts": {
"dokku": {
"predeploy": "npm run db:migrate",
"postdeploy": "npm run db:seed"
}
},
"formation": {
"web": { "quantity": 2, "size": "standard-1x" },
"worker": { "quantity": 1 }
},
"healthchecks": {
"web": [
{
"type": "startup",
"name": "web check",
"path": "/health",
"attempts": 10
}
]
}
}
Database plugins
# Install PostgreSQL plugin:
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
# Create database:
dokku postgres:create myapp-db
# Link to app (auto-sets DATABASE_URL):
dokku postgres:link myapp-db my-api
# Backup:
dokku postgres:export myapp-db > backup.sql
# Restore:
dokku postgres:import myapp-db < backup.sql
# Install Redis plugin:
sudo dokku plugin:install https://github.com/dokku/dokku-redis.git redis
# Create and link Redis:
dokku redis:create myapp-cache
dokku redis:link myapp-cache my-api
# Install Let's Encrypt:
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
dokku letsencrypt:enable my-api
dokku letsencrypt:cron-job --add
Scaling and management
# Scale processes:
dokku ps:scale my-api web=3 worker=2
# View logs:
dokku logs my-api -t # tail logs
dokku logs my-api -n 100 # last 100 lines
# App management:
dokku apps:list
dokku ps:report my-api
dokku domains:report my-api
# Rollback:
dokku ps:rollback my-api # rollback to previous deploy
# Docker options:
dokku docker-options:add my-api deploy "--memory 512m"
dokku docker-options:add my-api deploy "--cpus 1.0"
# Proxy/port mapping:
dokku ports:add my-api http:80:3000
dokku ports:add my-api https:443:3000
# Maintenance mode:
dokku maintenance:enable my-api
dokku maintenance:disable my-api
# Custom domains:
dokku domains:add my-api api.example.com
dokku domains:add my-api www.example.com
Deployment automation
// Automate Dokku via SSH:
import { NodeSSH } from "node-ssh"
const ssh = new NodeSSH()
await ssh.connect({
host: "dokku.example.com",
username: "dokku",
privateKeyPath: "~/.ssh/id_rsa",
})
// Run Dokku commands remotely:
async function dokku(command: string): Promise<string> {
const result = await ssh.execCommand(command)
if (result.stderr) console.error(result.stderr)
return result.stdout
}
// Create app:
await dokku("apps:create staging-api")
// Set config:
await dokku('config:set staging-api NODE_ENV=staging DATABASE_URL="postgresql://..."')
// Check status:
const report = await dokku("ps:report staging-api")
console.log(report)
// Scale:
await dokku("ps:scale staging-api web=2")
// Cleanup:
await dokku("cleanup") // Remove dangling images
ssh.dispose()
# .github/workflows/deploy-dokku.yml
name: Deploy to Dokku
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Deploy to Dokku
uses: dokku/github-action@master
with:
git_remote_url: "ssh://dokku@example.com:22/my-api"
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
branch: main
Feature Comparison
| Feature | Coolify | CapRover | Dokku |
|---|---|---|---|
| Interface | Web UI (modern) | Web UI (functional) | CLI only |
| Deploy method | Git, Docker, Compose | Git, Docker, Compose, image | Git push, Dockerfile |
| Buildpacks | ✅ (Nixpacks) | ❌ (Dockerfile only) | ✅ (Heroku buildpacks) |
| Docker Compose | ✅ | ✅ | Via plugin |
| One-click apps | ✅ | ✅ (marketplace) | Via plugins |
| SSL (Let's Encrypt) | ✅ (automatic) | ✅ (automatic) | ✅ (plugin) |
| Database management | ✅ (built-in) | ✅ (one-click) | ✅ (plugins) |
| Multi-server | ✅ | ✅ (Docker Swarm) | ❌ (single server) |
| API | ✅ (REST) | ✅ (REST) | SSH commands |
| Monitoring | ✅ (built-in) | Basic | Via plugins |
| Rollback | ✅ | ✅ | ✅ |
| Persistent storage | ✅ | ✅ | ✅ |
| Custom domains | ✅ | ✅ | ✅ |
| Resource limits | ✅ | ✅ | ✅ (Docker options) |
| Language | PHP/Laravel | Node.js | Go/Bash |
| GitHub stars | 25K+ | 13K+ | 29K+ |
When to Use Each
Use Coolify if:
- Want the most modern, polished web UI for managing deployments
- Need built-in monitoring, database management, and multi-server support
- Prefer Docker Compose and Nixpacks auto-detection
- Building a team platform where non-CLI users need deployment access
Use CapRover if:
- Want a simple, lightweight PaaS with a one-click app marketplace
- Need Docker Swarm cluster support for multi-node setups
- Prefer a functional web dashboard with straightforward Docker deployments
- Want the easiest setup for common services (databases, caches, CMS)
Use Dokku if:
- Want a Heroku-like
git pushdeployment experience - Prefer CLI-first workflow with SSH-based management
- Need Heroku buildpack compatibility for zero-config deploys
- Want the most mature, battle-tested self-hosted PaaS
Methodology
GitHub stars as of March 2026. Feature comparison based on Coolify v4.x, CapRover v1.x, and Dokku v0.34.x.
Compare developer tools and deployment platforms on PkgPulse →