Skip to content

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:

RoleWhat it doesDefault Coolify setup
Control planeStores your configuration, queues deployments, sends commands to serversThe coolify container, backed by coolify-db (PostgreSQL), coolify-redis, and coolify-realtime
Reverse proxyOwns ports 80 and 443, terminates TLS, routes hostnames to containersThe coolify-proxy container — Traefik by default, Caddy as an option
Container runtimeBuilds or pulls images, runs containers, keeps data in volumesDocker 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:

  1. Accept the connection on port 80 or 443.
  2. Match the request by hostname and path.
  3. 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 seeWhat it usually means
502 Bad GatewayThe 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 ServerThe proxy has no upstream it considers usable: health check failing, domain or port mismatch, proxy issue
504 Gateway TimeoutThe request went somewhere and never came back: network isolation, or a proxy timeout shorter than your app's response time
Certificate warning, HTTPS refusedPorts 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:

SymptomLayer to suspectFirst thing to check
Site loads fine, but you cannot deploy or open the dashboardControl planeStatus of the Coolify containers, instance disk space, queue workers
502, and the app container looks healthyProxy ↔ runtime boundaryTarget port equals the port the app listens on; app bound to 0.0.0.0; container health
503 No Available ServerProxyHealth check configuration, domain and port mismatch, proxy container status
504 on slow endpointsNetwork pathProxy timeouts, Docker network isolation, upstream response time
HTTPS fails or the certificate is wrongProxyPorts 80/443 open in the firewall, DNS pointing at this server, certificate resolver
Deploy reported success, users still see the old versionRuntimeWhich replacement path was used, whether the image was reused instead of rebuilt, whether the proxy route was regenerated
Data disappeared after a deployRuntimeWas 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".

Related Reading