# Backups & restore
URL: https://synquic.tech/docs/guides/backups-restore.md

Back up your databases and service data on a schedule, keep the copies you need, and restore one with the step-by-step wizard, from the panel or the terminal.

import { Step, Steps } from 'fumadocs-ui/components/steps';

A backup is a spare copy of your data kept somewhere else, so that if today's copy is lost or broken you can
go back to a good one. Synq does three jobs here:

- it copies your data to a storage place you choose, a **destination**;
- it does that on a schedule, following a **policy**, and prunes old copies;
- when you need it, it puts a copy back with a **restore**.

This page covers databases and services. For a single named volume, see
[Volumes & volume backups](/docs/guides/volumes-backups).

<Callout title="What you need first">
- A project with at least one **service** that holds data, such as a database added from the catalog or a
  Compose `db` service.
- A backup destination: an S3-compatible bucket, an SFTP or FTP host, Dropbox, Google Drive, one of your
  servers, or a local folder. Add it once under **Backups** in the sidebar; see
  [Backup destinations](/docs/dashboard/backups).
</Callout>

## What gets backed up

The **Backup method** of a policy decides what one backup contains. **Auto** is the default and does the
right thing for most services.

| Method | What it captures | Restore |
| --- | --- | --- |
| **Auto** | Picks per service: a database dump for a recognised database image, otherwise a snapshot of the service's volumes. The editor tells you what it detected and the method Auto will use (for PostgreSQL, `pg_dump`). | As for the method it picked. |
| **Database** | A logical dump taken inside the running service: `pg_dump` (PostgreSQL, PostGIS), `mysqldump` (MySQL, MariaDB, Percona), `mongodump` (MongoDB) or an RDB snapshot (Redis, Valkey). Consistent as of the moment it starts, and restorable into a newer version. | Written back through the running database. |
| **Volume** | A `tar` of the service's volumes and bind mounts, taken live. | The service is stopped, the volumes are replaced, the service starts again. |
| **Files** | Only the folders you name, archived from inside the running service. | Extracted back into those folders. |
| **Custom** | The output of your own **Dump command**, run inside the container. | Fed to your **Restore command** on standard input. |

A database is recognised by its image name: `postgres`, `postgis/postgis`, `mysql`, `mariadb`,
`percona/percona-server`, `mongo`, `percona/percona-server-mongodb`, `redis`, `redis/...` or `valkey/valkey`.
For a custom-built image, choose **Database** and pick the **Engine** yourself.

## Step 1: set a backup policy

A **policy** says: back this up, to that destination, on this schedule, and keep this many copies.

<Steps>

<Step>

### Open the project's Backup tab

Open the project and choose **Backup**. You see **Backup destinations** (with **Manage** to add more),
**Services + policies**, and **Recent backups**.

</Step>

<Step>

### Choose what the policy covers

- **Project-level policy**: one policy that backs up **every** service of the project. Press **Create
  policy** on its row.
- **A single service**: press **Create policy** on that service's row. You can also do this from the service
  itself: **Apps & Services** → the service → **Backup**.

If **Create policy** is greyed out, there's no destination yet (**Add a destination first**).

A single app with no services has no policy rows here. Keep its data in a named volume and back that up
with a [volume backup](/docs/guides/volumes-backups#back-up-a-volume).

</Step>

<Step>

### Fill in the policy

- **Destination**: where the copies go. **Add new destination** opens the destination form without leaving
  the editor.
- **Schedule**: **Hourly**, **Daily (03:17)**, **Weekly (Sun 03:17)**, **Monthly (1st 03:17)**, **Manual only**,
  or your own five-field cron expression (for example `0 2 * * *` for 02:00 every day). **Manual only** never
  runs on its own.
- **Retain count**: keep the N most recent successful backups. **Retain days**: drop backups older than N
  days. Use either, both, or neither (**Unlimited**).
- **Backup method**: see the table above. Each method shows its own options:
  - **Database**: **Engine**, to force a dump tool.
  - **Files**: **Folders to archive** (one absolute path per line) and **Replace on restore** (empty the
    folder before extracting; refused for system and top-level folders).
  - **Volume**: **Volumes to include** (blank means all), **Pause while copying** (freezes the service for a
    consistent copy; it's unavailable until the copy finishes) and **Exclude** patterns such as `./cache` or
    `*.sock`.
  - **Custom**: **Dump command** and **Restore command**. The restore command is required; without it a
    backup could never be restored.
  - **Compression**: **zstd** (smallest), **gzip** (works without any download, for air-gapped servers) or
    **none**. **Auto** uses what the service has.

</Step>

<Step>

### Optional: Advanced options

Open **Advanced options** for:

- **Pre-deploy trigger**: also back up automatically right before each new deployment goes live. Your
  safety net for bad deploys and migrations.
- **Webhook trigger**: creates a private token so an outside scheduler or CI job can start this backup (see
  [Trigger a backup from outside](#trigger-a-backup-from-outside)). **Rotate token** replaces it.
- **Pre-hook (runs before snapshot)**: a shell command run first. It can produce what gets backed up, for
  example `pg_dump > /tmp/dump/db.sql` followed by a **Files** backup of `/tmp/dump`. If it fails, the run is
  aborted, so a stale copy is never saved as if it were fresh.
- **Post-hook (runs after upload)**: cleanup. Its failure is logged but doesn't fail the run.

Check the **Summary**, keep **Policy enabled** ticked, and press **Create policy**.

</Step>

</Steps>

Retention runs after each successful backup, separately for each service in a project-level policy.
Protected copies are skipped (Step 3). If cleanup fails, the records are kept and retried; the new backup
is still valid. The **Backup retention prune** system job is a fallback; new installations run it every hour
at minute 29, and you can change it under **Jobs**.

## Step 2: run a backup now

You don't have to wait for the schedule. Any service or project policy row has **Backup now**. It asks for
an optional **Name**, for example `Before the v2 migration`, so you can find this copy later; leave it empty
for an unnamed run. A name is up to 80 characters of letters (any language), digits, spaces and
`- _ . : ( ) # @ + , /`. Extra spaces are collapsed, and a name with any other character is refused rather
than changed. Press **Run backup**. A **Live backup** card appears with real-time progress; the backup runs on
the server, so it keeps going if you close the tab. When it finishes, the run appears in **Recent backups**
(the 25 most recent runs of the project) with its name, status and size. The name is also written into the
backup's `manifest.json` at the destination.

Over the API, send the name in the body: `POST /api/backup-policies/<policyId>/run` with
`{"name": "Before the v2 migration"}`. The body is optional.

## Step 3: protect a copy from pruning

Retention deletes old copies automatically. To keep one no matter what (say, the last good state before a
big change), press the lock icon on the run in **Recent backups** (**Protect from retention**). It gets a
**Protected** badge and pruning skips it. Press it again (**Allow retention to prune**) to release it.

## Step 4: restore a backup

A restore puts a saved copy back in place of the current data. It **overwrites** what's there now, so Synq
walks you through it in steps.

<Steps>

<Step>

### Start

In **Recent backups**, find a successful run and press its **Restore** icon (**Restore from this backup**).
The **Restore backup** wizard opens. Its steps are **Review**, **Prepare**, **Confirm**, **Apply** and **Done**.

</Step>

<Step>

### Review

The first screen shows the backup's date, size and number of artifacts, and warns **This will overwrite
current data.** Leave **Protect the latest backup from retention prune** ticked (the default): it keeps the
most recent successful backup safe from automatic deletion, so you can go back to "right before this
restore". Press **Continue → Prepare**.

</Step>

<Step>

### Prepare

Synq checks that the backup's files exist at the destination and are intact (**Verifying backup...**).
Nothing is changed yet; the service keeps running. When it's done: **Backup verified, ready to apply.**

</Step>

<Step>

### Confirm

Type the **service name** exactly (for a volume backup, the volume name). This is the "are you really sure?"
gate, because the next step replaces the current data. Press **Apply restore**.

</Step>

<Step>

### Apply

For a volume or files backup, the service stops briefly while the data is swapped, then starts again. A
database dump is written back through the running database (Redis applies it after a restart). Keep the window
open until it says **Restore complete.**

</Step>

</Steps>

<Callout title="Take a fresh backup first" type="info">
Synq does **not** back up the current state for you before a restore. The protect tick only shields a
backup that already exists. If you might want today's data back, press **Backup now** before you start the
restore.
</Callout>

### Cancelling a restore

- Before anything is written, **Cancel** (on **Review**) or **Cancel restore** (while preparing) leaves
  your data exactly as it was.
- During **Apply**, **Abort restore** asks for confirmation. Once data is being written, aborting leaves the
  target with partial data and the service is deliberately left **stopped**. Run the same restore (or a newer
  one) to completion before starting it again.

## Trigger a backup from outside

With **Webhook trigger** on, a policy has a token. Start the backup from anywhere that can make an HTTPS
request, with the token as a bearer token:

```bash
curl -X POST -H "Authorization: Bearer $BACKUP_WEBHOOK_TOKEN" \
  "$SYNQ_URL/api/webhooks/backup"
```

The token is the last part of the URL the policy editor shows. The response contains the new `runId`.
`SYNQ_URL` is your panel address (for example `https://app.example.com`).

## CLI

The base command is `synq backup`, with the groups `destination`, `policy`, `run` and `restore`.

```bash
# 1. Add a destination (S3-compatible example) and verify it
synq backup destination create \
  --name "Production R2" --kind s3_compatible \
  --endpoint https://<account>.r2.cloudflarestorage.com \
  --bucket my-backups --region auto \
  --access-key-id <id> --secret-access-key <key>

synq backup destination list
synq backup destination preflight <destinationId>

# 2. A daily policy for one service, keeping 7 copies, also before each deploy
synq backup policy create \
  --project <projectId> --destination <destinationId> \
  --service <serviceId> --cron "0 3 * * *" \
  --retain-count 7 --pre-deploy

# ...or one project-level policy for every service (leave out --service)
synq backup policy create --project <projectId> --destination <destinationId> --cron "0 3 * * *"

# 3. Run it now and watch it finish
synq backup policy run <policyId> --follow

# 4. See runs, and protect one from pruning
synq backup run list --project <projectId>
synq backup run protect <runId>            # or --release to unprotect

# 5. Restore, deliberately in two steps
synq backup run restore <runId>            # prepares it, prints a restoreId and a token
synq backup restore apply <restoreId> --token <confirmationToken> --follow
```

`--payload-kind` chooses the method (`auto`, `pg_dump`, `mysql_dump`, `mongo_dump`, `redis_rdb`, `volume`,
`path`, `custom_command`), and `--payload-config` takes its options as JSON.

<Callout title="Restore is deliberately two steps" type="info">
`backup run restore` only prepares the restore and hands you a confirmation token; nothing changes yet.
`backup restore apply` with that token swaps the data in. This mirrors the "type the name" gate in the panel.
</Callout>

## Troubleshooting

<Callout title="A destination won't verify" type="error">
Press **Verify connection** on the **Backups** page and read the reason. The usual causes are a wrong key or
password, a bucket or folder that doesn't exist, or a host or port that isn't reachable.
</Callout>

<Callout title="Create policy is greyed out" type="warn">
There's no destination yet. Add and verify one under **Backups**, then come back to the project's **Backup**
tab.
</Callout>

<Callout title="Auto made a volume snapshot of my database" type="warn">
Auto uses a dump only when it recognises the image and the service has a running container. For a custom
image, choose **Database** and set the **Engine**. Check the **Summary** before saving.
</Callout>

<Callout title="A backup run failed" type="error">
Open it in **Recent backups**; a failed run shows its error. A failing **pre-hook** aborts the run on purpose
(for example a `pg_dump` that can't connect), so the copy is never half-made.
</Callout>

<Callout title="I need to undo a restore" type="warn">
There is no one-click undo. Restore again from the backup taken before the restore (the one you protected,
or the one **Backup now** made).
</Callout>

## Related guides

<Cards>
  <Card title="Backup destinations" href="/docs/dashboard/backups" description="Add and verify the S3 bucket, SFTP or FTP host, Dropbox, Google Drive, server or folder backups go to." />
  <Card title="Volumes & volume backups" href="/docs/guides/volumes-backups" description="Back up and restore one named volume at a time." />
  <Card title="Environment variables" href="/docs/guides/environment-variables" description="Give backup hooks the credentials they need." />
</Cards>
