Skip to main content

Three Stages of Self-Hosting: The Operational Checklist for Moving Beyond One Server

9 min readDora NodaDora Noda
Share
On this page

A second server is not a Kubernetes requirement. It is a promise you make to your users: that a bad disk, a failed update, or a mistaken deploy on one machine will not decide whether the application is available.

That promise is easy to make too early and dangerous to make too late. The useful dividing line is not whether a team has learned Kubernetes vocabulary. It is whether it can meet its own recovery and change commitments with the equipment and people it has. A one-box PaaS can be the right production platform for a long time. But once the business depends on it, “we can SSH in and fix it” stops being an operating model.

The checklist: four signals that decide the next stage

Start with the table, not a tool choice. These are example bands, not universal service-level objectives. A payment service with a 15-minute recovery target and a team that covers incidents around the clock will graduate sooner than an internal tool whose users can wait until morning. Set the targets with the people who own customer commitments, data-loss tolerance, budget, and on-call coverage.

SignalStage 1: one dependable boxStage 2: multiple apps, one operator boundaryStage 3: replaceable fleet
Downtime ownershipA planned maintenance window and an occasional host outage are acceptable.An outage affects several important apps or interrupts a team that needs weekday availability.A single node outage must not take the service down; failure handling is rehearsed.
Recovery proofA current off-box backup exists and has been restored at least once.Restore time and data-loss tolerance are written down and tested quarterly for every stateful dependency.Restore and failover procedures are tested against the stated RTO/RPO; backups, credentials, and runbooks work without the failed node.
Deploy concurrencyOne person can serialise deploys and roll back directly.Two changes often collide, or a deploy competes with a backup, migration, or incident.Multiple services can roll out with health gates, capacity limits, and an auditable rollback path.
Node replacementRebuilding the host manually in a few hours is acceptable.Replacing it means rediscovering SSH keys, firewall state, volumes, and deployment settings.A declared machine template can produce a replacement; a node-loss drill has a measured completion time.

The thresholds move with the workload. A single marketing site backed by a managed CMS may sensibly remain in Stage 1 even at meaningful traffic. A small SaaS with local uploads, background jobs, and a contractual uptime target may need Stage 3 before it has many users. The table asks a falsifiable question: can we demonstrate each claim, or are we assuming it?

Uptime Institute’s 2025 outage analysis reported a rise in human-error incidents tied to failure to follow procedures. That is a useful warning against treating more machinery as a cure. A fleet helps only when its desired state, recovery process, and change procedure are simpler to execute under pressure than a manual repair.

Stage 1: one dependable box is a legitimate production choice

Stage 1 is not “toy production.” It is a deliberately bounded system: a few services on one server, known resource limits, a documented deploy path, and data that can be recovered. Coolify, Dokploy, CapRover, or a small Docker Compose setup can make this stage dramatically more pleasant than hand-managed processes.

The working example is a typical small product: a web app, a worker, Redis for ephemeral queues, and managed Postgres. The app and worker share one VPS; Postgres is intentionally outside the host failure domain. The operator has a 24-hour recovery target, accepts up to one hour of application-data loss, and publishes a two-hour maintenance window when needed.

That is viable if the team can actually do the following:

  • restore a database backup into a disposable environment and make the application read it;
  • recreate the server from documented configuration, including DNS, TLS, secrets, and deploy keys;
  • measure CPU, memory, disk, and certificate-expiry headroom; and
  • prove that a bad release can be rolled back without improvising on the production host.

Do not confuse a successful backup job with recoverability. Coolify’s own backup documentation says that local storage alone does not protect against losing the server, and that a backup is not a restore test. Its control-plane backup also does not include application volumes or external databases. Those are not flaws unique to Coolify; they are the normal distinction between having an archive and proving recovery.

The Stage 1 anti-pattern is pretending a second app makes the server highly available. It does not. It only makes the same host more valuable. If the server disappears, every container, local volume, and local-only backup disappears with it.

Stage 2: several services expose the operator boundary

Stage 2 begins when the constraint is no longer container scheduling; it is coordination. You may have multiple machines attached to a PaaS, separate build servers, or an experimental swarm. That can spread workloads and improve capacity, but it is not automatically an HA system.

Coolify’s multi-server documentation makes this distinction unusually plainly: attaching additional servers can create multiple application instances, but does not by itself create a complete highly available system. Persistent storage is the usual revealing constraint. Moving a stateless web replica is easy; deciding where a write goes, how it is backed up, and what happens during a partial failure is where “another server” turns into an architecture.

At this stage, make the coordination work explicit before adding a cluster:

  1. Put every backup and important log copy off the application host, then run a timed restore drill. Record the measured recovery time, not the desired one.
  2. Give each service CPU and memory limits, disk alerts, and an owner. A noisy neighbour is still an outage when all workloads share a machine.
  3. Serialise production changes when the team cannot yet prove that concurrent deploys, schema migrations, and rollbacks are safe together.
  4. Write the replacement runbook as if the primary host were already gone: inventory external DNS, object storage, secrets, registries, firewall rules, and the control-plane encryption key.
  5. Choose an explicit trigger for fleet design. Examples: a node loss exceeds the published recovery target; deploys must be queued more than once a week; or no second operator can rebuild the environment from the runbook.

These measures may be the correct stopping point. A two-person team running five low-criticality services does not become safer merely by adopting a control plane it cannot maintain. The goal is to reduce unknown work during failure, not to collect distributed components.

Stage 3: make machines replaceable, then make deployments safe

Stage 3 is warranted when losing one node must be routine rather than heroic. Kubernetes provides workload controllers for this: a Deployment can roll out gradually and uses maxUnavailable and maxSurge to bound availability during an update; the Horizontal Pod Autoscaler can adjust replicas from resource or custom metrics. Neither feature replaces capacity planning, application readiness checks, or a data-replication design, but together they let a team encode the safety properties it previously managed by hand.

The machine layer needs the same treatment. Cluster API represents cluster and machine lifecycle as desired state. With a provider such as Cluster API Provider Hetzner, a machine template and machine deployment describe how worker capacity is created and changed rather than leaving each node as a bespoke SSH project. A MachineHealthCheck can identify an unhealthy machine and trigger remediation, while maxUnhealthy prevents an automated repair loop from replacing too much of the fleet at once.

The core Stage 3 exercise is a node-loss drill:

  1. Pick a worker node during a normal operating period and state the expected result first: which replicas move, what capacity remains, and what recovery time is acceptable.
  2. Drain or terminate the node through the approved lifecycle path—never by silently changing its configuration out of band.
  3. Watch the workload controller replace replicas, verify readiness and error rate at the ingress, and confirm that no stateful workload was assigned a recovery story it does not possess.
  4. Let the machine controller replace capacity from its declared template. Record the time from node loss to ready capacity, plus any manual action required.
  5. Restore a representative backup separately. Node replacement proves compute recovery; it does not prove data recovery.

If any step depends on a person remembering an unversioned command, the fleet is not yet replaceable. That is useful evidence, not a failure. Put the missing action into image build, bootstrap configuration, GitOps, or a runbook, then run the drill again.

Stage 3 also adds real costs: control-plane upgrades, CNI and ingress operations, image and registry policy, observability, incident access, and ownership of the automation that creates machines. Do not sell this as zero-ops. Its benefit is that the work becomes repeatable and bounded instead of appearing only during a host failure.

Run a 30-day graduation experiment before adopting the fleet

The decision does not require a theoretical maturity score. For 30 days, capture four numbers: restore completion time, maximum observed deploy overlap, time to prepare a replacement node, and the number of services affected by a single-host incident. Compare the results with the service targets agreed for that product.

Move from Stage 1 to Stage 2 when you repeatedly miss a recovery or coordination target but can still name a small, controlled set of operational fixes. Move from Stage 2 to Stage 3 when the same targets require a node to be replaceable and workloads to survive it—especially if one host serves several independently important services. Stay put when the measured risk is acceptable and the proposed fleet would create a larger unsupported system than the one it replaces.

That framing keeps Kubernetes from becoming a badge of seriousness. One server is self-hosting. So is a declaratively managed fleet. The distinction is the promise being kept, the evidence behind it, and whether the next failure asks for a repair or a routine replacement.

Bex.co is the open-source, AI-native Render alternative: push a Git repository and run an HTTPS service on machines you own. Its Cluster-API-based approach is aimed at the Stage 3 transition, where repeatable machine lifecycle and a developer-friendly deploy surface need to coexist. Explore the project on GitHub.

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