Dokploy passed 35,000 GitHub stars this summer — more than double CapRover's roughly 15,000 and over half of Coolify's roughly 60,000, by the June 2026 self-hosted PaaS comparison counts — and its most celebrated workflow asks you to write YAML before you can deploy anything. That is worth sitting with. The self-hosted PaaS with the fastest-rising star chart in 2026 centers a deploy contract — a compose.yaml you author, version, and maintain — that the original PaaS pitch spent fifteen years trying to delete.
Here is the verdict up front, because this post is a comparison and comparisons should not bury the lede. If you deploy one web app or one worker — the actual shape of most deploys — git push with build detection still beats authoring a Dockerfile or a compose file, and it is not close. If you deploy a genuinely multi-service system that needs precise orchestration control, compose-first earns its keep. The mistake is making the multi-service contract the default every tenant must satisfy, which is what a compose-first platform's center of gravity does even when auto-detection exists as an option.
What each path asks you to author
Comparisons of deploy tooling usually compare features. Compare authoring burden instead, because that is the tax every tenant pays on every deploy, forever. There are three rungs on this ladder, and each rung up moves work from the platform onto you.
Rung one is detection-first: you push source and the platform figures out the build. No Dockerfile, no compose file, no build manifest of any kind. A Node app with a package.json, a Python app with a requirements.txt, a Go module — the builder detects the toolchain, installs dependencies, and emits an OCI image. Cloud Native Buildpacks (graduated CNCF this summer), Railway's Railpack, and Nixpacks before it all live on this rung, with different detection engines and the same contract: source code in, running container out.
Rung two is Dockerfile-first: you write and maintain the image recipe. A minimal production Dockerfile is thirty-odd lines before you add the parts that matter — a pinned base image, a non-root user, layer caching that does not re-download the world on every build, a healthcheck, signal handling so deploys do not SIGKILL in-flight requests. Every one of those lines is yours to keep current across base-image CVEs, language-version bumps, and the slow drift of Docker best practices. You own the recipe and its entire maintenance tail.
Rung three is compose-first: you write the Dockerfile and the orchestration. A Dokploy-style compose deployment for one app plus its database looks roughly like this once you include what production actually needs:
services:
web:
build: .
restart: unless-stopped
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
networks: [dokploy-network]
db:
image: postgres:16
restart: unless-stopped
volumes: [pgdata:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
retries: 5That is before Traefik routing labels, TLS configuration, resource limits, log rotation, and the backup story for pgdata. None of it is hard in isolation. All of it is yours — to write, to review in every PR that touches infrastructure, to debug at 2 a.m. when depends_on ordering and a slow migration interact badly. For a single stateless web app, this file is pure overhead: ceremony the platform could have derived from the source.
Notice the asymmetry. Moving down the ladder — from compose to Dockerfile to detection — deletes artifacts a tenant maintains. Moving up adds them. A platform's default rung is therefore a statement about whose time it values, and compose-first defaults spend tenant time on every deploy, including the trivial ones.
Where compose-first genuinely wins
Fairness first: there is a real class of workloads where rung three is the right rung, and a post that pretends otherwise is marketing, not analysis.
Multi-service apps with ordering and wiring requirements are the canonical case. An API plus a worker plus a database plus a cache, with the migration gate running before the web tier accepts traffic — that dependency graph is application knowledge, and expressing it declaratively in one file beats encoding it across four dashboard screens or a pile of shell scripts. Dokploy's Compose deployment type exists precisely for this: one artifact describing the whole stack, deployed atomically, with Traefik labels injected for routing.
Local-production parity is the second genuine win. When the same compose file runs on a laptop and on the VPS, docker compose up becomes a faithful rehearsal of production. Teams that keep this discipline catch an entire category of "works on my machine" failures — missing environment variables, wrong service hostnames, volume-mount assumptions — before they reach the server. Detection-first platforms can approximate this with dev containers or preview environments, but a compose file the developer already runs is the shortest path to parity that exists.
The third win is precise control for operators who know exactly what they want. Custom networks, placement constraints, bespoke healthcheck semantics, sidecar containers with shared volumes — compose exposes the Docker API surface nearly wholesale, and some teams use every inch of it. For platform engineers running stateful or exotic workloads, that control is not ceremony; it is the job.
And to keep the comparison honest: Dokploy is not compose-only. Its Application deployment type offers Nixpacks auto-detection alongside Dockerfile, Railpack, and Heroku/Paketo buildpack build types, as the Coolify-vs-Dokploy comparison documents. The critique in this post is aimed at the platform's center of gravity — the celebrated workflow, the templates, the community guidance — which pulls tenants toward authoring compose files even for workloads that would deploy fine from source alone. A default you have to route around is still the default.
Where detection-first wins: the 80% case
Now the other side of the ledger, which is most of the ledger. The overwhelming majority of what gets deployed to a PaaS is a single web service or a single worker: a marketing site, an API, a cron-like job, a side project, a staging copy of any of those. None of them has a dependency graph. None of them needs orchestration. Each one, on a compose-first platform, still pays the authoring tax from the previous section — a compose file describing a "stack" of one.
Detection-first deletes that tax. Push the repo, the builder detects the framework, the platform wires routing and TLS. Render has run this playbook at scale for years: native runtimes for Node, Python, Ruby, Elixir, Go, and Rust with a Dockerfile escape hatch for everything else, all driven from a git push or a render.yaml Blueprint. Heroku proved the model a decade earlier with buildpacks. The mechanism keeps improving — Railpack now resolves exact language versions and produces images 38% smaller for Node and 77% smaller for Python than Nixpacks did, as we covered when Coolify bet its v4.1 builds on it — but the contract is unchanged since 2011: source in, service out.
The maintenance story is where detection-first compounds its lead. Consider CVE day for a base image. On a Dockerfile-first fleet, every tenant's Dockerfile pins some base image, and every one of those pins needs a bump, a rebuild, a test, and a redeploy — multiplied by every app, on every tenant's own schedule, assuming they notice at all. On a buildpack-based fleet, the platform patches the builder's base image once and rebuilds every app against it, often without touching application code. Bloomberg's buildpack fleet — 500-plus applications with centralized patching that cut vulnerability resolution from weeks to hours — is the enterprise proof, but the math favors detection at any scale: one patch point beats N Dockerfiles.
There is also a quieter win: detection removes an entire category of review surface. Nobody reviews a Dockerfile diff for a base-image bump that never happened. Nobody argues in PR comments about COPY ordering or layer-cache hygiene for a recipe that does not exist in the repo. The platform's build team — or the upstream buildpack maintainers — holds that expertise once, instead of every tenant re-deriving it badly.
The honest gotchas, both directions
A comparison that lists only one side's failure modes is a sales pitch. Detection-first has real ones, and anyone choosing it should know them before the first deploy, not after.
Auto-detection guesses, and guesses fail on unusual repos. The January 2026 Nixpacks bug that emitted duplicate Nginx location blocks for Laravel projects containing both composer.json and package.json broke a class of deploys until upstream fixed it — the archetypal detection failure, where a heuristic met a repo shape it did not expect. Version resolution is the chronic version of the same problem: detection must pick a language version from ambiguous signals, and "close enough" rots into security debt when the resolved version falls behind. Opaque build logs compound both — when detection fails, the error often explains what the builder tried, not what your repo should change.
The mitigation is the escape hatch, and its quality decides whether a detection-first platform is trustworthy. Render's Dockerfile fallback, Dokploy's per-application build-type switch, a pack build you can run locally to reproduce exactly what the platform runs — these are what make detection a default rather than a trap. A detection-first platform without a clean manual override is just a compose-first platform with extra steps and worse errors.
Compose-first has symmetric failure modes that its fans underweight. Compose files drift: the checked-in file describes last quarter's topology while the dashboard holds this quarter's truth, because someone fixed production in the UI at midnight and never back-ported the change. Every tenant reinvents the same load-bearing details — healthcheck semantics, restart policies, TLS labels, secret injection — with N different quality levels and no shared review. And compose's greatest strength, exposing the full Docker surface, is also its failure signature: tenants cargo-cult settings they do not understand from templates they did not write, then page someone when unless-stopped and a host reboot interact in a way the template author never tested.
Both ladders have snakes. The question was never which rung is flawless; it is which rung's failure modes you want to own by default.
The decision rule, and what the default gets wrong
Strip the preceding four sections to a rule you can apply to your next deploy:
| Workload shape | Right rung | Why |
|---|---|---|
| Single web app or worker | Detection-first (git push) | No dependency graph to declare; compose/Dockerfile is pure authoring tax |
| App plus managed-database URL | Detection-first | A database the platform provisions is config, not orchestration |
| Multi-service with ordering/wiring | Compose-first | The dependency graph is real application knowledge; declare it once |
| Exotic runtime or compliance-shaped image | Dockerfile-first | Detection cannot guess what policy dictates; own the recipe |
| Anything detection misbuilds | The escape hatch | Defaults need exits; use them without guilt |
Read the table's first two rows and you see the shape of the market: the first two rows are most deploys, and both say detection. That is the 80% case the TODO framing names, and it is why the default matters more than the feature matrix. A platform can offer every rung and still get the tradeoff wrong if its gravity — its templates, its quickstarts, its community lore — parks every tenant on rung three. Dokploy's Application type with Nixpacks default proves the platform knows detection exists; the compose template library, the compose-first guides, and the "best suited for teams heavily using Docker and Compose" positioning prove where its heart is. New tenants follow the heart, not the dropdown.
This is the deliberate bet a buildpack-first, Render-compatible deploy path makes in the other direction: the trivial deploy should require zero build artifacts, and the sophisticated deploy should be an explicit step up, taken when the workload genuinely needs it. Convention over configuration was never about removing power — the Dockerfile hatch stays open — but about refusing to charge every tenant the configuration price for power most deploys never use.
Dokploy's rise to 35,000-plus stars is deserved; it is a fast, clean, genuinely useful piece of software, and its compose story is the best reason to choose it. But stars measure enthusiasm, not defaults, and enthusiasm for a compose-first workflow should not confuse anyone about what that default costs the tenant deploying one small web app. Fifteen years after Heroku first turned git push into a deploy, the most productive thing a platform can do for the common case is still the same: read the source, build the image, and never ask the tenant to describe what the code already says.
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.



