From one Coolify server to automatic scaling on Hetzner

Coolify deploys to several servers but does not decide how many. What has to move off a single Coolify box before the app can scale, and how an autoscaler fits.

Author
Published
Last updated
Tags
guide, coolify, hetzner, autoscaling

Coolify autoscaling is two problems, not one. Coolify can deploy an application to several servers, but it does not decide how many servers there should be, and it does not create or remove them when load changes; that part is yours. And before either matters, the single Coolify server most people run, with the application, its database and TLS all on one box, has to be taken apart so that a second server is possible at all. This article covers both: the half day to a day of restructuring, and what an autoscaler does with a Coolify setup once it is “multi-server ready”.

What a single Coolify server looks like

One Hetzner server runs Coolify itself, its Traefik proxy with Let’s Encrypt, the application built from source on that server, a PostgreSQL or MySQL container, often Redis, and a volume for uploads. It is a good setup. It is also exactly one server, and every part of it assumes that.

Adding a second server to it fails for five separate reasons:

  1. The database is on the box. A copy of the server is a second database with diverging data; a deleted server is a deleted database.
  2. Uploads and other volumes are on the box. Same problem, smaller blast radius.
  3. TLS is on the box. Traefik on each server would request its own certificate, and Let’s Encrypt’s HTTP challenge would land on whichever server DNS or a balancer picks.
  4. The image is built on the box. A second server has nothing to pull.
  5. Coolify is on the box. If the server that runs Coolify is one of the servers being copied and deleted, you are deleting your deploy tool.

The order below fixes them in the order that keeps the site running throughout.

Step 1: move the database and the other state out

Create a second, small Hetzner server and add it to Coolify as a server (Coolify connects over SSH with its own key). Create the database resource there instead of on the application server, dump and restore, switch the application’s DATABASE_URL, and verify before dropping the old container. Or use a managed database provider; Coolify does not care where the database is as long as the application can reach it.

Redis goes the same way if sessions or cache live in it. Uploads go to S3-compatible object storage (Hetzner Object Storage or any other), which most frameworks support with a driver change and a one-off copy.

When this step is done, the application server holds nothing you would miss. That is the test: if this server disappeared, would anything be lost? Until the answer is “no”, stop here; the rest of the article does not apply, and a scaling group would be refused in preflight anyway.

Step 2: deploy from a registry image

Coolify’s multi-server deployment needs an image that every server can pull. In the application’s settings, enable pushing the built image to a Docker registry (Docker Hub, GitHub Container Registry or your own), give it a name and a tag, and deploy once. From here, every server runs the same image, and a new server does not have to build anything.

This is also where Docker Compose applications in Coolify stop: a Compose stack is a set of services that assume one host, and it cannot be deployed to several servers as one unit. If your application is a Compose stack, split the application service out as a registry-image application and leave the rest (a worker, a cron container) where it is, or on the state server.

Step 3: put TLS on a load balancer, not on Traefik

Create a Hetzner Cloud Load Balancer with a health check on your application’s health endpoint, terminate TLS there with a managed certificate, and point DNS at it. The step-by-step version, including how to move the certificate without a gap, is Put your Docker Compose app behind a Hetzner Load Balancer; the Coolify-specific part is the proxy setting on the application servers.

Two ways that work:

  • Set the server’s proxy to None and publish the application’s port directly. The balancer talks to that port over the private network.
  • Keep Traefik and give the application an http:// FQDN instead of https://. Traefik then routes by host name without requesting a certificate; the balancer terminates TLS in front of it.

Either way, no server in the group requests certificates. The balancer’s health check is now the thing that decides whether a server receives traffic.

Step 4: keep Coolify off the group

Coolify itself stays on a server that is not part of the pool. The original box is a fine candidate: it was created before Hetzner’s price adjustment of 15 June 2026, so it keeps its old price for as long as it exists, and it already has Coolify installed. Strip it down to Coolify, move the application to a fresh server that becomes the first member of the group, or keep the application on it as well and accept that this one server is never removed.

You are now “multi-server ready”

In Coolify terms: the application deploys from a registry image, all state is external, every server in the group has the same CPU architecture, TLS ends at a load balancer, and Coolify runs elsewhere. This is what Coolify’s multi-server deployment is for. Add a server to Coolify, attach the application to it, deploy, and the application runs on both servers behind the balancer.

What Coolify does not do is decide when to add the third server or remove the second. It has no view of load, no notion of a minimum and a maximum, and no health-check-driven replacement. You add servers when you think you need them and remove them when you remember. For a campaign that you know about in advance, that is fine. For a slow rise over months, a broken server at 03:00, or a server type that is suddenly out of stock, it is not.

What an autoscaler adds

A control plane sits beside Coolify and does the capacity part. This is how Hetscale’s Coolify adapter works, the third adapter in our order after snapshot and webhook:

  1. It creates the server. From the load balancer’s request metrics and the servers’ CPU, it decides that the group needs one more, creates a Hetzner server of the group’s type in the group’s location with Coolify’s public SSH key in the server spec and its own ownership labels on it.
  2. Coolify deploys to it. Hetscale registers the server in Coolify through the API, lets Coolify validate it, attaches the application, and triggers the deploy. Coolify does what it would do if you had added the server by hand.
  3. The balancer decides when it is ready. The server becomes a target and counts as capacity only once the load balancer’s health check passes. Coolify’s “deployment finished” is a hint, never the signal.
  4. Removal is the same in reverse. Remove the target, wait for open connections to finish, detach the server in Coolify, shut it down, delete it, and never delete a server that does not carry the labels from step 1.

The Hetzner API token lives in one place, Hetscale; Coolify’s own Hetzner integration is not used, so no second copy of the token exists. The Coolify API token Hetscale asks for has read, write and deploy permissions only, never root, and Hetscale never touches Coolify’s database.

One limitation to know about today. Coolify’s public API deploys an application to all of its servers at once; there is no call that says “deploy to this one new server only”. So the day-one mechanism, which we call fleet mode, triggers the application-wide deploy after attaching the new server, and the existing servers restart their containers as part of it. A pull request that adds a per-destination deploy endpoint is open upstream (coollabsio/coolify#11799, open as of September 2026); once it ships in a Coolify release, Hetscale detects the version and deploys to the new server alone. Until then, fleet mode is disclosed in onboarding, not hidden.

The honest fit

Starting point Prerequisite work Time Fit (1–10)
Coolify, single server, app and database together Steps 1–4 above Half day to a day 3, then 7
Coolify, multi-server ready None 20–30 minutes 8

The fit numbers are from our onboarding table and are what the preflight tells you before anything is created. A single-server Coolify install is a 3 because the work is real and yours; the same install after the four steps is a 7, and a Coolify setup that already deploys to several servers behind a balancer is an 8, with the tokens, the preflight and one supervised dry run as the whole setup. The two case pages, Coolify on a single server and Coolify, multi-server ready, have the steps and the questions people ask about each.

Whether the group would ever grow past two servers is a question for your traffic, not for this article. The calculator gives a first estimate from your server type, your fleet today and the shape of your traffic; a read-only trial replaces the estimate with your last 30 days of metrics before anything is changed.

Frequently asked questions

Do I lose Coolify? No. Coolify keeps building and deploying; it just does it to servers that appear and disappear. Your dashboard, your logs and your environment variables stay where they are.

Does Coolify need my Hetzner API token? No. The autoscaler creates the servers and hands them to Coolify with Coolify’s SSH key. Coolify’s built-in Hetzner server creation is not used, because it cannot set the labels that make a server safe to delete later.

Can I keep the database in Coolify? Yes, on a server that is not in the group. Coolify manages databases on any of its servers; the only rule is that the database server is never one of the servers that scale.

What about Docker Compose applications in Coolify? Not in a scaling group. A Compose stack assumes one host. Split the web service out as a registry-image application; the rest stays put.

Which Coolify versions are supported? The adapter is tested against recorded responses of specific versions and keeps a compatibility list. An unsupported version pauses the group with a clear message rather than guessing at the API.

See what Hetscale would have done with your real data — connect read-only, get your report in minutes.

Connect read-only