---
title: "Backups in depth"
description: "Kinds, source types, schedules and retention, and which combinations are legal."
url: "https://saved.sh/docs/backups"
---

A **backup** is a definition, not a copy. It says what to take, where it runs, how often,
and how long the results are kept. Each execution is a **run**, and a successful run
produces an **artifact**.

Three things about a backup are decided once and shape everything else: its **kind**, its
**source type**, and its **retention**. The rest can be changed whenever you like.

## Kinds [#kinds]

Kind is chosen when the backup is created and **cannot be changed afterwards**. It decides
who connects to your data.

| Kind     | We connect | You run a worker | Source credential              |
| -------- | ---------- | ---------------- | ------------------------------ |
| `local`  | No         | Yes              | Stays on your machine          |
| `cloud`  | Yes        | No               | Held in our vault              |
| `manual` | No         | No               | There is no source, you upload |

Kind is immutable because changing it would silently move a credential across a trust
boundary. Create a second backup instead.

<Cards>
  <Card href="/docs/backups/local" title="Local" description="Your worker connects. The credential never leaves your network." />

  <Card href="/docs/backups/cloud" title="Cloud" description="We connect, with a credential you hand us for the purpose." />

  <Card href="/docs/backups/manual" title="Manual" description="No source and no schedule. You produce the file." />
</Cards>

## What a backup is made of [#what-a-backup-is-made-of]

| Field                   | Required      | Changeable | Notes                                           |
| ----------------------- | ------------- | ---------- | ----------------------------------------------- |
| `name`                  | Yes           | Yes        | 1 to 100 characters, for you rather than for us |
| `kind`                  | Yes           | **No**     | `local`, `cloud` or `manual`                    |
| `source_type`           | Except manual | Yes        | Must be legal for the kind                      |
| `worker`                | Local only    | Yes        | Which of your workers runs it                   |
| `schedule`              | Except manual | Yes        | Standard cron                                   |
| `compression`           | No            | Yes        | Off by default                                  |
| `encryption.public_key` | No            | Yes        | Applies to future artifacts only                |
| `retention`             | No            | **No**     | Write-once, see below                           |
| `delivery`              | No            | Yes        | Where copies are written                        |

A backup starts as a **draft** and becomes **active** once it has everything its kind
requires. Until then it holds no schedule and produces nothing.

| State      | Meaning                                                |
| ---------- | ------------------------------------------------------ |
| `draft`    | Created but not yet complete. Nothing is scheduled     |
| `active`   | Complete and scheduled                                 |
| `paused`   | Schedule suspended. Definition and artifacts untouched |
| `deleting` | On its way out. No longer configurable                 |

Pausing is the right way to stop a backup temporarily. Deleting is refused while the backup
still has artifacts, so a definition cannot be removed out from under the things it
produced.

## Source types [#source-types]

| Type       | `local` | `cloud`   | Produces                        |
| ---------- | ------- | --------- | ------------------------------- |
| `postgres` | Yes     | Yes       | `pg_dump` custom-format dump    |
| `mysql`    | Yes     | Yes       | `mysqldump` SQL                 |
| `redis`    | Yes     | Yes       | RDB snapshot                    |
| `s3`       | Yes     | Yes       | One tar of the bucket or prefix |
| `web`      | Yes     | Yes       | The response body               |
| `script`   | Yes     | **Never** | Whatever your command writes    |
| `file`     | Yes     | **Never** | The file                        |
| `folder`   | Yes     | **Never** | One zip of the tree             |

**A run produces exactly one artifact.** Sources that are naturally many files are streamed
into a single archive, so `keep_last` counts runs rather than files.

Submitting a type that is illegal for the kind is refused at configure time, naming the type
you sent. See [Sources](/docs/backups/sources) for each one in detail.

## Schedules [#schedules]

Standard cron, evaluated in UTC unless you set a timezone on the backup.

```yaml
schedule: "0 2 * * *"      # 02:00 daily
schedule: "0 3 * * 0"      # 03:00 Sundays
```

A manual backup takes no schedule, and asking for one is refused rather than ignored. See
[Schedules](/docs/backups/schedules).

## Retention [#retention]

One rule with a floor under it. An artifact is removed when it is **both** older than
`expire_after` **and** outside the newest `keep_last`:

```yaml
retention:
  expire_after: 90d    # nothing older than 90 days
  keep_last: 10        # but always keep the 10 most recent, however old they are
```

**`keep_last` is a floor, not a cap.** On its own it deletes nothing: without `expire_after`
you keep everything forever, however high the count. This is on purpose. A count read as a
cap would delete your only recent copies the moment a source starts producing runs faster
than you expected.

The newest artifact is never removed, so a backup that has run at least once always has
something to restore from.

Retention is the only setting that bounds what you store, and therefore what you are billed
for archive. A backup with no retention keeps everything forever.

**Retention is write-once.** It is fixed when you define the backup and cannot be edited,
loosened, tightened or turned off. A different policy means a new backup. See
[Retention](/docs/backups/retention) for why, and for `lock_for`.

## The pipeline [#the-pipeline]

Every kind converges on the same post-run pipeline, whoever produced the bytes.

```
produce → inspect → compress → encrypt → deliver → finalize
```

| Stage    | Where it runs                                  | Detail                                                                                     |
| -------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Produce  | Your worker, or ours, or your upload           | [Local](/docs/backups/local), [Cloud](/docs/backups/cloud), [Manual](/docs/backups/manual) |
| Compress | Ours                                           | [Compression](/docs/backups/compression)                                                   |
| Encrypt  | Ours for cloud and manual, **yours** for local | [Encryption](/docs/backups/encryption)                                                     |
| Deliver  | Ours                                           | [Delivery](/docs/backups/delivery)                                                         |
| Finalize | Ours                                           | The artifact record is written here, and not before                                        |

A run that stores no copy anywhere fails **before** finalize, so no artifact record is
written for a file that does not exist.

## Next [#next]

<Cards>
  <Card href="/docs/backups/sources" title="Sources" description="Every source type, its fields, and what it produces." />

  <Card href="/docs/backups/schedules" title="Schedules" description="Cron, timezones, overruns and triggers." />

  <Card href="/docs/backups/retention" title="Retention" description="The rule, the floor, and the protection window." />

  <Card href="/docs/backups/runs" title="Runs" description="Reading an execution, and the history window." />
</Cards>
