VPS Snaps

How to back up a Dokku server

Dokku has no single backup command. Export each database with its plugin (dokku postgres:export, or postgres:backup straight to S3), save each app's variables with dokku config:export --format envfile, and archive the directories Dokku keeps its state in: /home/dokku, /var/lib/dokku/config, /var/lib/dokku/data, /var/lib/dokku/services and /var/lib/dokku/plugins. App code comes back with git push.

10 min readUpdated Checked against official documentation

What a Dokku server holds

Dokku is an open-source platform that builds and runs apps on one host, and it keeps all of its state on that host. The commands below were checked against Dokku 0.38.31 and the Postgres plugin 2.2.0, the current releases in October 2026.

WhatWhereHow to back it up
App sourceA git repository per app under /home/dokku, plus your own cloneKeep your remote repository; git push restores the app
Config variables and plugin settings/var/lib/dokku/configdokku config:export, and the directory archive
Domains, nginx config and certificates/home/dokku/<app> and /var/lib/dokku/config; certificates in /home/dokku/<app>/tlsThe directory archive, with domains:report as a readable list
Uploads and other files apps keepStorage mounts, by convention /var/lib/dokku/data/storage/<app>Copy the directory
DatabasesPlugin services in /var/lib/dokku/servicesThe plugin's export or backup command
Plugins/var/lib/dokku/pluginsThe directory archive

Storage only lands under /var/lib/dokku/data/storage if you mount it from there, as Dokku's backup guide recommends, so one archive covers every app. dokku storage:list <app> shows an app's mounts.

Export the databases

Each official datastore plugin dumps its service to standard output. For a Postgres service named lollipop (see dokku postgres:list):

Terminal
dokku postgres:export lollipop > lollipop.dump

The plugin runs pg_dump -Fc --no-acl --no-owner inside the service container, so the dump is PostgreSQL's compressed custom format, made by the server's own version, without owners or grants. postgres:import loads it with pg_restore --clean --if-exists, which replaces existing objects:

Terminal
dokku postgres:import lollipop < lollipop.dump

The MySQL, MariaDB, MongoDB and Redis plugins have the same export and import commands. pg_dump reads a consistent snapshot while the database runs; a copy of the files under /var/lib/dokku/services may contain partially written data, as Dokku's backup guide warns. More in the pg_dump guide and restoring a dump.

Send database backups to S3 on a schedule

The plugin can also upload backups itself, to AWS S3 or any S3-compatible storage. Give it a key for the bucket first:

Terminal
dokku postgres:backup-auth lollipop <access-key-id> <secret-access-key> <region> s3v4 <endpoint-url>

On AWS itself you can stop after the region. Each call replaces every stored setting, so repeat them all when you change one, and use a key limited to the backup bucket, as in the S3 bucket guide. Then set a passphrase, run a backup and schedule it:

Terminal
dokku postgres:backup-set-encryption lollipop <passphrase>
Terminal
dokku postgres:backup lollipop my-dokku-backups
Terminal
dokku postgres:backup-schedule lollipop "0 3 * * *" my-dokku-backups
  • Each backup dumps the database to a temporary file on the host, so leave room for one dump, then uploads postgres-lollipop-<timestamp>.tgz with the dump inside as backup/export. The timestamp is UTC, such as 2026-10-04-03-00-01.
  • With a passphrase set, the upload is encrypted with GPG (AES256) and ends in .tgz.gpg. Keep the passphrase off the server: without it the backups cannot be read.
  • The bucket name may end in a path, such as my-dokku-backups/postgres, to upload under a prefix.
  • The schedule is a cron expression or a word such as @daily. It goes into the dokku user's crontab, output is appended to /var/log/dokku/postgres.log, and dokku postgres:backup-schedule-cat lollipop prints the line.

The plugin does not delete old backups. Add a lifecycle rule to the bucket so they expire, as in choosing a retention policy.

dokku cron:list --global lists scheduled backups. Dokku writes that crontab only when the global scheduler or at least one app uses the docker-local scheduler, so a k3s-only host runs no scheduled backups.

Save app config, domains and mounts

config:export prints an app's variables. The envfile format writes one dotenv line per variable, which config:import (Dokku 0.37 and later) reads back; on older versions, set them again with config:set. --global exports the variables every app inherits:

Terminal
dokku config:export --format envfile shop > shop.env
Terminal
dokku config:export --format envfile --global > global.env

These files hold every secret the apps use: keep them readable by root only and encrypt them before they leave the server. Record each app's domains and mounts too:

Terminal
dokku domains:report shop --domains-app-vhosts
Terminal
dokku storage:list shop --format json

dokku certs:show shop crt and dokku certs:show shop key print a certificate you added yourself. Certificates from the Let's Encrypt plugin need no backup: it issues new ones on the new server.

Archive Dokku's own directories

Dokku's backup guide archives five directories, at a time when no Dokku command or deploy is running:

Terminal
sudo tar -czf dokku-files.tar.gz -C / home/dokku var/lib/dokku/config var/lib/dokku/data var/lib/dokku/services var/lib/dokku/plugins

That covers the app repositories, every plugin's settings, the storage under /var/lib/dokku/data, each service's configuration and password, and the installed plugins. It also copies database files while they run, which can be caught mid-write, so restore databases from the dumps. Because those files change during the copy, GNU tar may exit with status 1, which means some files changed while being archived.

A nightly backup script

/usr/local/bin/dokku-backup.sh
#!/usr/bin/env bash
set -euo pipefail
umask 077

BACKUP_DIR="/var/backups/dokku"
KEEP_DAYS=14
SERVICES="lollipop"
OUT="$BACKUP_DIR/$(date +%Y-%m-%d_%H%M)"

mkdir -p "$OUT"

dokku config:export --format envfile --global > "$OUT/global.env"
for APP in $(dokku --quiet apps:list); do
  dokku config:export --format envfile "$APP" > "$OUT/$APP.env"
  dokku domains:report "$APP" --domains-app-vhosts > "$OUT/$APP.domains"
  dokku storage:list "$APP" --format json > "$OUT/$APP.storage.json"
done

for SVC in $SERVICES; do
  dokku postgres:export "$SVC" > "$OUT/$SVC.dump"
  head -c 5 "$OUT/$SVC.dump" | grep -q PGDMP
done

tar -czf "$OUT/dokku-files.tar.gz" -C / home/dokku var/lib/dokku/config \
  var/lib/dokku/data var/lib/dokku/services var/lib/dokku/plugins || [ "$?" -eq 1 ]

find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$KEEP_DAYS" -exec rm -rf {} +
Terminal
sudo chmod 700 /usr/local/bin/dokku-backup.sh
/etc/cron.d/dokku-backup
30 2 * * * root /usr/local/bin/dokku-backup.sh >> /var/log/dokku-backup.log 2>&1
  • List your Postgres services in SERVICES. A custom-format dump starts with the bytes PGDMP, so the head check stops the script on an empty or failed export.
  • umask 077 makes every file readable by root only. dokku --quiet apps:list prints app names without the header.
  • || [ "$?" -eq 1 ] accepts tar's status 1 for files that changed, and still stops on real errors (status 2).
  • The find line deletes dated folders older than 14 days.

Schedule it away from deploys and the plugin's own backups, and copy /var/backups/dokku off the server, for example with rclone. More in the cron guide.

Restore onto a new server

Install the same Dokku version as the old server (dokku version prints it) on Ubuntu or Debian, with that version in the bootstrap URL and DOKKU_TAG:

Terminal
wget -NP . https://dokku.com/install/v0.38.31/bootstrap.sh
Terminal
sudo DOKKU_TAG=v0.38.31 bash bootstrap.sh

Route 1, the whole archive. Extract it over the fresh install, rerun the plugins' install steps, which a restore does not trigger, start each service, load its dump and rebuild the apps:

Terminal
sudo tar -xzf dokku-files.tar.gz -C /
Terminal
sudo dokku plugin:install
Terminal
sudo dokku plugin:install-dependencies
Terminal
dokku postgres:start lollipop
Terminal
dokku postgres:import lollipop < lollipop.dump
Terminal
dokku ps:rebuild --all

On a server with a different CPU architecture, run rm -rf /home/dokku/.basher straight after extracting. Recreate the Docker networks that dokku network:report lists with dokku network:create, and redeploy apps that came from an image (git:from-image) with the command you first used, since ps:rebuild may fail for them. Then check dokku cron:list --global for the scheduled backups.

Route 2, rebuild from the exports. Use this to move to a newer Dokku or start clean. Add your SSH key, create the app and the service, load the dump and link them:

Terminal
cat ~/.ssh/authorized_keys | sudo dokku ssh-keys:add admin
Terminal
dokku apps:create shop
Terminal
dokku postgres:create lollipop
Terminal
dokku postgres:import lollipop < lollipop.dump
Terminal
dokku postgres:link lollipop shop --no-restart

Create the service at the old server's Postgres version or newer: dokku postgres:info lollipop --version shows it, and postgres:create takes --image-version. The new service has a new password and the link sets a new DATABASE_URL, so leave the old one out of the config you import:

Terminal
grep -v '^DATABASE_URL=' shop.env > shop-nodb.env
Terminal
dokku config:import --no-restart shop shop-nodb.env

config:import prints each variable it sets, values included, so run it where the output is not logged. Then restore the domain and the storage mount, taking just that app's files from the archive:

Terminal
dokku domains:set shop shop.example.com
Terminal
sudo tar -xzf dokku-files.tar.gz -C / --numeric-owner var/lib/dokku/data/storage/shop
Terminal
dokku storage:mount shop /var/lib/dokku/data/storage/shop:/app/storage

--numeric-owner keeps the owners' numeric IDs, such as the 32767 Dokku uses for Herokuish apps. From your machine, point the remote at the new server and push to the branch Dokku deploys, master unless you set deploy-branch:

Terminal
git remote set-url dokku [email protected]:shop
Terminal
git push dokku main:master

Once DNS points at the new server, install the Let's Encrypt plugin, set an address with dokku letsencrypt:set --global email [email protected], run dokku letsencrypt:enable shop and schedule renewals with dokku letsencrypt:cron-job --add. For a certificate you added yourself, use dokku certs:add shop server.crt server.key. Moving providers too? See migrating to a new provider.

Restore a backup from S3

Download the object with any S3 client or your provider's console. Decrypt it if you set a passphrase, extract it, and import the dump inside:

Terminal
gpg --output backup.tgz --decrypt postgres-lollipop-2026-10-04-03-00-01.tgz.gpg
Terminal
tar -xzf backup.tgz
Terminal
dokku postgres:import lollipop < backup/export

gpg asks for the passphrase; in a script, add --batch --pinentry-mode loopback --passphrase-file <file>. Do not pipe the .tgz itself into postgres:import: pg_restore needs the backup/export file inside it.

Test the restore

Load the newest dump into a scratch service beside the real one, count rows in a table you know, and remove it:

Terminal
dokku postgres:create restoretest
Terminal
dokku postgres:import restoretest < /var/backups/dokku/2026-10-04_0230/lollipop.dump
Terminal
echo 'SELECT count(*) FROM users;' | dokku postgres:connect restoretest
Terminal
dokku postgres:destroy restoretest --force

Without a terminal, postgres:connect reads SQL from standard input. Time the import: that is the database part of your recovery time. See testing a restore.

Common errors

ErrorFix
Missing AWS_ACCESS_KEY_ID fileRun postgres:backup-auth for the service, or pass --use-iam on EC2 with an instance role.
Service container is not runningStart it with dokku postgres:start lollipop; backups need a running service.
input file does not appear to be a valid tar archiveThe .tgz from S3 went straight into postgres:import. Extract it and import backup/export.
input file appears to be a text format dump. Please use psql.The file is plain SQL. Load it with dokku postgres:connect lollipop < dump.sql.
pg_restore reports an unsupported version in the file headerThe dump came from a newer Postgres than the service. Create the service at that version or newer.
password authentication failed for user "postgres" after a rebuildThe imported config kept the old DATABASE_URL. Run dokku postgres:unlink lollipop shop, then dokku config:unset shop DATABASE_URL, then link again.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! on git pushThe host name now points at the new server. Remove the old key with ssh-keygen -R <host> and push again.
Permission denied (publickey) on git pushYour key is not on the new server. Add it with dokku ssh-keys:add.

Frequently asked questions

Where does Dokku store app data?
Config and plugin settings in /var/lib/dokku/config, app repositories and certificates under /home/dokku, files in storage mounts wherever you mounted them (by convention one folder per app under /var/lib/dokku/data/storage), and datastore services in /var/lib/dokku/services.
How do I back up a Dokku Postgres database?
Run dokku postgres:export with the service name and redirect it to a file for a local dump, or set up postgres:backup-auth and postgres:backup-schedule to upload backups to S3 on a schedule, encrypted if you set a passphrase.
Does the Dokku Postgres plugin delete old S3 backups?
No. Each backup gets a timestamped name and stays until you delete it, so add a lifecycle rule to the bucket.
How do I move Dokku apps to a new server?
Install the same Dokku version, then either extract an archive of Dokku's directories and rebuild the apps, or recreate each app from its config export and database dump and git push the code.
Can I restore a Dokku backup on a server with a different CPU architecture?
Yes. Remove /home/dokku/.basher straight after extracting, as Dokku's backup guide says, because it does not carry across architectures, then rebuild the apps.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: