agent.yaml reference
The single source of truth for this schema lives with the
open-source agent at
restorable/docs/agent-config.md.
This page renders that spec for the dashboard audience; if the
two ever drift, the spec wins.
Every field the agent reads from config.yaml. In the
recommended install, this file lives at
/var/lib/restorable/config.yaml, owned
root:restorable with mode 0640.
The agent validates the file at startup and refuses to run on a
malformed config. Secrets never appear in the file directly;
they are referenced with env: or file:
indirection and resolved at use time.
Top-level shape
mode: managed
agent:
id: agent_01hz...
org_id: org_abc
api_token: file:keys/api_token
orchestrator:
url: https://app.restorable.app
heartbeat_interval_s: 60
restore_scratch:
kind: auto # auto (default) | podman | docker | uri
# image: postgres:16 # optional override; agent auto-picks per source major
# uri: postgres://... # required when kind=uri
storage:
# all optional. Each defaults to /.
# blobs_dir: /var/lib/restorable/blobs # staged ciphertext
# receipts_dir: /var/lib/restorable/receipts # signed receipts
# events_dir: /var/lib/restorable/events # local event log
sources:
- slug: prod-db
kind: postgres
connection:
uri: env:RESTORABLE_DB_URI
checks:
- healthy
checks:
- id: healthy
summary_sql: |
SELECT 1 AS ok
expect: "ok == 1" mode
managed or standalone. setup
writes managed; init writes
standalone. The paying-customer path is managed.
Standalone is the open-source, self-sufficient path for
air-gapped installs; scheduling is done by external cron or
systemd timers.
agent
agent.id
The identifier the orchestrator assigned at registration. Appears
in every receipt the agent signs. Written by setup;
do not edit.
agent.org_id
The org this agent belongs to. Written by setup; do
not edit.
agent.api_token
Bearer token the agent presents on every orchestrator call.
Always a secret reference (file: is the default,
pointing at
file:keys/api_token relative to the install root).
Putting the token inline in the config file is rejected at
validation.
orchestrator
orchestrator.url
Base URL of the Restorable orchestrator the agent talks to.
Default: https://app.restorable.app. Override only
for a private orchestrator deployment.
orchestrator.heartbeat_interval_s
Seconds between heartbeat polls. Default 60. Lower gives the dashboard faster feedback at the cost of more HTTP calls. Values under 10 are rejected.
restore_scratch
Where the ephemeral restored database lives during a restore test. Two kinds, mutually exclusive.
restore_scratch.kind: auto (default)
The agent detects the container runtime at use time: Podman if
installed, otherwise Docker. Pin explicitly with
kind: podman or kind: docker if you
run both and want one. Runs the scratch DB as a short-lived
container; destroyed at session end. With Podman (rootless) no
extra group membership is needed; with Docker the
restorable user is added to the docker
group by the installer.
Optional image: field overrides the default image.
By default the agent picks the image matching your source's
major version (e.g. postgres:16 for PG 16.x,
mongo:7 for Mongo). Override to pin extensions or
collations; leave empty to let the agent track your source.
restore_scratch.kind: uri
The agent restores into an external reachable database endpoint you control. The agent drops and recreates a scratch database on that endpoint between sessions. Use this when the host cannot run any container runtime, or when you want to keep the scratch in a separate managed DB outside the agent host.
restore_scratch:
kind: uri
uri: env:RESTORABLE_SCRATCH_URI The endpoint needs a role that can create and drop databases. Nothing persists between sessions.
sources
An array. Each entry defines one database to back up.
sources[].slug
Human handle for this source, local to this agent. URL-safe (lowercase letters, digits, hyphens). Required and unique. Changing a source slug is treated as delete-and-recreate; historical receipts for the old slug stay verifiable but the source under the new slug starts with no schedule.
In managed mode the slug maps to an orchestrator-minted opaque
source_id at runtime via
POST /v1/sources/resolve. That opaque id is what
appears on the wire; the slug stays in your config. In standalone
mode the slug is also what appears in the receipt's
source_id field.
sources[].kind
postgres or mongodb. Determines the
dump tool and the check payload shape.
sources[].connection.uri
The database connection string. Always a secret reference:
env:NAME or file:path. Relative
file: paths resolve from the install root.
For Postgres: a standard postgres:// URI.
sslmode=require is strongly recommended.
For MongoDB: a mongodb:// or
mongodb+srv:// URI. Atlas and self-hosted clusters
both work.
sources[].checks
Array of check ids that run against this source's restores. Each
id must match an entry in the top-level checks:
list. Order is significant; checks run sequentially against one
shared scratch database.
sources[] schedule and retention (standalone mode only)
These fields are relevant only in standalone mode. The agent never runs an internal scheduler based on them; they document the intended cadence for your crontab or systemd timer.
-
backup_schedule: cron expression documenting when your system timer invokesrestorable backup --source <slug>. -
restore_schedule: cron expression documenting when your system timer invokesrestorable restore-test --source <slug>. -
retention_days: how long to keep backups beforerestorable prunedeletes them. Minimum 7 days.
In managed mode, schedules and retention are set in the dashboard.
The orchestrator is the sole authority; omit these fields from
config.yaml.
checks
An array. Each entry defines one verification that a source can reference by id. See Writing a check for how the contents are evaluated. The fields:
checks[].id
Stable identifier. Appears in every receipt that runs this check.
checks[].expect
The assertion expression. A small comparison DSL over the identifiers returned by the summary query.
checks[].summary_sql
Postgres check body. One query returning exactly one row. Column
names become the identifiers expect can reference.
The agent hashes the canonical SQL text and stores the hash in
the receipt; the text itself stays on your host.
checks[].summary_find
Mongo check body. Mutually exclusive with
summary_sql. One of three shapes: a filter (implicit
count), a full aggregate pipeline, or a list-collections probe
that counts user collections in the database (the mongo
equivalent of postgres's schema-count check).
summary_find:
database: app
collection: orders
filter: { "status": "open" } # produces { n: <count> }
# --- or ---
aggregate:
- { $match: { status: "open" } }
- { $group: { _id: null, n: { $sum: 1 } } }
# --- or ---
list_collections: true # produces { n: <user-collection count> }
With list_collections: true the agent runs
db.getCollectionNames() against
database, drops names that start with
system., and returns the count as
{ n }. collection,
filter, and aggregate must be unset in
this mode; database is required.
Secret references
Fields marked secret accept three forms. Resolution happens at use time, so rotated secrets take effect on the next operation.
-
a-literal-string: taken as-is. Avoid; secrets do not belong in the YAML directly. -
env:VAR_NAME: read from the environment variable. The systemd unit loads/var/lib/restorable/envinto the process environment at start time. -
file:path: read from a file, whitespace-trimmed. Relative paths resolve from the install root. Absolute paths work as written.
storage
Local directory layout for the three things the agent writes to
disk during normal operation. Each field defaults to a
subdirectory of the runtime install root
(/var/lib/restorable/ in systemd installs) and is
overridable independently. Missing fields fall back to the
default.
storage.blobs_dir
Default: <root>/blobs. Staged
ciphertext, one file per backup. Sized proportionally to the
compressed dump. In managed mode the file is deleted after
upload succeeds, so the peak usage is one concurrent dump.
If
/var/lib is on a small boot volume and your dumps
are large, point this at a bigger disk and add that path to
the systemd unit's ReadWritePaths. See
Moving
blobs to a larger disk for the full recipe (the systemd
sandbox blocks writes to any path not listed).
storage.receipts_dir
Default: <root>/receipts. DSSE receipts
(.intoto.json). One per restore test. Kilobytes
each. Retained locally as an auditor-cross-checkable record
alongside the orchestrator's copy.
storage.events_dir
Default: <root>/events. Append-only
local event log. Kilobytes. The agent emits events for backup
/ restore lifecycle and ships them as part of the heartbeat.
storage.s3 (standalone mode only)
Direct S3 uploads for the standalone deployment. Managed mode ignores this block: ciphertext always uploads through the orchestrator's pre-signed URLs so the agent never needs object storage credentials.
storage:
s3:
endpoint: s3.fr-par.scw.cloud
region: fr-par
bucket: my-restorable-backups
access_key: env:RESTORABLE_S3_ACCESS_KEY
secret_key: env:RESTORABLE_S3_SECRET_KEY Systemd sandbox and custom paths
The vendored systemd unit runs with
ProtectSystem=strict and
ReadWritePaths=/var/lib/restorable. Any custom
path you supply to storage.blobs_dir,
storage.receipts_dir,
storage.events_dir, or a
restore_scratch of kind uri with
local-file credentials must be covered by
ReadWritePaths or the agent will fail with a
read-only filesystem error.
The pattern is a drop-in override rather than an edit to the vendored unit:
sudo mkdir -p /etc/systemd/system/restorable.service.d
sudo tee /etc/systemd/system/restorable.service.d/storage.conf >/dev/null <<'EOF'
[Service]
ReadWritePaths=/mnt/big-disk/restorable
EOF
sudo systemctl daemon-reload
sudo systemctl restart restorable
One entry covers every subpath beneath it. Verify with
systemctl cat restorable.
Reloading after an edit
Changes to config.yaml require reloading the agent:
sudo systemctl reload restorable.service Reload re-reads the config. Restart is not required unless you changed something the agent caches at process start (very rare).
Validation errors
The agent's Validate() pass produces one-line
errors like:
source "prod-db": connection.uri is required
check "healthy": cannot mix summary_sql (postgres) and summary_find (mongodb)
duplicate source slug "prod-db" Every validation error is a refusal to start. Fix the YAML; the agent does not run with a half-valid config.