Dokku's own documentation says the quiet part out loud: its default scheduler "manage[s] application lifecycles for a single server." For a decade that sentence was the end of the discussion — one box, Heroku-style git pushes, done. dFlow reopens it. The open-source project layers a remote controller over as many Dokku servers as you attach, provisions apps over SSH via node-ssh, builds with Railpack, and wraps the whole thing in one dashboard. Same git-push UX, N boxes.
Here is the verdict up front, before the mechanism: dFlow genuinely answers Dokku's single-box limit for teams running a handful of servers — but every box it adds is still SSH-orchestrated, not reconciled. Nothing watches for drift, nothing heals a dead container unasked, and nothing provisions the ninth box for you. That is the whole post in one paragraph. The rest is the receipts: what the SSH model concretely covers, the five gaps it structurally can't close, and where that ceiling sits against a Cluster API fleet running on the same Hetzner hardware.
| Question | dFlow's answer |
|---|---|
| Deploy one app to box 3 of 5? | Yes — remote provisioning over SSH from one dashboard |
| Notice box 3 drifted from what you declared? | No — there is no declared state to compare against |
| Restart the container that died silently overnight? | No — unless you (or a cron you wrote) notice first |
| Add box 6 when boxes 1–5 fill up? | Manually — attach, install, place workloads yourself |
| Replicate Postgres off its one disk? | No — same shared gotcha as every SSH/Swarm panel |
What dFlow actually is: a controller, N Dokku workers, and SSH between them
dFlow introduced itself in May 2025 as "your personal PaaS, powered by Dokku and Railpack," and the architecture is exactly that composition. Each server you attach runs plain Dokku — deployments, containers, and services handled by the same battle-tested plugins (Postgres, Redis, Let's Encrypt) Dokku has shipped for years. The dFlow controller never replaces that layer; it drives it remotely. Attach a server with an SSH key, a public IP, and a sudo-capable user, and the controller installs the stack (Docker, Dokku, monitoring, backup, routing) on first contact.
Every subsequent operation — create app, set domain, manage env vars, scale containers — runs as remote commands over SSH, powered internally by node-ssh.
The Railpack layer is what makes the result feel like Railway rather than five Dokku CLIs in a trench coat. Borrowing Railway's project/service vocabulary, dFlow adds app-level metadata management, service attachments, environment syncing, and team collaboration on top of raw Dokku. Builds go through Railpack itself — Railway's open-source Go + BuildKit frontend, which detects your language, emits low-level BuildKit LLB directly instead of generating a Dockerfile, and takes customization through a railpack.json file. And the networking story has already grown a serious answer: the secure-ubuntu-paas project wires the controller-to-worker path over Tailscale SSH, so workers expose no public SSH at all — the controller attaches over the tailnet, hardens the box, and stops.
Recent releases show where the project's energy goes: server management (reset, Railpack removal, Dokku uninstallation), moving services between projects on the same server, queue monitoring, and a server terminal streaming real-time logs over SSE. This is a dashboard and provisioning story maturing fast — roughly 500 GitHub stars into a TypeScript codebase that is unambiguously alive. None of it changes the orchestration model underneath, which is what the next two sections are about.
What SSH orchestration concretely covers
Give the model its due: for a small fleet, SSH orchestration covers nearly everything an operator does in a week. Remote app create and configure, domain linking with SSL, environment variables, database provisioning from the dashboard, per-server app grouping so you can place workloads by preference or provider pricing, multi-host fan-out for deploys, container scaling, log streaming. If your fleet is three Hetzner boxes and a laptop staging node, dFlow's dashboard genuinely replaces five terminal windows with one control surface — no Kubernetes to learn, no cluster to bootstrap, no control plane to feed.
This is the same "attach another server" model yesterday's second-machine showdown described for Coolify, and the appeal is identical: the second box joins in minutes, the mental model never exceeds "which server does this app live on," and the cost of box two is just the box. For side projects, agency client sites, and small-team production with predictable capacity, that is not a compromise — it is the right amount of machinery.
But notice what every item on that list has in common: each is an imperative command issued once, at deploy time, down an SSH connection. The controller tells the worker what to do and the moment passes. Nothing persists except the resulting container state on the worker — and that is precisely where the ceiling begins.
The ceiling, mechanism by mechanism
SSH orchestration has no memory of intent. A reconciled system stores desired state and runs a loop that drags reality toward it; an SSH system runs commands and hopes reality stays where the commands left it. Five concrete gaps follow from that one architectural fact:
1. No desired-state reconciliation when a worker drifts. Someone tweaks an env var directly on the box during an incident, a Dokku plugin upgrade rewrites a config default, a disk fills and a deploy half-applies. In dFlow's model there is no declared state to diff against, so none of this registers as drift — the dashboard shows what it last commanded, the box shows what actually happened, and the two quietly disagree until something breaks loudly. A Cluster API fleet treats "production matches the manifest" as a continuously enforced guarantee; an SSH fleet treats it as a hope renewed at every deploy.
2. No health-driven remediation when a container dies unnoticed. Dokku runs your containers under Docker with restart policies, which covers a crashed process on a healthy host. It does not cover the cases that page people: the host itself going dark, the Docker daemon wedging, an OOM loop the restart policy faithfully perpetuates, a volume filling until the database can't write. dFlow can show you logs and queues, but observation is not remediation — there is no MachineHealthCheck equivalent deciding the node is lost and rescheduling elsewhere, because there is no "elsewhere" in the model. Every worker is a pet with a dashboard.
3. No machine lifecycle when the fleet needs a ninth box. Adding capacity is a human project: procure the VPS, attach it over SSH, wait for the stack install, decide which apps move, move them. Nothing in the system watches utilization and provisions ahead of demand, drains a box before maintenance, or replaces a dead machine with an equivalent one. Compare the primitive a Cluster API fleet leans on daily: a MachineDeployment plus the cluster autoscaler turns "we need more room" into a declarative replica count. dFlow's answer to the same sentence is a checklist you execute by hand — fine at three boxes, a part-time job at thirty.
4. Per-box databases with no replication story. This is the shared gotcha yesterday's showdown hung over Coolify, Dokploy, CapRover, and Deploynix alike, and dFlow inherits it unchanged: Dokku's Postgres plugin provisions a database on one server's disk. Multi-server dFlow gives you more app slots, not safer data. Backups (Restic is in the attach stack) bound the blast radius to your last backup window, but failover remains a restore procedure, not an architecture. If your second server exists for redundancy rather than capacity, the database is the part that didn't get the memo.
5. The controller is a single point of failure. One dFlow instance holds the keys, the metadata, and the dashboard. Lose it and deploys, scaling actions, and visibility stop until it is restored — the workers keep serving traffic, but the fleet is headless. There is no controller HA story, no leader election, no backup-controller promotion, because the system was never designed around a control plane that must survive itself. (The workers' resilience here is genuinely good — static sites stay up — but "serving while headless" degrades fast the moment anything needs changing.)
None of these is a bug report. Each is the honest price of a model that chose simplicity over a control loop — and for fleets that stay small and hands-on, the price is correctly low.
Where the ceiling sits against a Cluster API fleet
Put the same Hetzner hardware under Cluster API and every row of the table above inverts. "Add a box" becomes a replica count on a MachineDeployment: CAPH provisions the VPS, bootstraps the node, joins it to the cluster, and the scheduler places work on it without anyone naming the box. "Heal a box" becomes MachineHealthCheck plus node auto-repair: an unhealthy machine is detected, drained, deleted, and replaced by the same declarative loop. Drift gets reconciled continuously, not rediscovered at the next deploy. This is the "zero-drift" posture the SNCF case study runs at national-rail scale — monthly forced reconciliation across hundreds of clusters — and the same controllers enforce it at five nodes.
The honest accounting runs both directions, though. Cluster API buys all of this with a management cluster you operate, provider versions you track, and a conceptual load (Cluster, MachineDeployment, KubeadmConfig, bootstrap tokens) that dwarfs "attach a server over SSH." dFlow's install story is an afternoon; a production-grade CAPI fleet is a project. The question was never which architecture is better in the abstract — it is at what fleet size the reconciliation dividend exceeds its complexity tax.
That crossover sits further out than Kubernetes advocates usually admit and closer in than SSH maximalists hope. The forcing functions are concrete: the first time drift causes an incident you can't explain from the dashboard, the first dead box at 3 AM that needed a human to notice, the first capacity crunch during a launch when "attach a server" was on the critical path. One of those is an anecdote; two is a pattern; three is a migration plan.
Who should use which
Pick dFlow when: your fleet fits in your head (say, under eight boxes), your capacity changes on human timescales, you want Heroku-style git-push UX on machines you own, and your team has no Kubernetes operator. The Tailscale-SSH wiring plus per-server grouping is a genuinely good answer for agencies, indie hackers, and small teams whose infrastructure ambition is "boring and owned." Stay current with releases — the queue management and server terminal work shows a project still climbing its own feature curve.
Graduate to Cluster API when: boxes join and leave faster than a human should track, silent failures have paged you twice, your database needs failover instead of backups, or you want machines to be cattle the system replaces rather than pets you nurse. The migration need not be a rewrite: the same Hetzner account, the same app containers, the same git-push UX layered on top — only the machine layer changes from SSH commands to declared state.
The through-line of every panel in this wave — dFlow, Coolify, Dokploy, CapRover — is that the industry keeps rediscovering Dokku's decade-old insight: most teams want to push code and get a URL. dFlow's contribution is proving that insight scales to N servers without demanding a cluster. Just know which N yours stops at, and what you'll reach for when it does.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



