Docs navigation

Platform

Backups & restore

Daily database dumps and volume snapshots, retention against your plan, downloads, and restores from an instance's own backups.

An instance has up to two backup streams. Each is a plug whose runs record their files as artifacts:

Stream Plug Artifact
Database backup PostgreSQL: one pg_dump custom-format file (.dump). MySQL (Ghost): one plain mysqldump file (.sql).
Volumes snapshot_volumes One gzipped tar (.tar.gz) per data volume.

An instance with a database has the dump; one with data volumes has the snapshot. A custom app's dump covers the database it declares and its snapshot the volumes; with neither declared, it has no backup stream. GET /api/v1/instances/{slug}/plugs lists what an instance supports.

Schedule

Each stream gets a daily routine when the instance is created, at 04:00 UTC plus a minute derived from the instance. The volume snapshot runs on a different minute from the database dump. Change the cadence or disable the routine on the Actions tab, or with PATCH /api/v1/routines/{id}. A second routine on the same plug (an hourly dump beside the daily one) needs POST /api/v1/routines; see routines. Run now on the same tab, or POST /api/v1/instances/{slug}/plugs/{plug}/runs (write_plugs), takes one immediately.

The Overview → Composition card shows each container and volume with what backs it up, for example pg_dump · daily · off-host. A row with no successful backup in 48 hours reads stale; one that has never had one reads never run. Lunary's operators are alerted when a backup run fails and when a stream goes 48 hours without a successful run.

What a backup contains

  • Database dump: taken inside the database container with the container's own credentials, then checked before upload (the custom-format header for PostgreSQL, the completion line for MySQL). A dump that fails the check fails the run.
  • Volume snapshot: read-only and crash-consistent. Complete files are captured; a file being written during the snapshot may be partial. An embedded database kept inside a volume, such as SQLite, is captured as it would be after a power loss.

Backups are stored in object storage in Canada, off the server they came from.

Downloading

Dashboard: the Activity tab, then the run, then Download beside each artifact. API: GET /api/v1/runs/{id}/artifacts/download?key=… (read_plugs) returns a presigned url valid for 15 minutes, with its expires_at; pass the artifact's key from GET /api/v1/runs/{id} as given. MCP: get_artifact_url.

The files are standard formats: a .dump restores with pg_restore, a .sql with the mysql client, a volume archive with tar -xzf.

Retention

Your plan caps the backup storage you retain: 100 GiB on Essential, 500 GiB on Pro, 1,024 GiB on Enterprise. The dashboard's Backups tile shows the reading against the cap. Storage over the cap is reclaimed oldest first, across all your instances, after each backup and in a daily sweep. Each instance's newest database dump and newest volume archive are kept whatever the cap. A pruned artifact shows pruned_at in its run detail and can no longer be downloaded or restored from.

When those newest backups alone reach the cap, new backups are refused. Running the plug directly (Run on the Actions tab, POST /api/v1/instances/{slug}/plugs/{plug}/runs, or MCP trigger_plug) is refused up front, with 422 over_budget over the API. A routine's run, scheduled or started by hand (Run now, POST /api/v1/routines/{id}/runs), ends unauthorized and pauses the routine (paused_reason: "over_budget"). Resume it once the cap is raised. Dry runs are exempt.

Restoring

Two plugs restore an instance from its own backups:

  • restore replaces the database with one of the instance's dumps, named by key: the dump's artifact key without the leading <org>/.
  • restore_volumes replaces every volume in one of the instance's snapshots, named by snapshot_run: the id of the snapshot run. Files written since the snapshot are lost.

The instance's app containers stop for the restore and restart afterwards. Both plugs take dry_run, which downloads and checks the backup and changes nothing. A passing dry run means the backup is intact; for restore it also means the database is reachable with its own credentials. It does not replay the restore.

Dashboard: the Actions tab, Run on the plug, then pick the backup from the list; tick Dry run to check it first. API (write_plugs):

curl -X POST https://lunary.cloud/api/v1/instances/nocobase/plugs/restore/runs \
  -H "Authorization: Bearer lc_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"params": {"key": "nocobase/backup/20260731T120000Z.dump"}, "dry_run": true}'

MCP: trigger_plug. A key or run that is not one of the instance's own backups is refused (422 unknown_artifact). To copy another instance's database, use a data sync.

After an instance is deleted

Deleting an instance keeps its data on the server for 30 days, and provisioning the same app under the same slug in that window starts it on that data. DELETE /api/v1/instances/{slug}?remove_volumes=true deletes the data at teardown instead. Either way the instance's backups stay downloadable from its runs (GET /api/v1/runs?instance_slug={slug}), pruned as above, until 30 days after the teardown, when they are deleted.

Reading with an agent? This page as markdown, or the whole docs as llms.txt.