Backup
A definition, not a copy. What to take, from where, how often, kept how long.
View as MarkdownA backup is a definition. It says what to copy, where it runs, how often, and how long the results are kept.
It is not the copy. The copies are artifacts, and one backup accumulates many of them over its life.
Backup "prod-db"
├── Run 2026-08-06 02:00 ──► Artifact
├── Run 2026-08-07 02:00 ──► Artifact
└── Run 2026-08-08 02:00 ──► ArtifactWhat it holds
| Field | Changeable | Notes |
|---|---|---|
name | Yes | For you, not for us. Everything else references its ID |
kind | No | Who connects to your data |
source_type | Yes | What is being taken |
worker | Yes | Which of your machines runs it, for local |
schedule | Yes | Cron, in UTC |
compression | Yes | Off by default |
| encryption key | Yes | Applies to future artifacts only |
retention | No | Write-once |
delivery | Yes | Where copies are written |
Two fields are immutable, and both for the same reason: they are promises other things depend on. See kind and retention.
Kind decides who holds your credentials
Kind is chosen at creation and cannot be changed.
| Kind | We connect | You run a worker | Source credential lives |
|---|---|---|---|
local | No | Yes | On your machine, only |
cloud | Yes | No | In our vault |
manual | No | No | There is no source |
Changing kind would silently move a credential across a trust boundary, which is not something a settings form should be able to do. To change it, create a second backup.
This is the single most consequential choice you make. local means we hold ciphertext and
never see your source credentials. cloud means we hold the credential and process your
plaintext. Both are legitimate; they are not equivalent.
Source type
What is being taken. manual backups have none.
| Type | local | cloud |
|---|---|---|
postgres, mysql, redis, s3, web | Yes | Yes |
script, file, folder | Yes | Never |
The two "never" rows are guarantees rather than gaps. We do not execute customer commands in our cloud and your filesystem does not exist there; the drive sources run on an OAuth grant that is ours to hold and a local worker never sees.
States
draft ──► active ⇄ paused
└──► deleting| State | Meaning |
|---|---|
draft | Created, 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 |
A backup becomes active on its own, as soon as its definition has everything its kind requires. You do not activate it explicitly.
Creating one is two steps
sctl backup create prod-db --kind local # reserves the name, the kind, and an ID
sctl backup configure "$BACKUP_ID" ... # everything elseThe split exists because kind must be fixed before anything else can be validated against it.
A manifest hides this: sctl apply does both.
Deleting
Refused while artifacts still exist. A definition cannot be removed out from under the things it produced, and a locked artifact blocks it until the lock lapses.
Pause is almost always what you want. It stops the schedule and changes nothing else.
Naming
Names are for humans. Every command and API call takes the ID, which sctl backup list
prints beside the name.
Name for what fails, not for what it is: prod-db beats postgres-backup-1, because the
name is what you read at 3am while deciding what to restore.