# Backups

> What is copied, where it goes, how long it is kept, and what you have to do to get one back.

Production environments are backed up without you doing anything. Two things
are copied: the **database**, and the **files** your application has written —
uploads, generated exports, anything under the directories that survive a
release.

Your code is not backed up, because it is in git and in a build artifact
already. Restoring a repository from us would be restoring it from a worse
copy.

## Where a copy goes

| Copy | Where |
|---|---|
| Near | object storage (Cloudflare R2) on the same continent as your machines |
| Cold | a different provider, in the EU: Scaleway, in Paris |
| On the machine | the database only, for the last two days |

**Two offsite copies, on different providers.** A backup that shares a fate
with the thing it protects is not a backup — and one that shares a *provider*
with it is only slightly better.

The near copy follows your machines. The cold copy recently moved from
Hetzner in Helsinki to Scaleway in Paris; until the new one holds enough
history, both are written, so nothing already kept is lost in the move.

The copy on the machine is the one that survives a dropped table or a bad
deploy, which are the restores people actually ask for; it is the database
only, because a copy of your files on the same disk as your files protects
against nothing. It keeps everything from the last 48 hours and never fewer
than the three most recent, and a restore reads from it first because nothing
has to leave the machine.

It shares the machine's disk with your database, so it gives way first: once
that disk is 90% full, or has less than 1 GB free, the copy on the machine is
not written — the backup says so as a failed step — and the database keeps the
room it needs to go on working. The offsite copies are written as usual. What
the copy on the machine takes is shown on the environment's **Storage** tab.

## Keeping a copy of your own

Under **Backups** on your team, you can point backups at storage you control
as well. Two steps, because the credentials belong to your team rather than to
one destination:

1. **Add the storage under Integrations**, once — S3-compatible object
   storage, an SFTP host or an rclone remote. On S3, give it the bucket
   backups should go in, and a destination appears on its own.
2. **Otherwise add a destination under Backups** and pick the integration. It
   asks for a name and nothing else: where it writes and what it signs with
   come from the integration, so rotating a key there fixes every backup that
   uses it.

**This is additive.** Connecting your own storage asks for a copy inside your
boundary; it does not stop us keeping ours. It is also how non-production
environments get backed up offsite, since we do not do that for you.

## When they run

Once a day, in a two-hour window at night: 03:00–05:00 in the country your team
is billed in, so a backup is not running while your visitors are. If your
visitors keep other hours, choose the hour and timezone yourself under
**Other → Backup / maintenance schedule** on the project's **Configuration**
page. One window covers every environment of the project, and scheduled
maintenance on your machines — upgrades that restart a service — is done in
the same window. A team with no billing address yet is backed up at
05:00–07:00 local to the continent its machines are on.

**The database can be copied every four hours instead.** That is an add-on on
production, available on either plan and bought under Commercial terms, and it
is included on the [Dedicated shape](shapes.md#dedicated). Files stay once a
day whatever the database does: uploads change slowly, and the data somebody
actually loses in an afternoon is in the database. The extra dumps are kept
for a week; beyond that the daily series is what reaches back.

If a backup is missed — the machine was rebuilding, something was down — it
runs as soon as it is noticed rather than waiting for tomorrow. A backup that
keeps failing is worth a [notification](notifications.md): nothing else
notices until somebody needs to restore.

## What is kept

| | Production | Staging | Development |
|---|---|---|---|
| Daily | 7 | 3 | 1 |

Deliberately shallow. The restores people ask for are from the last few days,
and a snapshot from a year ago restored today is a different site rather than
an older one.

**The same on either plan.** How far back a series reaches is not decided by
whether a project is shared or dedicated — it is bought for production, and
changed whenever you like under Commercial terms on the project's configuration
page. Thirty days instead of a week keeps a snapshot for each of the last thirty
days, with a weekly and a monthly alongside, and it is the one that catches a
problem nobody noticed at the time — the mistake you find on your next
invoice rather than the same afternoon. The database can also be dumped every four hours
rather than nightly, bought the same way.

The staging and development counts apply where those environments have a
backup at all — on your own storage, or the database copy on their machine;
see [below](#non-production-environments).

**A backup you take by hand is kept for 30 days**, regardless of the counts
above. Somebody taking one is usually about to do something risky, and what it
protects against tends to surface days later — after seven dailies would have
rolled it off.

## Non-production environments

**We do not back up staging or development offsite.** What we pay to store is
the data you cannot reproduce, and a staging environment is a deploy away from
existing again.

They keep the database copy on their own machine, which is what saves a botched
migration. If you want them kept properly, connect your own storage.

## Restoring

**Backups → Restore a snapshot** on the environment. It replaces what is there
now — anything written since the snapshot is lost — so it asks you to confirm.

Production can only be restored from **its own** history. Putting staging's
database into production is not a restore; it is replacing the live site with a
copy of somewhere your developers have been working.

## The encryption key

Every repository is encrypted, and **each one has a key of its own**. A
repository is one environment at one destination, so an environment writing to
three places has three repositories with three unrelated keys. That is
deliberate: a key decrypts the whole repository it belongs to, so sharing one
across environments would mean a staging machine holding something that could
read production.

The key is generated when the repository is first created, 256 bits from the
system's random source, and stored encrypted. It is handed to a machine only
for the job that needs it and never written to a task record.

**Losing it makes that repository unreadable** — by you, by us, by anyone.
Restic has no way to change a repository's password without rewriting the
repository, so it is the same key for the life of the backups.

### Taking it with you

Where the destination is **your own storage**, the key is yours to have: the
team's **Backups** page lists every repository on it, with **Show the key**
beside each. With your own credentials and the key you can restore anywhere,
with no involvement from us — `restic -r <repository> restore`. It takes an
Admin or the Owner rather than anybody who can restore, because one string
decrypts every snapshot in that repository, including ones taken before you
joined the team, and cannot be changed afterwards — somebody who leaves the
team keeps it.

On the **platform's own destinations** the key is not offered, and it would not
help: those buckets hold every customer's backups behind credentials that are
ours, so the key would unlock something you cannot reach. What works there is
an export — see below — or [bringing your own destination](#keeping-a-copy-of-your-own),
which is the recommended arrangement for exactly this reason.

## Taking a copy out

**Backups → Prepare a download** turns one snapshot into a plain copy — a
database dump or the files — and gives you a URL to it. That is the route out
that always works, on any destination: it is decrypted on the machine, so you
need no key and no credentials of ours.

What comes out is unencrypted and the URL needs no further authentication, so
it asks you to confirm before it is made. The link expires shortly after it is
ready; prepare it again for a fresh one.

## When a subscription ends

The machines are destroyed a day after the end of the period you paid for.
**The backups are kept for thirty days after the subscription ends**, so the
site can be brought back, and then deleted from our storage — the data
itself, not only the record of it.

A copy in your own storage is not ours to delete and stays where it is, with
its key still yours: that is the one copy that outlives the subscription for
as long as you want it to.

## Copying data between environments

**Backups → Copy from another environment.** This is how you get realistic data
into staging. It is not read from a backup: the receiving machine asks the
source for a fresh database dump and copies its files across directly, so what
arrives is what the source holds right now. It replaces rather than merges —
the database is rebuilt and the file directories mirrored, so nothing that was
there before survives beside it. A deploy follows.

The deploy is not optional and not cosmetic. The database that arrives belongs
to whatever code the *source* was running, and the environment receiving it is
running its own — so the update hooks and configuration import have to run for
the two to agree. Without that you have a database and a codebase that
disagree, which shows up as a fatal on the first page anyone opens.

**Nothing is scrubbed unless you say so.** See below.

On an environment whose database has a machine of its own, the copy runs in
parts, each on the machine holding it: the database on the database machine,
the files on the web machine, then your sanitisation commands where the
application runs. They appear as separate rows under Activity. A restore of the
database there does the same: the data goes back on the database machine, and
your sanitisation and the cache rebuild follow on the web machine.

Not available into production, for the same reason a cross-environment
restore is not.

## Resetting a database

**Backups → Reset database** empties the environment's database and starts it
again on the version `vallic.yaml` declares, with a backup taken first and the
newest backup restored after. It exists for one job — moving to a database
version that cannot read the old files — and
[Service upgrade](service-versions.md) is where to start if that is what you
are doing.
Only an Admin or the Owner can do it, and only by typing the environment's name.

## Sanitising what arrives

We do not scrub anything on our own. We cannot: we do not know which of your
tables hold people. Guessing would produce a result nobody could rely on —
scrubbed enough to look handled, not enough to be.

So you declare it, in `.vallic/commands/sanitization.yml`:

```yaml
commands:
  - 'drush sql:sanitize --yes'
  - 'drush sql:query "UPDATE orders SET customer_email = CONCAT(id, ''@example.test'')"'
  - 'drush sql:query "TRUNCATE payment_tokens"'
```

They run in your application container, in order, immediately after the
database arrives — from a copy or from a restore into that environment — and
before anyone can reach the environment. The first one to
fail stops the rest and fails the task — a half-scrubbed environment holding
real data while looking handled is worse than one that visibly did not finish.

**If the file is not there, nothing runs.** Production data lands in staging
exactly as it was, and every developer with access to that environment can read
it. That is worth deciding deliberately rather than discovering.

The file is read from the environment **receiving** the data, on the branch it
is running — so staging's copy governs what happens to staging.

**Never on production.** A restore into production is production being put
back from its own history, and that data belongs there — so the file is not
read and nothing runs, whatever it declares. Nothing can be copied into
production from another environment either, so there is no case where
production is scrubbed.

Other files can live in `.vallic/commands/` later; sanitisation is the first.

## Next

[Logs](logs.md) covers the other thing that leaves the machine, and
[Shell access](shell.md) covers importing a database dump by hand.
