Migrating from Dokku to DeployHQ
Dokku is the smallest reasonable PaaS — Heroku-compatible buildpacks, git push dokku master, container-based isolation, plugins for Postgres and Redis, automatic Let's Encrypt — running on a single VPS you own. For a small team shipping one or two apps to one server, it is genuinely hard to beat.
This guide is for teams who have outgrown that shape. The common triggers are:
- You need multi-server deploys. Dokku scales out (Dokku Pro, Kubernetes-flavoured setups) but the path is not as smooth as starting from a tool that assumed multi-server from day one.
- You want governance. Audit log of every deploy, role-based deploy permissions, deploy approvals — none of which fit naturally into
git pushplus SSH-into-the-box-and-run-dokku-commands. - Your stack has parts that do not belong in containers. Static sites, S3 syncs, FTP-only legacy hosts, Elastic Beanstalk apps, Heroku targets, Kubernetes clusters via
kubectl— Dokku is one tool, DeployHQ is a hub for many.
If none of those apply and your single-server Dokku setup is happy, this is not the migration for you. DeployHQ is not a drop-in PaaS replacement — read on for what that actually means.
For a side-by-side feature comparison, see DeployHQ vs Dokku. If you are weighing other self-hosted PaaS options, DeployHQ vs Coolify and DeployHQ vs Kamal cover the same territory. The migration shape from Coolify is nearly identical to this one — see Migrating from Coolify to DeployHQ if that is your starting point instead.
This is a model shift, not a one-to-one swap
Every other migration guide in this series translates between similar shapes — a deploy.rb to a UI, one dashboard to another. Dokku is structurally different. It is a PaaS. DeployHQ is a deploy automation tool. They overlap on "ship code to a server" and almost nowhere else.
Be honest about what changes:
| What Dokku gives you | What DeployHQ replaces it with |
|---|---|
| Container-based deploys via buildpacks or Dockerfile | Different model. Atomic file-based releases via symlinked release directories — the Capistrano-style approach. Or: container deploys via Custom Actions running docker / kubectl / cloud CLIs. |
git push dokku master to deploy |
Web UI, CLI, API, or automatic deployments on every push to a tracked branch. |
| Built-in nginx/HAProxy routing per app | You manage this. Caddy, nginx, or a load balancer in front of your servers. |
dokku letsencrypt automatic SSL |
You manage this. Caddy (recommended for new setups — automatic Let's Encrypt out of the box), certbot, or terminate at the load balancer. |
Plugins: dokku postgres:create, dokku redis:create |
External services. Managed Postgres (RDS, Neon, Supabase, your VPS provider), managed Redis (Upstash, Redis Cloud), or run these as separate services on a dedicated server. |
dokku config:set APP KEY=value |
Environment variables per project, global env vars shared across projects, plus shared files for .env-style secrets. |
dokku ps:scale web=3 worker=2 |
Process management is your concern (systemd, supervisord, Procfile-runner of choice). DeployHQ deploys the code; you decide how it runs. |
| Heroku-compatible buildpacks | Build pipelines that run any commands you need — no buildpack format required. |
Read that table twice. If half of it represents work you do not want to take on, Dokku is doing more for you than you realised, and "migrate" might mean "stay on Dokku and add DeployHQ alongside it for the rest of the stack." That is a fine outcome.
Three migration paths
Path 1: Full migration to file-based atomic releases
The most common DeployHQ shape. You give up containers and buildpacks; you gain atomic symlinked releases, multi-server parallel deploys, build pipelines that run off the target, audit logs, and per-environment permissions. You take on direct responsibility for routing, SSL, and process management.
This works well if your apps are framework-shaped (Rails, Laravel, Django, Express) and the buildpack was largely doing bundle install, npm ci, composer install, and asset compilation — all of which become DeployHQ build pipeline steps with the same outcome.
The shape:
- Provision a server (your existing Dokku host can be reused once you have removed Dokku, or use the Managed VPS beta).
- Install your runtime directly: PHP-FPM, Puma, Gunicorn, PM2 — whatever the buildpack was doing inside the container.
- Set up Caddy in front for routing and SSL. Caddy with
caddy.example.com { reverse_proxy localhost:3000 }plus automatic Let's Encrypt is a one-file replacement fordokku letsencrypt:enable. - Move your Procfile process types into systemd units (or your supervisor of choice).
- Connect DeployHQ, configure the project, and start deploying.
Path 2: Keep containers, deploy via Custom Actions
If your apps need containers — multi-stage Dockerfiles, image-only deploys, Kubernetes targets — Custom Actions (currently in beta) run any CLI tool inside Docker as a deploy step. You can drive docker push, kubectl set image, gcloud run deploy, aws ecs update-service, or terraform apply from a DeployHQ deploy with the same audit log, role-based permissions, and rollback story as SSH deploys.
This is the right path if you want the DeployHQ governance layer (UI, audit, RBAC, AI error explanation) without de-containerizing.
Path 3: Hybrid — keep Dokku for what it does well, use DeployHQ for the rest
If your Dokku setup is happy and the only reason you are looking at DeployHQ is to deploy the other parts of your stack (the static marketing site, the S3 asset bucket, the FTP-only legacy app, the Heroku side project), the answer might be "both."
DeployHQ does not need to replace Dokku to be useful — it can sit alongside it and handle every deploy target Dokku does not. Many teams run this way for years.
Filling the gaps Dokku used to fill
If you choose Path 1 (full migration), here is the practical replacement set:
SSL and routing: Caddy, installed once on the server, is the closest one-step replacement for dokku letsencrypt. Define each app as a site block; Caddy handles HTTPS automatically. nginx + certbot is the slightly-more-fiddly alternative.
Postgres and Redis: Move to a managed service if you can — your time saved on operating these is worth the per-month fee at any meaningful scale. If you stay self-hosted, run them on a dedicated server (or the same server, if you understand the resource trade-offs) and treat them as ordinary services with their own backups and monitoring.
Environment variables: Per-project environment variables cover the dokku config:set use case. For values shared across multiple projects (a single SENTRY_DSN, a shared API key), global environment variables avoid duplication.
Scaling: A Procfile becomes a set of systemd units (or supervisord configs, or pm2 ecosystem files). DeployHQ deploys the release; the supervisor restarts the right number of workers.
Heartbeats and uptime monitoring: Better Stack, UptimeRobot, or your monitoring tool of choice. See sending deploy events to Better Stack for the integration pattern.
Per-app domains: Caddy site blocks. Or your CDN (Cloudflare, Fastly) terminating in front of the server.
Migration in three steps (Path 1)
1. Stand up the new server
This is the work Dokku used to do at install time. Provision a server (or reuse your Dokku host once decommissioned), install your runtime, install Caddy for SSL/routing, and configure systemd units for each process type from your Procfile.
If you are starting from scratch and want this off your plate, the Managed VPS beta provisions a server inside DeployHQ in a couple of clicks, with SSH keys handled automatically.
2. Replicate the buildpack as a build pipeline
Most Heroku/Dokku buildpacks resolve to a small set of commands. Translate them into build pipeline steps:
- Ruby/Rails:
bundle install --deployment --without development test,bundle exec rails assets:precompile - Node:
npm ci --omit=dev,npm run build - PHP/Laravel:
composer install --no-dev --optimize-autoloader,npm ci && npm run build - Python/Django:
pip install -r requirements.txt,python manage.py collectstatic --noinput
These run once on our build infrastructure; the resulting release ships to every target server.
3. Wire deploy hooks as SSH commands
Anything the buildpack ran on container start, or that Dokku triggered post-deploy, becomes an SSH command tied to a deploy stage:
- Database migrations:
bundle exec rails db:migrate,php artisan migrate --force,python manage.py migrate— after upload, before symlink change - Service restart:
sudo systemctl reload puma,sudo systemctl restart php-fpm,sudo systemctl restart django-app— after deploy - Cache warmup, queue restart, etc. — after deploy
For most Dokku apps, end-to-end migration of a single app is a full day — significantly more than the half-day typical for the script-based migrations in this series, because you are also taking on routing, SSL, and process management. Plan accordingly. Concierge migration is available on Pro and above — point us at your Dokku setup and we will do the migration with you on a screenshare.
What teams ask before they switch
Will my Heroku-style buildpacks survive? No, in the buildpack sense. But the work a buildpack did (installing dependencies, building assets) translates directly into DeployHQ build pipeline commands. You write three to five lines instead of relying on autodetection.
What about my dokku-postgres database? Dump and restore into your new database (managed service or self-hosted). The data migration is independent of the deploy migration; do it once during the cut-over window.
Can I keep my existing server? Yes — but only after Dokku is fully decommissioned. Dokku heavily customises the server's nginx, Docker, and process layout. Migrating means tearing down those changes and rebuilding. Most teams find it easier to spin up a fresh server, migrate apps one by one, and decommission the Dokku host last.
What if I have ten apps on one Dokku host? Migrate one at a time. Stand up the new server, migrate app one, prove it works, migrate app two, and so on. Run both setups in parallel until all apps are off Dokku.
Do I have to give up git push deploys? No. DeployHQ supports automatic deployments on every push to a tracked branch. The shape changes — instead of git push dokku main you push to your normal Git remote and DeployHQ deploys on the webhook — but the developer experience of "push and it deploys" is preserved.
What about HTTPS? Caddy is the smoothest replacement for dokku letsencrypt. Install once, define a site block per app, restart. Automatic Let's Encrypt out of the box. No DeployHQ-side configuration required — DeployHQ deploys your code; Caddy handles the TLS layer.
What if I want to keep containers but move off Dokku? Path 2 (Custom Actions) is your route. Or evaluate dedicated container platforms like Kamal — see DeployHQ vs Kamal for the trade-offs, and Migrating from Kamal to DeployHQ if you eventually outgrow that too.
Start your migration
Stand up a new server (or use the Managed VPS beta), connect a DeployHQ project to your repo, replicate your buildpack as a build pipeline, wire your post-deploy hooks as SSH commands, and migrate one app at a time. Plan for a full day per app and a parallel-running period before you decommission the Dokku host.
If your stack is part Dokku-shaped and part not, consider Path 3 (hybrid) before committing to a full migration. There is no requirement to move everything.
Dokku is a trademark of its respective maintainers. DeployHQ is not affiliated with the Dokku project. We just understand what the migration looks like when a self-hosted PaaS stops being the right shape for the job.