Skip to main content

Moving Off a PaaS to Self-Hosted Dokploy: What Actually Changes

devops dokploy self-hosting deployment infrastructure

We run a number of small production apps - each a monorepo with an API, a web frontend and a worker. They lived on a managed PaaS, which was excellent until the bill scaled with the number of apps rather than with the traffic. Ten quiet apps cost roughly ten times one quiet app, even though together they would not trouble a single modest server.

So we moved them onto Dokploy on one VPS. This is what changed, in both directions.

What Dokploy is

Dokploy is an open-source, self-hosted deployment platform. You put it on a server and it gives you the PaaS-shaped workflow - connect a Git repo, build from a Dockerfile, get TLS and a domain, roll out on push - on hardware you control. Under the hood it is Docker Swarm plus Traefik, with a web UI and an API in front.

The pitch is that you keep the deployment ergonomics and stop paying per app. Broadly, that held for us.

The architecture decision that matters most

If you take one thing from this: run one shared data plane, not a database per app.

Our layout is a single Postgres instance and a single Redis instance, shared across every app on the box. Each app and environment gets its own Postgres role and database, and its own Redis DB index. Isolation is by per-app credentials, not by per-app instances.

This is the difference between self-hosting being cheaper and self-hosting being a second job. A Postgres instance per app means a dozen instances to patch, back up, monitor and size, each idling on memory you paid for. One instance with a dozen databases is one thing to operate.

The tradeoff is real and you should take it deliberately: a shared instance is a shared blast radius. If it goes down, everything goes down together, and a runaway query in one app can starve the others. For a portfolio of small apps where the alternative was not self-hosting at all, we think that is clearly the right trade. For anything with a real availability commitment, it is not.

Beyond that, the shape is one Dokploy project per app, holding one Application per service, all built from the repo’s own Dockerfiles. Traefik terminates TLS with Let’s Encrypt and everything sits on a single overlay network.

Migration order, so you do not lose anything

The sequence matters more than any individual step, and most of the risk is concentrated in the first one.

Pull the environment variables out first, before you delete anything. This is the step you cannot redo. Most platforms expose resolved variables through an API - dump them per service and per environment and keep them somewhere safe. Do this while the old deployment still exists, because the moment you tear it down, any secret that existed only there is gone.

When you copy them over, sort them into three piles:

  • Real application secrets you must carry: mail API keys, object storage credentials, OAuth clients, billing keys, push notification keys.
  • Platform-internal variables you must not carry: anything the old platform injected, including its DATABASE_URL and REDIS_URL. You will be setting your own.
  • Regenerable secrets: session and encryption keys. If the environment has no data worth preserving, generate fresh ones rather than migrating them.

Then create the per-app database role, then deploy, then cut DNS. Keep the old deployment alive and serving until the new one answers correctly on its own hostname. Only then move DNS. If the old database held live data, the data copy is its own rehearsed step and it belongs immediately before the DNS switch, not before the deploy.

The parts that are genuinely worse

We would make the same move again, and we are not going to pretend it is free.

You own the box now. Kernel updates, disk space, certificate renewals, the container runtime - all yours. None of it is hard, all of it is a thing that can page you on a Sunday.

Memory is the constraint that will actually bite. A single VPS running a shared Postgres, a Redis, a reverse proxy and a dozen app containers gets tight faster than you expect, and the first symptom is usually a build being killed rather than an app falling over. Provision generous swap before you need it. We learned this in the order you would expect.

Builds compete with production. Building images on the same box that serves traffic means a heavy build degrades live apps. If you deploy often, keep builds off the serving box.

Nothing auto-starts unless you tell it to. Creating a resource and deploying a resource are separate operations. A freshly created database sits idle until you explicitly deploy it, which is obvious in retrospect and confusing the first time.

Your monitoring is now load-bearing. On a managed platform, someone else notices the host is unhealthy. Here, if you do not have uptime checks and alerts, the first person to notice is a visitor.

Things that worked on the old platform can quietly stop working. Anything that depends on how the artifact is packaged, mounted or proxied is worth re-verifying against the new deployment rather than assumed. We wrote up one of these in your security headers probably cover half your site: correct code, green local tests, and headers missing in production.

What got better

Cost stops scaling with app count. This was the entire point and it delivered. Adding the eleventh small app costs approximately nothing.

Deploys are still push-to-deploy. We did not trade ergonomics for cost, which is what we were most worried about. Push to the default branch, it builds and rolls out.

Everything is inspectable. When a deploy misbehaves you can SSH in and look. On a managed platform you file a ticket and wait.

It has an API. Anything you can do in the UI you can automate. Ours is driven from a small CLI in our infrastructure repo, so a new app is a config entry rather than twenty minutes of clicking.

Who should not do this

If your app has a real uptime commitment to paying customers, or you have no appetite for owning a server, stay on the managed platform and pay the premium. It is a fair price for someone else carrying the pager.

This trade makes sense for a portfolio of small apps where the per-app pricing has become the dominant cost and the availability requirement is “should be up, and if it is not we will fix it.” That describes a lot of side projects, internal tools and small client sites. It does not describe production infrastructure with an SLA.

Be honest about which one you have before you move.

Need help shipping?

We help teams build and ship software that works. Performance, SEO, features, weekly demos, full ownership.

Tell Us What's Stuck