Skip to main content

Kubero vs Porter vs Coolify: What Happens When Your Self-Hosted Heroku Needs a Second Server?

12 min readDora NodaDora Noda
Share
On this page

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:

PlatformWhat it primarily managesWhat happens when you need another serverWho owns machine lifecycle?
KuberoApplications and pipelines inside KubernetesAdd capacity to the Kubernetes cluster, then let Kubero schedule another application replicaThe Kubernetes distribution, cloud provider, or Cluster API operator underneath it
PorterA Heroku-like application experience on Kubernetes provisioned in your cloud accountProvision or resize the cluster, then deploy the application through PorterPorter plus your cloud/Kubernetes layer; the account and infrastructure remain yours
CoolifyDocker resources on connected Linux serversAttach a server, push the image to a registry, deploy a copy, and configure traffic routing separatelyYou: server access, replacement, load balancer, DNS, firewall, and shared state
CAPI-based platformClusters and machines as declarative resourcesChange the desired machine pool; provider controllers create, heal, or replace machinesThe 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:

text
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.

EventKuberoPorterCoolifyCAPI-based platform
Detect that a node is goneKubernetes node health and scheduling signalsKubernetes/cloud control plane signalsCoolify server health and provider signalsCAPI conditions plus workload-cluster health
Restore application capacityAdd or autoscale a Kubernetes node, then reschedule PodsAdd or autoscale cluster capacity, then reschedule workloadsAttach or repair a server, redeploy the image, and route traffic to itReconcile the machine pool and let Kubernetes reschedule workloads
Replace the machineOutside Kubero’s app operatorDepends on cloud/Kubernetes configurationManual or provider-specific workflowProvider controller creates a replacement from a declared template
Reconcile configuration driftKubernetes reconciles app resourcesKubernetes and Porter reconcile their managed resourcesCoolify reapplies resource/deployment settings; server OS state remains a separate concernDesired machine, cluster, and bootstrap state remains the source of truth
Traffic failoverKubernetes Service/Ingress or external LBKubernetes ingress/LB or cloud routingExternal load balancer and health checks must be configuredIngress/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:

text
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 there

The 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:

  1. Remove one application server without warning.
  2. Record who detects it and how long detection takes.
  3. Recreate the machine from a versioned image and configuration, without clicking through an undocumented console path.
  4. Verify that application replicas, traffic routing, certificates, logs, and external state recover.
  5. 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.


Sources

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex