Skip to content
Pages

Platform

Backups and restore

What Leitagu backs up, when and where the copies are kept, how to restore the database, how to take a backup by hand, and the known gaps.

Updated 23 October 2026View as Markdown

Leitagu's only data that needs a backup is its PostgreSQL database. Coolify dumps it every six hours, keeps a week of copies on the server and uploads every copy to Cloudflare R2 in the EU, where they are kept for 90 days. Coolify's own settings are backed up once a day to the same place. A restore from R2 was tested on 2026-10-23. This page describes the setup as of that date, how to restore, and what is still missing.

What is backed up

What Where it lives Backed up?
The Leitagu database (accounts, stores, products, rooms, chats, events, CRM, audit log) Coolify resource leitagu-postgres, PostgreSQL 16 Yes, every 6 hours
Coolify's own database: the apps, their environment variables and domains The Coolify server, servers.leitagu.ee Yes, daily
App code and images ghcr.io/leitagu/leitagu-<app>, built from this repository Not needed: rebuilt from git (see Deploys)
Files None: the apps keep no files on disk, and product images are links to the stores' own image addresses Nothing to back up

The knowledge hub has no database; its pages are the Markdown files in the repository.

Database backups

  • Schedule. Every 6 hours, at 00:00, 06:00, 12:00 and 18:00 Estonian time (Europe/Tallinn).
  • Format. pg_dump custom format, which pg_restore reads.
  • On the server. Coolify keeps the last 7 copies, at most 14 days old, in /data/coolify/backups/databases/root-team-0/leitagu-postgres-<uuid>/ on the Coolify droplet.
  • Off-site. Every copy is uploaded to Cloudflare R2, bucket leitagu-backups (EU jurisdiction), under data/coolify/backups/databases/root-team-0/leitagu-postgres-<uuid>/. Copies there are kept for 90 days.

Coolify's own backup

  • What. Coolify's database: which apps exist, their environment variables (including the secrets the apps use) and their domains. With it, the whole setup can be rebuilt on a new server.
  • Schedule. Daily at 02:30 Estonian time, uploaded to the same R2 bucket under data/coolify/backups/coolify/, kept for 90 days.
  • The key. Restoring it needs Coolify's APP_KEY, which is in /data/coolify/source/.env on the server. Keep a copy of it off the server, in the team's password manager: if the server is lost, the backup can't be read without it.

Restoring the database

From the Coolify interface

For restoring onto the running database from a copy Coolify still lists:

  1. In Coolify (servers.leitagu.ee), open the leitagu-postgres database and its Backups tab.
  2. Choose the copy and restore it, following Coolify's prompts.
  3. Restart the web, admin, partner and CRM apps so they reload their in-memory product cache, then run the checks in DEPLOYMENT.md ("Checks after each deploy").

By hand, from R2

For a copy older than the server's 14 days, a new server, or a test restore into a scratch database:

  1. Download the dump from R2 with any S3 client (for example the AWS CLI or rclone), pointed at the R2 S3 endpoint of the Leitagu Cloudflare account. Use an R2 access key with read access to leitagu-backups; the endpoint and keys are in the Cloudflare dashboard and the password manager, never in this repository. The newest file under data/coolify/backups/databases/root-team-0/leitagu-postgres-<uuid>/ is the latest backup.

  2. Start a target database if you don't restore into an existing one, for example a scratch postgres:16 container.

  3. Restore with pg_restore (version 16 or newer), connecting with the target's connection string:

    pg_restore --no-owner --no-privileges --dbname "$TARGET_DATABASE_URL" leitagu-backup.dmp
    

    Add --clean --if-exists to replace the tables of a database that already has them.

  4. Check it. Compare the list of tables and the row counts of the main tables (users, stores, products, schema_migrations) with the source or with what you expect.

  5. Point the apps at it only when it is meant to replace production: change DATABASE_URL in Coolify for the web, admin, partner and CRM apps and redeploy them. The apps run any migrations the restored database is missing when they start.

Last test: 2026-10-23. A dump was downloaded from R2 and restored with pg_restore into a scratch postgres:16 container; the tables and their row counts matched the live database.

Taking a backup by hand

Before a risky change (a big migration, a bulk import, moving servers): in Coolify, open Database → Backups for leitagu-postgres and press Backup now. The copy goes to the server folder and to R2 like the scheduled ones.

Deploys

The servers keep no code, so nothing on them needs a backup to redeploy:

  • A merge to main runs the images workflow (.github/workflows/images.yml) in GitHub Actions. It builds an image for each app the change touches, starts it against a throwaway Postgres to check it, and pushes it to ghcr.io/leitagu/leitagu-<app> with the tags latest and sha-<short commit>.
  • Its deploy job then calls Coolify's deploy API (servers.leitagu.ee) for each app whose image was pushed, one at a time in the order web, admin, crm, partner, docs, and waits for each deployment to finish. Web goes first because it runs new migrations.
  • To roll back, point the app at an older sha- tag in Coolify. Details are in DEPLOYMENT.md ("Option C: container images").

Known gaps

  • No alert when a backup fails. No notification channel is set up in Coolify yet, so a failing backup only shows in Coolify's interface. Check the Backups tab now and then until alerts are on.
  • Droplet backups not confirmed. It is not confirmed whether DigitalOcean's own backups or snapshots are turned on for the Coolify droplet. Losing the droplet would not lose data (the database copies and Coolify's backup are in R2), but rebuilding the server would take longer.
  • Same channel for scheduled jobs. Without notifications, a failing scheduled job is also only visible in Coolify.
  • Up to six hours of data. A restore brings the database back to the last dump, so changes since then are lost. There is no point-in-time recovery.