Skip to main content

Guide

Coolify vs CapRover vs Dokku (2026)

Compare Coolify, CapRover, and Dokku for self-hosted PaaS. Git push deployments, Docker management, SSL, databases, and which self-hosted platform to use in.

·PkgPulse Team·
0

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 push workflow

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

FeatureCoolifyCapRoverDokku
InterfaceWeb UI (modern)Web UI (functional)CLI only
Deploy methodGit, Docker, ComposeGit, Docker, Compose, imageGit push, Dockerfile
Buildpacks✅ (Nixpacks)❌ (Dockerfile only)✅ (Heroku buildpacks)
Docker ComposeVia 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)BasicVia plugins
Rollback
Persistent storage
Custom domains
Resource limits✅ (Docker options)
LanguagePHP/LaravelNode.jsGo/Bash
GitHub stars25K+13K+29K+

VPS Selection and Initial Setup

The server running your self-hosted PaaS is as important as the PaaS software itself. All three platforms run comfortably on a 2-CPU, 4 GB RAM virtual machine from Hetzner, Vultr, or DigitalOcean — prices range from $12–20/month, a fraction of equivalent managed cloud services. Ubuntu 22.04 LTS is the most tested host OS for all three platforms. Enable automatic security updates (unattended-upgrades), configure a firewall (UFW or nftables) that allows only ports 80, 443, and your SSH port, and disable root SSH login in favor of key-based authentication before installing any PaaS software. Allocate a separate block storage volume for Docker volumes and database data rather than storing everything on the root disk — this simplifies server migration and prevents application data from filling the OS partition.

Production Infrastructure Considerations

Self-hosted PaaS platforms add an operational layer that managed cloud services eliminate — you own the server, the disk, the network configuration, and the upgrade cycle. For production workloads, all three platforms need a reverse proxy configured correctly: Coolify and CapRover embed their own proxy (Traefik and nginx respectively), while Dokku defaults to nginx via the dokku-nginx core plugin. Each proxy must be configured for WebSocket upgrades if your application uses them, and for proper X-Forwarded-For header passing if your app reads client IPs for rate limiting or geo-blocking. SSL certificate renewal via Let's Encrypt requires valid DNS and port 80 accessibility — plan for certificate renewal windows and test your renewal scripts before they hit expiry in production.

Data Backup and Disaster Recovery

None of the three platforms provide built-in backup automation for application databases — this is a gap teams often discover after a data loss event rather than before. Dokku's PostgreSQL plugin includes dokku postgres:export for manual dumps, but scheduling automated S3-compatible backups requires a separate cron job or a third-party backup service. Coolify's UI surfaces database connection details but delegates backup strategy to the operator. For production Dokku deployments, use a pre-deploy hook in app.json to run database migrations, but also configure a separate cron job on the host that dumps the database to an encrypted S3 bucket nightly. CapRover's one-click app marketplace includes database tools that simplify setup, but backup automation still requires manual configuration.

Scaling Strategies and Performance

Coolify's multi-server support and CapRover's Docker Swarm integration are the key differentiators for teams that anticipate horizontal scaling. Docker Swarm with CapRover allows adding worker nodes to distribute container workloads — a practical middle ground between single-server Dokku and full Kubernetes. Dokku scales vertically by default, using dokku docker-options:add to set memory and CPU limits per process type, but it runs all processes on a single server. For Next.js applications with heavy SSR, Dokku's web=2 scaling runs two web processes behind nginx with load balancing, while CapRover's Docker Swarm spreads those containers across nodes. Teams expecting more than ten concurrent containers should evaluate CapRover or Coolify over Dokku.

Security Hardening

Self-hosted PaaS requires explicit security hardening that managed platforms handle automatically. All three platforms expose an admin interface that should be protected with a non-default port, IP allowlisting, or VPN access — the default Coolify dashboard on port 8000 and CapRover dashboard on port 3000 are common targets for credential-stuffing attacks. Dokku's SSH-based access is inherently more secure since it uses public-key authentication rather than a web UI, but the dokku user's authorized_keys file must be audited regularly. Enable automatic security updates on the host OS regardless of which platform you use — a patched application running on an unpatched kernel negates the security benefit of keeping your application dependencies current.

TypeScript and Node.js Deployment Patterns

All three platforms deploy Node.js applications effectively, but the developer experience differs for TypeScript projects. Coolify's Nixpacks auto-detection builds TypeScript projects by detecting tsc in the build scripts of package.json — ensure your build script compiles TypeScript and your start script runs the compiled output from dist/. Dokku's Node.js buildpack runs npm build if present, making it equally straightforward. CapRover requires a Dockerfile or captain-definition file for TypeScript — the two-stage Docker build pattern (compile in builder stage, run compiled output in production stage) is the standard approach and significantly reduces the production image size by excluding TypeScript compiler toolchain and development dependencies.

Monitoring and Alerting for Self-Hosted Applications

Self-hosted PaaS platforms shift infrastructure observability responsibility to your team. Coolify includes built-in resource monitoring showing CPU, memory, and disk usage per service, with email alerting for downtime and resource threshold violations — this covers basic operational visibility without additional tooling. For production workloads, supplement Coolify's built-in monitoring with a dedicated metrics stack: deploy Prometheus and Grafana as services within Coolify itself, configure your application containers to expose /metrics endpoints, and set up Grafana dashboards for request rate, error rate, and latency. CapRover's monitoring is lighter — it shows container resource usage in the dashboard but lacks alerting. Integrate CapRover deployments with an external uptime monitor (BetterStack, UptimeRobot) to get notified when services go down. Dokku's CLI-first philosophy means monitoring is entirely external — use dokku logs app-name for real-time log tailing, and pair with Loki for centralized log aggregation across multiple Dokku apps on the same host.

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 push deployment 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 →

See also: AVA vs Jest and Vercel vs Netlify vs Cloudflare Pages, Gitea vs Forgejo vs Gogs (2026).

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.