Backups Configuration
Gordon has two backup flows:
- Database backups: PostgreSQL logical dumps via
pg_dump, stored on the local filesystem.
- Volume backups: best-effort filesystem archives of Gordon-managed named volumes, uploaded to S3. Administrative bind mounts are operator-owned host paths and are never archived.
Backups are identified by app, service, and the declared database or volume
(see Apps). Domains are routing addresses and are never a
backup identity.
Schedule
Each backup flow runs on one installation-wide schedule. Apps only declare
what to back up; the installation decides when.
schedule |
Runs |
Default time |
hourly |
every hour at the time minutes |
00:00 (on the hour) |
daily |
every day |
02:00 |
weekly |
every Sunday |
03:00 |
monthly |
the first day of each month |
04:00 |
time is HH:MM (24-hour) in the server's local timezone (TZ, or /etc/localtime). The run keeps the same wall-clock time across daylight-saving changes. A time skipped by the spring-forward change runs when the gap ends. A time repeated by the fall-back change runs once.
- The next run is computed from the current time when Gordon starts. A run that falls while Gordon is down is skipped, not caught up: no workload runs then, so there is nothing new to back up.
- Gordon logs the schedule and
next_run at startup (backup scheduler enabled, volume backup scheduler enabled).
Database backups
[backups.databases]
enabled = true
schedule = "daily"
time = "03:00"
storage_dir = "~/.gordon/backups"
[backups.databases.retention]
hourly = 24
daily = 7
weekly = 4
monthly = 12
| Key |
Default |
Description |
backups.databases.enabled |
false |
Enables PostgreSQL backup service wiring |
backups.databases.schedule |
"daily" |
Run frequency: hourly, daily, weekly, monthly; databases run when their declared schedule matches |
backups.databases.time |
"" |
HH:MM server local time; empty uses the preset default |
backups.databases.storage_dir |
"" |
Root backup directory; defaults to {server.data_dir}/backups |
backups.databases.retention.* |
0 |
Number of backups to keep per DB and schedule tier |
Volume backups to S3
[backups.volumes]
enabled = true
schedule = "daily"
time = "03:30"
compression = "gzip"
timeout = "2h"
max_concurrency = 2
helper_image = "alpine:3.20"
[backups.volumes.s3]
bucket = "gordon-backups"
region = "eu-west-3"
prefix = "prod/gordon"
# endpoint = "https://s3.example.com"
# path_style = false
# sse_algorithm = "AES256"
# sse_kms_key_id = ""
# storage_class = "ONEZONE_IA"
[backups.volumes.retention]
keep = 14
| Key |
Default |
Description |
backups.volumes.enabled |
false |
Enables automatic volume archives |
backups.volumes.schedule |
"daily" |
Run frequency: hourly, daily, weekly, monthly |
backups.volumes.time |
"" |
HH:MM server local time; empty uses the preset default |
backups.volumes.compression |
"gzip" |
gzip or zstd; helper image must provide the tool |
backups.volumes.timeout |
"2h" |
Per-volume archive/upload timeout |
backups.volumes.max_concurrency |
2 |
Volumes archived in parallel during a scheduled run |
backups.volumes.helper_image |
"alpine:3.20" |
Helper container image used for tar/compression |
backups.volumes.s3.bucket |
"" |
Required when volume backups are enabled |
backups.volumes.s3.region |
"" |
Required when volume backups are enabled |
backups.volumes.s3.prefix |
"" |
Object key prefix |
backups.volumes.s3.endpoint |
"" |
Optional S3-compatible endpoint |
backups.volumes.s3.path_style |
false |
Use path-style addressing for S3-compatible storage |
backups.volumes.s3.sse_algorithm |
"" |
Server-side encryption sent with each upload, such as AES256 |
backups.volumes.s3.sse_kms_key_id |
"" |
KMS key ID for aws:kms encryption |
backups.volumes.s3.storage_class |
"" |
Storage class sent with each upload, such as STANDARD_IA (AWS) or ONEZONE_IA (Scaleway One Zone); empty uses the bucket default |
backups.volumes.retention.keep |
14 |
Completed archives to keep per app + volume |
Volume backup objects are stored under the canonical app name:
<prefix>/apps/<app>/volumes/<volume>/<timestamp>-<id>.tar.gz # gzip
<prefix>/apps/<app>/volumes/<volume>/<timestamp>-<id>.tar.zst # zstd
Objects written by earlier versions under a domain prefix are not read or
migrated: back them up and re-run the app's volume backups if you need them in
the current layout.
Monitoring
With telemetry metrics enabled, Gordon exports the result, duration, and size of every backup run, the last success of each target, and the next scheduled run. See Backups metrics for the metric names and example alerts.
Notes
- Volume backups archive the app's declared volumes that its backup declaration references.
- Bind mounts, tmpfs mounts, anonymous volumes, and undeclared volumes are never backed up.
- Database backups archive the app's declared PostgreSQL databases that its backup declaration references.
- Only volumes listed in a service's
backup.volume are archived. Leave caches and rebuildable data (CI runner state, build caches) out of that list.
- Stored size is roughly
retention.keep × the compressed size of each backed-up volume.
- Live volume archives are best-effort; consistency requires application quiesce, pause, or stop.
- Automated volume restore is not part of the MVP.
- Snapshots are not generic across Docker/Podman volumes and require backend-specific support such as ZFS, Btrfs, LVM, EBS, or a snapshot-capable volume driver.
- S3 credentials should come from the environment, shared config, instance role, or Gordon secret conventions. Use least-privilege IAM for the configured prefix.
source
1# Backups Configuration
2
3Gordon has two backup flows:
4
5- **Database backups**: PostgreSQL logical dumps via `pg_dump`, stored on the local filesystem.
6- **Volume backups**: best-effort filesystem archives of Gordon-managed named volumes, uploaded to S3. Administrative bind mounts are operator-owned host paths and are never archived.
7
8Backups are identified by app, service, and the declared database or volume
9(see [Apps](../config/apps.md)). Domains are routing addresses and are never a
10backup identity.
11
12## Schedule
13
14Each backup flow runs on one installation-wide schedule. Apps only declare
15*what* to back up; the installation decides *when*.
16
17| `schedule` | Runs | Default `time` |
18|---|---|---|
19| `hourly` | every hour at the `time` minutes | `00:00` (on the hour) |
20| `daily` | every day | `02:00` |
21| `weekly` | every Sunday | `03:00` |
22| `monthly` | the first day of each month | `04:00` |
23
24- `time` is `HH:MM` (24-hour) in the **server's local timezone** (`TZ`, or `/etc/localtime`). The run keeps the same wall-clock time across daylight-saving changes. A time skipped by the spring-forward change runs when the gap ends. A time repeated by the fall-back change runs once.
25- The next run is computed from the current time when Gordon starts. A run that falls while Gordon is down is skipped, not caught up: no workload runs then, so there is nothing new to back up.
26- Gordon logs the schedule and `next_run` at startup (`backup scheduler enabled`, `volume backup scheduler enabled`).
27
28## Database backups
29
30```toml
31[backups.databases]
32enabled = true
33schedule = "daily"
34time = "03:00"
35storage_dir = "~/.gordon/backups"
36
37[backups.databases.retention]
38hourly = 24
39daily = 7
40weekly = 4
41monthly = 12
42```
43
44| Key | Default | Description |
45|---|---:|---|
46| `backups.databases.enabled` | `false` | Enables PostgreSQL backup service wiring |
47| `backups.databases.schedule` | `"daily"` | Run frequency: `hourly`, `daily`, `weekly`, `monthly`; databases run when their declared `schedule` matches |
48| `backups.databases.time` | `""` | `HH:MM` server local time; empty uses the preset default |
49| `backups.databases.storage_dir` | `""` | Root backup directory; defaults to `{server.data_dir}/backups` |
50| `backups.databases.retention.*` | `0` | Number of backups to keep per DB and schedule tier |
51
52## Volume backups to S3
53
54```toml
55[backups.volumes]
56enabled = true
57schedule = "daily"
58time = "03:30"
59compression = "gzip"
60timeout = "2h"
61max_concurrency = 2
62helper_image = "alpine:3.20"
63
64[backups.volumes.s3]
65bucket = "gordon-backups"
66region = "eu-west-3"
67prefix = "prod/gordon"
68# endpoint = "https://s3.example.com"
69# path_style = false
70# sse_algorithm = "AES256"
71# sse_kms_key_id = ""
72# storage_class = "ONEZONE_IA"
73
74[backups.volumes.retention]
75keep = 14
76```
77
78| Key | Default | Description |
79|---|---:|---|
80| `backups.volumes.enabled` | `false` | Enables automatic volume archives |
81| `backups.volumes.schedule` | `"daily"` | Run frequency: `hourly`, `daily`, `weekly`, `monthly` |
82| `backups.volumes.time` | `""` | `HH:MM` server local time; empty uses the preset default |
83| `backups.volumes.compression` | `"gzip"` | `gzip` or `zstd`; helper image must provide the tool |
84| `backups.volumes.timeout` | `"2h"` | Per-volume archive/upload timeout |
85| `backups.volumes.max_concurrency` | `2` | Volumes archived in parallel during a scheduled run |
86| `backups.volumes.helper_image` | `"alpine:3.20"` | Helper container image used for tar/compression |
87| `backups.volumes.s3.bucket` | `""` | Required when volume backups are enabled |
88| `backups.volumes.s3.region` | `""` | Required when volume backups are enabled |
89| `backups.volumes.s3.prefix` | `""` | Object key prefix |
90| `backups.volumes.s3.endpoint` | `""` | Optional S3-compatible endpoint |
91| `backups.volumes.s3.path_style` | `false` | Use path-style addressing for S3-compatible storage |
92| `backups.volumes.s3.sse_algorithm` | `""` | Server-side encryption sent with each upload, such as `AES256` |
93| `backups.volumes.s3.sse_kms_key_id` | `""` | KMS key ID for `aws:kms` encryption |
94| `backups.volumes.s3.storage_class` | `""` | Storage class sent with each upload, such as `STANDARD_IA` (AWS) or `ONEZONE_IA` (Scaleway One Zone); empty uses the bucket default |
95| `backups.volumes.retention.keep` | `14` | Completed archives to keep per app + volume |
96
97Volume backup objects are stored under the canonical app name:
98
99```text
100<prefix>/apps/<app>/volumes/<volume>/<timestamp>-<id>.tar.gz # gzip
101<prefix>/apps/<app>/volumes/<volume>/<timestamp>-<id>.tar.zst # zstd
102```
103
104Objects written by earlier versions under a domain prefix are not read or
105migrated: back them up and re-run the app's volume backups if you need them in
106the current layout.
107
108## Monitoring
109
110With [telemetry](./telemetry.md) metrics enabled, Gordon exports the result, duration, and size of every backup run, the last success of each target, and the next scheduled run. See [Backups metrics](./telemetry.md#backups) for the metric names and example alerts.
111
112## Notes
113
114- Volume backups archive the app's declared volumes that its backup declaration references.
115- Bind mounts, tmpfs mounts, anonymous volumes, and undeclared volumes are never backed up.
116- Database backups archive the app's declared PostgreSQL databases that its backup declaration references.
117- Only volumes listed in a service's `backup.volume` are archived. Leave caches and rebuildable data (CI runner state, build caches) out of that list.
118- Stored size is roughly `retention.keep` × the compressed size of each backed-up volume.
119- Live volume archives are best-effort; consistency requires application quiesce, pause, or stop.
120- Automated volume restore is not part of the MVP.
121- Snapshots are not generic across Docker/Podman volumes and require backend-specific support such as ZFS, Btrfs, LVM, EBS, or a snapshot-capable volume driver.
122- S3 credentials should come from the environment, shared config, instance role, or Gordon secret conventions. Use least-privilege IAM for the configured prefix.
123
124## Related
125
126- [App Manifest](./apps.md)
127- [CLI Backup Command](../cli/backup.md)
128- [Configuration Reference](./reference.md)
129- [Telemetry](./telemetry.md)