Coolify: Self-Hosted Heroku Alternative That Actually Works

After months of paying Heroku's inflated prices, I finally moved all my side projects to Coolify on a $5/month VPS. The transition wasn't smooth sailing, but I now have a fully functional PaaS that costs pennies to run.

Problem

I had Coolify running on a DigitalOcean droplet, connected my custom domain, and the SSL cert generated fine. Two months later, visitors started seeing security warnings. The certificate had expired and wasn't renewing.

Error: SSL certificate expired. Failed to renew: ACME challenge timed out

What I Tried

Attempt 1: Clicked "Force Renew" in the Coolify dashboard - got the same timeout error.
Attempt 2: Checked if port 80 was blocked - it wasn't, Nginx was listening fine.
Attempt 3: Tried switching from Let's Encrypt to ZeroSSL - still failed.

Actual Fix

The issue was that my DNS provider (Cloudflare) was proxying traffic through their own SSL, which broke the ACME challenge. I needed to either use Cloudflare's Origin certificates or set DNS-only mode during the initial cert generation.

# In Cloudflare DNS settings:
# 1. Click the orange cloud icon to turn it grey (DNS-only)
# 2. Wait 5 minutes for DNS propagation
# 3. Go to Coolify dashboard → Application → Domains
# 4. Click "Force Renew SSL Certificate"

# Or use Cloudflare Origin Certificate:
# 1. Get cert from Cloudflare SSL/TLS → Origin Server → Create
# 2. In Coolify, add custom certificate instead of Let's Encrypt
# 3. Keep Cloudflare proxy enabled (orange cloud)

Problem

Running Coolify on a 1GB RAM VPS with 3 small Node.js apps. The Coolify container kept getting OOM killed every few days, taking down all my apps with it.

docker ps showed Coolify constantly restarting. docker logs coolify revealed JavaScript heap out of memory errors.

What I Tried

Attempt 1: Added swap space - helped for a week, then crashes resumed.
Attempt 2: Tried limiting individual app memory - didn't reduce Coolify's usage.
Attempt 3: Removed unused Docker images and volumes - freed disk space but not RAM.

Actual Fix

Coolify itself runs on a resource-heavy stack (Nuxt.js + SQLite). The solution is to: 1) Add a properly sized swap file, and 2) Limit the number of concurrent build operations. Also, monitoring showed that builds were the RAM spike trigger - not the runtime.

# Create 4GB swap file (do this, not 1GB like I first tried)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# Reduce swapiness to prefer RAM over swap
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf

# In Coolify: Settings → General → Concurrent builds
# Set to 1 instead of default 3

# Restart Coolify
cd /opt/coolify
docker-compose down && docker-compose up -d

Problem

Connected my GitHub repo to Coolify, set up the webhook URL in GitHub repo settings. Pushing to main branch did nothing - no deployment, no logs, no error. GitHub webhook delivery showed "200 OK" but Coolify wasn't doing anything.

What I Tried

Attempt 1: Re-created the webhook in GitHub multiple times - same result.
Attempt 2: Checked Coolify logs - showed webhook received but "branch not configured".
Attempt 3: Set branch to "*" thinking wildcard would work - it didn't.

Actual Fix

In Coolify v3+, you need to explicitly configure the webhook branch in the application settings. The UI has a "Webhook" tab where you must set the exact branch name (case-sensitive). Also, the GitHub App integration is more reliable than manual webhook URLs.

# Recommended: Use GitHub App Integration instead of webhooks
# 1. In Coolify: Settings → Source → GitHub → Install GitHub App
# 2. Select your repos when prompted
# 3. In application: Source → Connect via GitHub App
# 4. Choose branch: "main" (exact match, not "Main")

# If using manual webhooks, check the preview URL format:
# https://your-coolify-domain.com/api/v1/webhooks/preview/{source-id}
# The source-id must match your application ID

What I Learned

Production Setup

Here's the complete setup that's been working for me on a $5/month DigitalOcean droplet (1GB RAM, 25GB SSD, 1TB transfer). Running 3 Node.js apps and a PostgreSQL database.

# 1. Initial server setup (Ubuntu 22.04)
curl -fsSL https://get.docker.com | sh
usermod -aG docker $USER

# 2. Create swap before installing Coolify
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
sudo sysctl vm.swappiness=10

# 3. Install Coolify via their script
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

# 4. Configure DNS (example for Cloudflare)
# - A record: coolify.yourdomain.com → your VPS IP
# - A record: *.apps.yourdomain.com → your VPS IP
# - Set proxy to DNS-only (grey cloud) for initial SSL

# 5. First-time setup
# - Access https://coolify.yourdomain.com
# - Create admin account
# - Configure domains and email

# 6. Configure resources in Coolify
# - Settings → General → Concurrent builds: 1
# - Each app: Add resource limits (512MB RAM per app)

# 7. Connect GitHub via GitHub App (most reliable)
# Settings → Source → GitHub → Install GitHub App

# 8. Deploy your first app
# - New Resource → Application → From GitHub
# - Select repo and branch
# - Set build command: npm run build
# - Set start command: npm start
# - Set port: 3000 (or your app's port)
# - Click Deploy

Monitoring & Debugging

Keep an eye on these metrics to avoid the issues I ran into:

Essential Commands

# Check Coolify logs
docker logs -f coolify --tail 100

# Check all container resource usage
docker stats

# Check if swap is being used (swpd > 0 means RAM pressure)
free -h

# Check disk space (Docker images can accumulate)
df -h

# Restart Coolify if it becomes unresponsive
cd /opt/coolify && docker-compose restart

Red Flags to Watch For

Related Resources