The first server is where a self-hosted Heroku alternative feels magical. Install the platform, connect GitHub, add a domain, and your application is online without a cloud bill that grows one line item at a time.
The second server is the reality check. It raises questions the app deployment layer cannot answer by itself: who creates the machine, who replaces it when it disappears, where traffic goes when one copy is unhealthy, and how the platform proves that every node still matches the intended configuration?
Here is the short answer for the three tools in this comparison:
| Platform | What it primarily manages | What happens when you need another server | Who owns machine lifecycle? |
|---|---|---|---|
| Kubero | Applications and pipelines inside Kubernetes | Add capacity to the Kubernetes cluster, then let Kubero schedule another application replica | The Kubernetes distribution, cloud provider, or Cluster API operator underneath it |
| Porter | A Heroku-like application experience on Kubernetes provisioned in your cloud account | Provision or resize the cluster, then deploy the application through Porter | Porter plus your cloud/Kubernetes layer; the account and infrastructure remain yours |
| Coolify | Docker resources on connected Linux servers | Attach a server, push the image to a registry, deploy a copy, and configure traffic routing separately | You: server access, replacement, load balancer, DNS, firewall, and shared state |
| CAPI-based platform | Clusters and machines as declarative resources | Change the desired machine pool; provider controllers create, heal, or replace machines | The CAPI provider and the platform operator, with the desired state in Git or an API |
The distinction matters because “self-hosted PaaS” describes where the application runs, not necessarily how the machines below it are born, upgraded, or retired. Kubero, Porter, and Coolify can all reduce the work between a Git commit and a running service. A fleet-lifecycle layer closes a different gap.
That is the useful question behind Kubero’s Show HN v3 launch. The project presents itself as an open-source Heroku alternative with Git deployments, review apps, autoscaling, and a Kubernetes operator. Those features solve the developer-facing half of self-hosting. The comparison below asks what remains when the application platform needs a new machine, not merely a new Pod.
Three useful platforms, three different boundaries
Kubero: the Kubernetes-native app layer
Kubero is the closest of the three to the Kubernetes API. Its documentation describes two deployment strategies: Docker, where Kubero pulls an existing image, and GitOps, where it clones a repository and runs a build. The GitOps path supports Runpacks, Nixpacks, and a Dockerfile, so a developer can keep a Heroku-like source workflow without writing a Deployment and Service by hand.
The project runs as a UI and operator on an existing Kubernetes instance. Its own quickstart says you can bring an existing cluster, or use the Kubero CLI to create one through selected providers such as GKE, Scaleway, DigitalOcean, or Linode. That is a helpful on-ramp, but it is not the same thing as Kubero owning the complete lifecycle of every machine in that cluster.
The concrete boundary is visible in the deployment model. A Kubero pipeline and app can express the application desired state through Kubernetes resources. A worker node is a different resource owned by the cluster's infrastructure layer. If the node pool is full, the operator must make the underlying cluster larger or connect autoscaling that understands how to create capacity. Kubero then benefits from the new capacity; it did not, by itself, turn a server purchase, image, network, bootstrap, and replacement policy into an application-level operation.
That separation is a strength when a team already has Kubernetes. It lets Kubero stay focused on developer experience, templates, review apps, add-ons, logs, and app-level policy. It is also the first place a small team can accidentally confuse “Kubernetes-native” with “machine lifecycle included.”
Porter: the team-owned cloud account boundary
Porter takes a different route. Its current quickstart asks for an AWS, GCP, or Azure account and says Porter provisions a Kubernetes cluster in that account. On AWS, the guide describes an IAM role created through CloudFormation; it also gives a default estimate of roughly $225 per month for the default cluster configuration, while warning that the real amount varies with region and customization.
That is a useful model for a team that wants a Heroku-like interface but needs its workloads and cloud account to remain under its control. The platform can hide much of the initial Kubernetes setup, then deploy from a GitHub repository or container registry. Porter’s application docs describe an application as a group of services that share a build and environment variables, which is a practical abstraction for a multi-process app.
But the infrastructure boundary still matters. Porter can provision a cluster; the resulting account, IAM permissions, Kubernetes nodes, cloud networking, and provider billing are not abstract concepts that disappear. A second application server is a cluster-capacity event. Whether that means adding a node, changing a node group, or letting a cloud autoscaler act depends on the Kubernetes and cloud configuration beneath the Porter workflow.
Porter therefore occupies an important middle layer: more infrastructure-aware than a Docker-on-one-server panel, but not automatically a provider-neutral machine reconciliation system. It can give a team a smooth application path while the team retains responsibility for the cluster contract it has chosen.
Coolify: connected servers and explicit external pieces
Coolify is often described as a single-server self-hosted PaaS, but that is no longer a precise description of its documented feature set. Coolify supports connected deployment servers, and its newer multi-server deployment guide describes building an image on a primary or build server, pushing it to a registry, and having additional servers pull and run that image.
That is a real multi-server workflow. It is also a deliberately explicit one. Attaching servers does not create traffic distribution. Coolify’s guide says that a complete highly available setup still requires a load balancer with health checks, DNS, firewall or security-group rules, shared data and session storage, and monitoring for every application server.
The distinction can be summarized like this:
Coolify can make the same application artifact run on server A and server B.
The operator still has to make server A and server B a reliable service.Coolify’s server documentation says it manages connected Linux machines through SSH. That makes the model approachable: add a server, validate the connection, configure a destination, and deploy. It also makes the machine boundary visible. A server that is powered off, reprovisioned with a new IP, or lost because its provider terminated it is an infrastructure incident, not simply another failed container deployment.
This is not a criticism of Coolify. For a single large server or a handful of independently managed machines, keeping the external parts visible can be simpler than introducing a full cluster control plane. It is a reminder to count the pieces when the requirement changes from “run two copies” to “survive a machine failure without a manual infrastructure runbook.”
The second-server test: one ordinary failure, end to end
Consider a representative small production fleet: twelve stateless web and worker services, three Linux servers, a container registry, one external database, and a requirement to tolerate the loss of one application server. The application image is reproducible, so the question is not whether the platform can build it. The question is what happens after server B vanishes at 02:00.
| Event | Kubero | Porter | Coolify | CAPI-based platform |
|---|---|---|---|---|
| Detect that a node is gone | Kubernetes node health and scheduling signals | Kubernetes/cloud control plane signals | Coolify server health and provider signals | CAPI conditions plus workload-cluster health |
| Restore application capacity | Add or autoscale a Kubernetes node, then reschedule Pods | Add or autoscale cluster capacity, then reschedule workloads | Attach or repair a server, redeploy the image, and route traffic to it | Reconcile the machine pool and let Kubernetes reschedule workloads |
| Replace the machine | Outside Kubero’s app operator | Depends on cloud/Kubernetes configuration | Manual or provider-specific workflow | Provider controller creates a replacement from a declared template |
| Reconcile configuration drift | Kubernetes reconciles app resources | Kubernetes and Porter reconcile their managed resources | Coolify reapplies resource/deployment settings; server OS state remains a separate concern | Desired machine, cluster, and bootstrap state remains the source of truth |
| Traffic failover | Kubernetes Service/Ingress or external LB | Kubernetes ingress/LB or cloud routing | External load balancer and health checks must be configured | Ingress/LB remains a platform concern; machine lifecycle is declarative |
The table shows why the “missing fleet-lifecycle layer” is not a claim that any of these tools cannot scale. They can all participate in a multi-server architecture. The missing piece is the chain from an abstract capacity request to a known machine: select an image, create the machine, attach networking, bootstrap it, join it to the cluster, mark it schedulable, drain it safely, and replace it when its health condition fails.
With Coolify, the operator may execute that chain through a hosting provider’s console, Terraform, an image pipeline, and a runbook. With Kubero, the chain belongs to whatever created the Kubernetes cluster. With Porter, it is split between Porter’s infrastructure workflow and the cloud/Kubernetes services it provisions. All three can be the right answer if the team is comfortable owning that boundary.
What Cluster API adds underneath the PaaS
Cluster API, or CAPI, is a Kubernetes subproject for declarative provisioning, upgrading, and operating multiple clusters. Its core idea is to represent cluster infrastructure with Kubernetes-style APIs: clusters, control planes, machines, and machine sets become objects observed by controllers rather than a list of imperative commands that ran successfully once.
The provider boundary is intentional. CAPI’s upstream project can be extended for infrastructure such as AWS, Azure, and vSphere, while bootstrap and control-plane providers handle how a machine becomes a Kubernetes node. An infrastructure provider translates the abstract machine intent into a provider-specific VM or bare-metal operation.
For a capacity change, the flow looks roughly like this:
desired MachineDeployment replicas: 3 -> 4
|
v
CAPI creates a Machine and provider-specific machine resource
|
v
provider creates the server and applies the bootstrap configuration
|
v
Kubernetes joins the node; workloads can be scheduled thereThe Cluster Autoscaler’s CAPI provider makes the same boundary explicit. It watches MachineSet, MachineDeployment, or MachinePool objects with minimum and maximum size annotations, then changes the scalable resource when pending workloads need capacity. Scale-from-zero is opt-in and depends on support from the infrastructure provider; it is not a universal promise made by the CAPI API.
For a Hetzner-backed fleet, Cluster API Provider Hetzner extends the Kubernetes API with provider-specific resources for declarative infrastructure management. Its maintainers describe self-healing, desired-state reconciliation, and Kubernetes-native operations as the provider’s core properties. CAPH is independently maintained by Syself and the community, not an official Hetzner project, which is exactly the kind of provider ownership and compatibility boundary an operator should audit before adopting it.
This layer does not make operations free. It adds a management cluster, provider credentials, node images, networking, control-plane backups, upgrade policy, observability, and failure testing. It does, however, make the machine lifecycle legible to the same reconciliation model that already governs a Deployment. That is the architectural seam a PaaS can build on when “deploy an app” must eventually include “make sure there is somewhere safe to run it.”
Which boundary fits your team?
Use the smallest platform that matches the failure you are prepared to operate.
- Choose Kubero when your Kubernetes cluster is already a product and you want a developer-facing app layer with pipelines, GitOps resources, review apps, and templates.
- Choose Porter when you want a hosted workflow that provisions Kubernetes into a team-owned public-cloud account and you accept the account, IAM, and cloud infrastructure contract underneath it.
- Choose Coolify when connected Linux servers and explicit external components are a good trade for a fast Docker-based experience, especially while the fleet is small enough to understand as a set of machines.
- Add CAPI when machine creation, replacement, upgrades, and capacity are becoming repeated fleet operations rather than occasional infrastructure work.
Before calling any self-hosted PaaS highly available, run this five-step exercise:
- Remove one application server without warning.
- Record who detects it and how long detection takes.
- Recreate the machine from a versioned image and configuration, without clicking through an undocumented console path.
- Verify that application replicas, traffic routing, certificates, logs, and external state recover.
- Repeat the test after an upgrade, not only on the day the platform is installed.
The first three tools make step zero—getting the application deployed—much easier. A fleet-lifecycle controller makes steps two through five repeatable across machines. In practice, a durable platform often uses both: an app layer for developer intent and a cluster/infrastructure layer for the machines that make that intent possible.
That is also the direction Bex.co takes. It is an open-source, AI-native Render alternative built around the idea that a Git push should produce a running HTTPS service on machines the operator controls, while the underlying fleet remains inspectable and reconcilable through Cluster API rather than hidden behind a vendor-only control plane.



