How to Deploy Dokku on a VPS
Updated Sep 2026
verified on Ubuntu 26.04 · Sep 2026Install Dokku on an Ubuntu VPS — a single-server, git-push PaaS with no web UI — then set a global domain, deploy a first app, add Let's Encrypt, and back up the directories that hold everything.
- A VPS with at least 1 GB RAM (more if apps build on the server)
- A fresh Ubuntu 22.04, 24.04 or 26.04 server with root/sudo SSH access, dedicated to Dokku
- An SSH key on your own computer, and git
- A domain where you can create a wildcard A record (recommended)
What Dokku is
Dokku is the smallest of the self-hosted platforms: a set
of shell scripts and plugins, MIT-licensed, that turns one server into a
Heroku-style PaaS. You git push an app to the server and Dokku builds it —
with a Dockerfile if the repo has one, or by detecting the language with
buildpacks — starts it in a container, and routes a hostname to it through
nginx. Databases, Let's Encrypt and more come as official plugins.
There is no web UI. Everything is a dokku command, either on the server
or over SSH from your laptop. That is the main thing to decide on: if you
want a dashboard, CapRover,
Dokploy or Coolify give
you one, and Dokku vs CapRover compares the two
approaches. If you like Heroku's CLI workflow, Dokku is the closest match.
Dokku installs as system packages and runs nginx on the host, owning ports 80 and 443. Give it a dedicated server.
Server sizing
Dokku's docs set the minimum at 1 GB of system memory with the default Docker scheduler, or less with swap added. Upstream notes that Dokku doesn't run daemons of its own; the memory goes to your apps and, above all, to builds. Buildpack builds of Node, Ruby or Java apps are the spikes that push a 1 GB box into swap, so:
- 1 GB for a few small apps, ideally deployed from prebuilt images.
- 2–4 GB if the server builds apps from source.
- Disk: images and build caches accumulate; start with 40 GB+.
Dokku supports AMD64 and ARM64 servers.
Prepare the server
Dokku's installer installs Docker if it is missing. Open SSH and the web ports:
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
sudo ufw status verbose
Your pushes go over SSH on port 22 to the dokku user, so there is nothing
else to open.
Paid link — we earn a commission if you shop through it.
Install Dokku
The official bootstrap script, pinned to a release (this is the version the docs listed when we wrote this; check the installation page for the current one):
cd ~
wget -NP . https://dokku.com/install/v0.38.31/bootstrap.sh
sudo DOKKU_TAG=v0.38.31 bash bootstrap.sh
On Debian and Ubuntu it installs Dokku as an apt package, along with its helpers. Upstream says it takes 5–10 minutes. Confirm it worked:
sudo dokku version
systemctl is-active nginx
SSH key and global domain
Dokku authorises pushes by SSH key. Your key is usually already in the admin
user's authorized_keys, so add it to Dokku under the name admin.
ssh-keys:add takes exactly one key, and some providers put comment lines in
that file (Google Cloud adds # Added by Google), which it rejects as "not a
valid ssh public key" — so feed it the first real key line only:
sudo dokku ssh-keys:list 2>/dev/null | grep -q 'admin' || \
grep -v '^#' ~/.ssh/authorized_keys | head -n 1 | sudo dokku ssh-keys:add admin
sudo dokku ssh-keys:list
If you log in with several keys, add each one under its own name.
Now the global domain. With a wildcard A record (*.example.com pointing
at the server), each app gets its own subdomain — myapp.example.com. With a
single domain or a bare IP, apps are reached on random ports instead, which is
much less pleasant. Set it (replace example.com with your domain):
sudo dokku domains:set-global example.com
sudo dokku domains:report --global
No domain yet? Upstream suggests SERVER_IP.sslip.io (your IP with
.sslip.io on the end) as a stand-in that still gives every app a subdomain.
Deploy a first app
The fastest test deploys a prebuilt image with git:from-image, which builds
the app as if its repo held only FROM <image>. traefik/whoami is a tiny
web server that echoes the request:
sudo dokku apps:exists hello 2>/dev/null || sudo dokku apps:create hello
sudo dokku git:from-image hello traefik/whoami:latest
sudo dokku ps:report hello | grep -i "running"
Check nginx routes the hostname to it (this works before DNS exists, by sending the Host header by hand):
curl -s -H "Host: hello.example.com" http://127.0.0.1/ | head -3
You should see Hostname: and the container's details. For your own code,
the usual workflow runs on your computer:
# on the server first: sudo dokku apps:create myapp
cd ~/code/myapp
git remote add dokku dokku@example.com:myapp
git push dokku main
Dokku detects a Dockerfile or picks a buildpack, builds, starts the app,
and prints its URL.
HTTPS + domain
Let's Encrypt comes from the official dokku-letsencrypt plugin. Install it,
set the contact email once, and add the renewal cron job:
sudo dokku plugin:installed letsencrypt || \
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
sudo dokku letsencrypt:set --global email you@example.com
sudo dokku letsencrypt:cron-job --add
Then, once hello.example.com resolves to the server and port 80 is
reachable, enable it per app:
sudo dokku letsencrypt:enable hello
curl -sI https://hello.example.com/
Dokku rewrites the app's nginx config to serve HTTPS and redirect HTTP. The cron job renews certificates before they expire.
Databases and persistent data
Datastores are plugins too. For Postgres, the official plugin creates a
database container and links it to an app, which sets DATABASE_URL:
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git
sudo dokku postgres:create mydb
sudo dokku postgres:link mydb myapp
App containers are replaced on every deploy, so files an app writes need
storage mounts. Upstream recommends app-specific directories under
/var/lib/dokku/data/storage, mounted with dokku storage:mount, so the
standard backup below includes them.
Securing it
- Anyone with a key in
dokku ssh-keyscan deploy — and run commands. Keep that list short, and remove keys you no longer use withdokku ssh-keys:remove <name>. - Keep the OS patched. Dokku's upgrade docs recommend unattended security upgrades, and Docker updates when they are released.
- Don't expose database ports. Linked datastores are reachable by the app
over Docker's network;
postgres:exposepublishes one to the world, and Docker-published ports bypassufw.
Backups
Dokku's documented backup is a tarball of the directories that hold its state, taken while no Dokku command or deploy is running:
export BACKUP_TIME=$(date +%Y-%m-%d-%H-%M)
sudo mkdir -p /var/lib/dokku/services
sudo chown dokku:dokku /var/lib/dokku/services
mkdir -p /tmp/dokku-backups/
sudo tar -czf "/tmp/dokku-backups/${BACKUP_TIME}.tar.gz" /home/dokku /var/lib/dokku/config /var/lib/dokku/data /var/lib/dokku/services /var/lib/dokku/plugins
ls -lh /tmp/dokku-backups/
Move it off the server. Two caveats from upstream: a file-level copy of
/var/lib/dokku/services can catch a database mid-write, so also take
logical dumps (dokku postgres:export mydb > mydb.dump); and apps deployed
from images may need their original deploy command re-run after a restore.
Restoring is sudo tar -xzf backup.tar.gz -C / on a fresh Dokku server,
then starting each datastore and rebuilding the apps.
Upgrades
Dokku is an apt package, so upgrades are apt upgrades. Read the migration guide for every version between yours and the target first, and if Docker or herokuish are being upgraded too, stop the apps and rebuild afterwards:
sudo dokku ps:stop --all
sudo apt-get update
sudo apt-get --no-install-recommends install dokku herokuish sshcommand plugn gliderlabs-sigil dokku-update dokku-event-listener
sudo dokku ps:rebuild --all
Troubleshooting
git push asks for a password. Your key isn't in dokku ssh-keys:list,
or you are pushing to the wrong user. The remote must be
dokku@your-server:app, not your own username.
A build dies partway through. Usually out of memory on a small server.
Check free -h and dmesg | tail; add swap, give the server more RAM, or
build the image elsewhere and deploy it with git:from-image.
The app runs but its domain shows another app or a default page. The
global domain or the app's domains are wrong. Check
sudo dokku domains:report myapp and that the DNS record points here.
Let's Encrypt fails. The domain must resolve to this server and port 80 must be reachable; the plugin's output names the failing check.
Verification + next steps
You're done when: git push dokku main from your laptop deploys an app, it
answers at https://myapp.example.com with a valid certificate, the renewal
cron job is in place, and a backup tarball has been copied off the box.
From there, move apps over one at a time, link a datastore where needed, and
remove the hello test app with sudo dokku apps:destroy hello. For hosting
picks, see Best VPS for Docker and
Best VPS for Node.js.