2026-09-03

Backing Up Postgres on Coolify, Then Proving It

A backup you have never restored is not a backup, it is a file. Here is how to make it one anyway.

A backup you have never restored is not a backup. A successful local dump can still leave you without the offsite copy you meant to keep. I run Coolify on a VPS, and a review of one client project's repo found zero backup configuration despite a signed hosting agreement promising daily backups. A backup promise needs checking at each step, right through to bringing the database back.

What Coolify gives you natively

Coolify documents scheduled Postgres backups for database resources and service databases. You can set a schedule and add S3-compatible storage for an offsite copy. The local dump and the upload have separate results, so check the execution and the object in the bucket before treating either as done.

The issue reports give you reasons to verify that path. One compose MongoDB user reported scheduled jobs that never reached the queue; another compose MariaDB user reported that Backup Now produced no execution or visible error. A separate report, about Coolify's own database, showed a successful local dump while the S3 upload failed and the bucket stayed empty. Those are other people's reports of separate failures, not a combined compose Postgres failure I observed on my VPS.

Read the separate Coolify issue reports

A dump file existing is not the same claim as data coming back

Even when the scheduled job genuinely runs and genuinely uploads something, that only proves a file landed in a bucket. It doesn't prove the file is a complete, uncorrupted dump of a database in a state you'd actually want back. It doesn't prove you still have the credentials to reach that bucket six months from now. It doesn't prove the dump format is one you remember how to restore under pressure, at 2am, with a client asking why the site is down. Every one of those gaps is invisible from a dashboard that just shows you a timestamp and a filename. The only way to close any of them is to actually restore the file into a real database and look at what comes out.

Taking a manual pg_dump from a Coolify-managed Postgres

Whatever scheduled backup you've got running, it's worth knowing how to take a dump by hand, because that's also what you reach for right before a risky migration or before decommissioning something. A Coolify-managed Postgres runs as its own container on the Coolify docker network, reachable by other containers using its container name (the service's UUID), not by an IP that can change on restart. That matters less for a manual dump than it does for wiring an app's DATABASE_URL, but it's the same container you're about to exec into.

The simplest path is to run pg_dump from inside the Postgres container itself, over SSH to the VPS, rather than trying to reach the database from your own machine. Your workstation may not even have a pg_dump binary at all: mine doesn't, and pg_dump has to be the same version or newer than the server it's dumping, so reaching for whatever happens to be installed locally is asking for a version mismatch anyway.

ssh your-vps
docker exec <postgres-container-name> \
  pg_dump -U <db-user> -d <db-name> -F c -f /tmp/backup.dump
docker cp <postgres-container-name>:/tmp/backup.dump ./backup.dump

The -F c flag takes the custom format instead of plain SQL. It's compressed, and it restores with pg_restore instead of piping a .sql file into psql, which also gives you options like restoring a single table later instead of the whole database. If you'd rather have a plain-text dump you can grep or diff, drop -F c and pipe pg_dump's output straight to a .sql file instead. Either way, that dump now exists as a file inside the container's filesystem and then, after the docker cp, sitting on the VPS itself. Neither of those locations is a backup yet.

Getting the dump off the host

A backup sitting on the same VPS as the database it was taken from dies with the VPS. If the disk fails, if the box gets compromised, if you fat-finger a docker command against the wrong volume, the dump goes with everything else. It has to leave the machine before it counts as protection against anything that can happen to the machine. Two workable options: copy it to a machine you control, or push it to object storage that isn't Coolify's own host.

scp your-vps:/root/backup.dump ./backups/backup-$(date +%F).dump

For anything you actually want to rely on, automate that trip off-box rather than remembering to scp a file by hand each time, and keep more than one copy in more than one place. Tools like rclone can push straight from the VPS to an S3-compatible bucket on a schedule, which is the same shape of thing Coolify's own backup feature is meant to do for a managed Postgres resource, just under your own control and something you've actually watched succeed.

The restore drill everyone skips

This is the step that turns a dump file into a backup, and it's the one that's easy to defer indefinitely because nothing is currently on fire. Spin up a throwaway Postgres, container or otherwise, that isn't your production database, and restore into it:

docker run --rm -d --name pg-restore-test -e POSTGRES_PASSWORD=test \
  -p 15433:5432 postgres:17-alpine

docker cp ./backup.dump pg-restore-test:/tmp/backup.dump
docker exec pg-restore-test createdb -U postgres restore_check
docker exec pg-restore-test pg_restore -U postgres -d restore_check /tmp/backup.dump

Then actually look at what you restored, not just whether the command exited without an error:

None of this is complicated once it's written down, which is exactly why it's the kind of task that gets skipped. A scheduled dump is one cron entry or one Coolify toggle. A restore drill takes ten minutes and produces nothing you can point to afterwards except confidence, right up until the day it produces everything. Do the drill before you need it, not while you need it.

Topics

Configuration reference: listmonk, Metabase, Miniflux, Vikunja. Ports, environment variables, volumes and the release timeline, each row linked to its upstream source.

Still stuck? Send it to Emsden Studio and get a straight answer on what it is and what fixing it costs.

More guides

All guides