What Is a Self-Hosted PaaS? Three Roles Behind One Dashboard
A self-hosted PaaS is not one program. It is a control plane, a reverse proxy, and a container runtime stacked on your own server — and knowing which layer failed is what turns a 2am outage into a ten-minute fix.
What you'll learn: the three roles every self-hosted PaaS fills (control plane, reverse proxy, container runtime), why your site keeps serving traffic while the dashboard is down, which layer each status code (502 / 503 / 504) belongs to, and what you are still responsible for after the platform is installed.
Who it's for: anyone comfortable on the command line who has heard "self-host instead of Vercel" but cannot say which jobs, exactly, it replaces.
What you can do after reading: draw a three-layer map of your own deployment, so the next error starts with "which layer broke" instead of "what's wrong with Coolify".
Install Coolify on a VPS, point a domain at it, and you have "your own Vercel". That is the pitch, and it is roughly true.
Then three months later the site returns 502 at eleven at night. You open a terminal, and you have no idea what you are looking for — because nobody told you that you are now running three systems, not one.
The three roles behind one dashboard
A self-hosted PaaS is not a piece of software you install. It is three roles stacked on the same machine:
| Role | What it does | Default Coolify setup |
|---|---|---|
| Control plane | Stores your configuration, queues deployments, sends commands to servers | The coolify container, backed by coolify-db (PostgreSQL), coolify-redis, and coolify-realtime |
| Reverse proxy | Owns ports 80 and 443, terminates TLS, routes hostnames to containers | The coolify-proxy container — Traefik by default, Caddy as an option |
| Container runtime | Builds or pulls images, runs containers, keeps data in volumes | Docker on every server you connect |
Every self-hosted PaaS fills those three jobs with something. Coolify, Dokploy, Dokku, CapRover — different products, same three seats to fill. Kubernetes fills them too, with different words and far more moving parts.
The useful part is not the vocabulary. It is one question: which of the three is broken right now? Answer that and the fix is usually ten minutes.
Role 1: the control plane never touches your traffic
This is the part that surprises people, and Coolify's documentation states it plainly: the control path and the application traffic path are separate, and requests to your app do not pass through the Coolify instance.
Trace a deployment. You press Deploy, or a Git webhook fires, or a scheduled job runs. Coolify reads the saved configuration from its database, drops the requested action into a queue, then opens an SSH connection to the target server and runs the Docker commands there. There is no permanent agent on the server — SSH is the whole mechanism, with an optional coolify-sentinel container that only adds monitoring data.
So the control plane's job is to decide and instruct, never to sit in the request path. Two consequences follow, and both matter more than they look.
Your site can be perfectly healthy while the control plane is down. Containers keep running. Volumes keep their data. The reverse proxy keeps its existing routing configuration and keeps answering requests. What you lose is the ability to change anything: no deployments, no dashboard, no scheduled jobs (the kind covered in Automating Tasks with a Schedule), no webhook-triggered automation, no status checks or notifications. Nothing breaks except your ability to act.
"Back up Coolify" is not "back up your applications". A Coolify instance backup contains the Coolify database — projects, environments, server and resource settings, credentials, platform state. It does not contain your application files, database data, or volumes; those live on the connected servers. If you restore an instance backup onto a fresh machine, you get your platform configuration back, not your users' data. Two different backups, two different recovery plans.
Role 2: the reverse proxy owns ports 80 and 443
Everything from the outside world arrives here first. In Coolify it is a container named coolify-proxy, created on each server where the proxy is enabled. It does three jobs:
- Accept the connection on port 80 or 443.
- Match the request by hostname and path.
- Forward it to the correct container port over a Docker network — and handle the TLS certificate on the way in.
Traefik is what Coolify selects by default when you add a server. Caddy is selectable (currently documented as experimental), and "Custom (None)" means you bring your own proxy and manage the entire public traffic path yourself.
This layer fails in its own distinctive ways, and the status codes are usually enough to localize the problem:
| What you see | What it usually means |
|---|---|
502 Bad Gateway | The proxy reached something that was not a working HTTP server: wrong target port, container not listening, container unhealthy, or a host-mapping mistake |
503 No Available Server | The proxy has no upstream it considers usable: health check failing, domain or port mismatch, proxy issue |
504 Gateway Timeout | The request went somewhere and never came back: network isolation, or a proxy timeout shorter than your app's response time |
| Certificate warning, HTTPS refused | Ports 80/443 closed, DNS not pointing at this server, or the certificate resolver misconfigured |
Coolify's own troubleshooting documentation is organized along exactly these lines — bad gateway, no available server, gateway timeout, plus a separate set of pages for Let's Encrypt and certificates. That structure is worth internalizing: it is the platform maintainers telling you which layer they consider each symptom to belong to.
One detail in this layer causes a disproportionate number of 502s, and it is not the proxy's fault: the port the proxy targets must be the port your app actually listens on inside its container. A Node app listening on 3000 behind a proxy that targets 8080 is a 502 every time, and no amount of restarting the container will change it. The container's listening address matters too — a process bound to 127.0.0.1 inside the container is not reachable from the proxy in the way a process bound to 0.0.0.0 is.
Role 3: the runtime is ordinary Docker
The third layer is the least magical and the hardest to blame. Nothing Coolify-specific runs inside your application container. It is a standard Docker container, on a standard Docker network, with standard volumes — which is exactly why the control plane can disappear without your site going down, and why you can always walk onto the server and operate the containers by hand.
Two facts about this layer explain most of the pain people attribute to "the PaaS":
Containers are replaced, not updated. Each deployment builds or pulls an image and swaps the container. Coolify prefers a rolling update: it starts the new container while the current one keeps serving, waits for the health check to pass, then removes the old one — and if the health check fails, it removes the new container and leaves the old one running. When a rolling update is not possible (host port mappings, a fixed container name, preview deployments, a custom IP), Coolify stops the current container first and only then starts the new one. That gap is visible to your users, and it is a configuration choice, not a bug.
Anything not in a volume disappears with the container. Persistent data has to live outside the container's writable filesystem — a named volume, a bind mount, or external storage. Data written inside the container and not mounted anywhere is lost the moment the container is replaced. This is the "my uploads vanished after a deploy" bug, and it has nothing to do with which PaaS you chose.
The build side is where self-hosting earns its keep: Git sources can be built with Nixpacks, Railpack (beta), a static build, a Dockerfile, or Docker Compose, or you can deploy a prebuilt image from a registry. Builds run on the deployment server by default, or on a dedicated build server if you have separated that load — in which case the image travels through a registry.
The table you want at eleven at night
Three layers, three families of symptom. This is the whole reason to learn the model:
| Symptom | Layer to suspect | First thing to check |
|---|---|---|
| Site loads fine, but you cannot deploy or open the dashboard | Control plane | Status of the Coolify containers, instance disk space, queue workers |
502, and the app container looks healthy | Proxy ↔ runtime boundary | Target port equals the port the app listens on; app bound to 0.0.0.0; container health |
503 No Available Server | Proxy | Health check configuration, domain and port mismatch, proxy container status |
504 on slow endpoints | Network path | Proxy timeouts, Docker network isolation, upstream response time |
| HTTPS fails or the certificate is wrong | Proxy | Ports 80/443 open in the firewall, DNS pointing at this server, certificate resolver |
| Deploy reported success, users still see the old version | Runtime | Which replacement path was used, whether the image was reused instead of rebuilt, whether the proxy route was regenerated |
| Data disappeared after a deploy | Runtime | Was that path a volume or bind mount, or did it live inside the container |
One honest caveat: this table localizes a problem, it does not diagnose it. The logs are still the evidence — deployment logs for anything the control plane did, container logs for the application, proxy logs for routing and TLS.
What a self-hosted PaaS does not take over
The documentation calls this the responsibility boundary, and it is the honest half of the "self-hosting is cheaper" argument. Coolify coordinates builds, deployments, routing, certificates, environment variables, and lifecycle actions. You remain responsible for:
- DNS records that point each domain at the right server
- firewall rules for ports 80, 443, SSH, and anything you expose directly
- operating system updates on the connected servers
- application and database data, plus a backup and restore plan you have actually tested
- secrets, access permissions, and the security of whatever you expose
- and, when self-hosting, updating, backing up, and recovering the Coolify instance itself
If a platform handled all of that for you, it would not be a PaaS on your server. It would be a hosting provider.
Where this leaves the alternatives
Once you see the three roles, the comparison stops being a feature table:
Vercel and Heroku run all three roles for you. You rent the whole stack, and you give up the ability to fix the proxy yourself.
Kubernetes fills the same three roles with more machinery: a control plane of its own (API server, scheduler, etcd), an ingress controller where the reverse proxy sits, and a container runtime per node. The roles are identical; the operational surface is an order of magnitude larger.
Docker Compose and a hand-written Nginx or Caddy config on one box is the same three-role model with you as the control plane — your shell history and your memory of which config file is authoritative. It works until the moment you need to know what changed and when.
Self-hosted PaaS tools — Coolify, Dokploy, Dokku, and friends — automate the three roles and put the configuration in a database instead of in your head. In the Automation hub, this is the starting point of the DevOps branch: everything else assumes you can name these three layers.
The predecessor to this article, How to Go from Syntax to Shipping, covers the layer above this one: deciding what to build and getting it live the first time. This article is about what you own once it is live.
FAQ
Is a self-hosted PaaS the same as Kubernetes? No. Same three roles, very different scale. Kubernetes gives you a cluster control plane, scheduling, and a much larger operational surface; a self-hosted PaaS like Coolify targets a handful of servers you can reason about individually. If you have one or three machines, Kubernetes usually costs more attention than it repays.
Does my application traffic go through the Coolify dashboard? No. Requests go to the reverse proxy on the server running the resource, then straight to the container over a Docker network. The Coolify instance is not in that path — which is why a control-plane outage does not take your site down.
Do I still need to back anything up myself? Yes, and in two places. The Coolify instance backup covers platform configuration. Your application data — database files, volumes, mounted directories — lives on the connected servers and needs its own backup and a restore test.
Can I stop using Coolify later without rebuilding everything? Largely yes, and this is the strongest argument for the approach: deployed resources are standard Docker containers, networks, and storage, so you can inspect and operate them directly. What you cannot take with you trivially is the automation and configuration that lived in Coolify's database. Plan for that as a migration, not a switch.
Is one server enough? For a personal project or a small product, yes. Just be aware that you are stacking all three roles on one machine, so a full disk or a crashed proxy takes the dashboard down with the site. Separating the control plane from the workload server is the first thing to split when you outgrow one box.
Draw your own three-role map
Take fifteen minutes and write your own three-role map, one line each:
Control plane: <who decides and sends the commands?>
Reverse proxy: <what owns 80/443 and the certificates?>
Runtime: <where do containers actually run, and what holds the data?>Keep it somewhere you will find it at two in the morning. The next time something breaks, the first useful question is not "what is wrong with Coolify" — it is "which of these three stopped doing its job".