I migrated my projects from Vercel to my own VPS
I've been relying on managed PaaS platforms for years. My services were scattered around Vercel for Next.js apps, Neon for databases, Upstash for Redis and Digital Ocean for Nest.js APIs e.g.
This setup works fine, but managing it is quite a hassle. It felt chaotic, each service was connected to a service on another platform. Not to mention the cost of these services.
Migrating it all to my own VPS was on my radar for a while so I finally took the plunge.
Dokploy vs Coolify
When it comes to self-hostable (open-source) PaaS platforms, there are two popular options: Dokploy and Coolify. I tried them both by spinning them up on two separate VPS's.
My first preference leaned towards Dokploy, mainly because its UX and UI felt smoother and more polished. But compared to Coolify, its community is smaller and the project feels a bit less mature. For me, that outweighs a good-looking UI. An active community and strong sponsorship give more trust. If something breaks, I’d rather rely on a wide community for solutions. That’s why I ended up going with Coolify over Dokploy.
Setting up the VPS with Coolify
Getting started with Coolify on a VPS is straightforward. First purchase a VPS on Hetzner, SSH into it and run the install script. This will install Docker and Coolify on the VPS.
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash
That's it. Easy.
Security
One thing I appreciated about Coolify is that it comes with some security baked in. It uses Traefik as a reverse proxy to automatically handle SSL certificates through Let's Encrypt and sets up secure Docker networks to isolate your applications.
While these built-in features cover some of the application layer security, I still needed to set up additional infrastructure-level security that was previously handled by managed platforms.
The first thing I did was moving my domain's nameservers to Cloudflare and pointing its DNS records to the IPv4 address of my VPS. This proxies all traffic through Cloudflare's network which gives me Cloudflare's security and caching benefits. As a bonus, this also hides the IPv4 address of my VPS from the public internet.
The next step was setting up a firewall in Hetzner to block all inbound traffic and only open up the 443 (HTTPS) port for Cloudflare's IPs only. This way no direct access is allowed unless it's been proxied through Cloudflare.
To be able to do local development I opened port 22 (for SSH) and 5432 (for PostgreSQL) for my local IP only.
CI/CD
The first deployment (a Next.js app) went far from smooth. I connected my Git repository and started the build process on Coolify. I immediately saw that the CPU usage was going through the roof which temporarily froze the entire VPS.
Clearly the 2 vCPUs were not enough for this task. One option was scaling up vertically, but that would cost more money and was quite unnecessary as this extra power would only be needed during deployments.
Instead I decided to take the build process away from the VPS and move it to Github Actions. I added a Dockerfile to the project, created a simple Action workflow that builds the container, and then let it push it to the GitHub Container Registry.
The final step to complete the CD pipeline was to create a webhook in the GitHub repository that would trigger Coolify to do a redeploy after a new container was available.
Visually this looks like this:
Something I learned
When developing this CD pipeline I wanted to test the Dockerfile by building a container on my Mac and pushing it directly to GHCR (skipping the GitHub Actions workflow).
When trying to run the container on Coolify I quickly learned that this would not work out of the box, because containers built on Mac (ARM architecture) are not directly compatible with Linux-based systems (which typically use x86_64).
More deployments
After getting the first service up and running, I moved on to other projects. Some of them needed a bit of tweaking to make them run smoothly in a Docker container but overall it was a smooth process. I even created a fresh Kotlin Spring Boot API to teach myself some Kotlin (and JVM in general).