Restore & Recovery
Temps restores a managed external service (Postgres, Redis, MongoDB, S3/RustFS) from an S3 backup. Three modes are supported: restore in place, clone the backup into a brand-new service, or point-in-time recovery (PITR) on Postgres. Use it to roll a database back to a known-good state, spin up a copy from a backup, or recover to a precise moment after data loss.
Overview
The restore framework is a generic orchestrator (in temps-backup) that dispatches to per-engine implementations (in temps-providers). It runs from the web console — the Service Restore page drives the entire flow — or directly against the REST API.
All restore routes live in the backup plugin, and plugin routers are nested under the /api prefix by the Temps core. Every endpoint path below is shown with the /api prefix exactly as served.
What's Included
- In-place restore (destructive overwrite of the existing service)
- Clone-to-new-service (provision a fresh service from a backup)
- Point-in-time recovery (PITR) for Postgres
- A read-only plan preview before you commit
- Per-engine capability discovery
- Disaster-recovery restore from another instance's backups
- Progress tracking via a
restore_runstable you can poll - Audit logging of every restore run
Why It Matters
- Preview exactly what a restore will do before running it
- Clone production data into a staging service without touching production
- Recover Postgres to a specific timestamp, transaction, or LSN
- Restore a backup produced by a different Temps instance
- Self-hosted — backups and restores stay on your infrastructure
How It Works
- Discover capabilities. Ask the service what restore modes it supports with
GET /api/external-services/{id}/restore-capabilities. The response also includes asuggested_new_service_name, pre-formatted as{source}-restore-{yyyymmdd-hhmm}, to pre-fill the clone dialog. - Plan (optional but recommended).
POST /api/external-services/{id}/restore-planreturns a read-only preview: the engine, target service, source backup, the chosenstrategy, the orderedsteps, adestructiveflag, plus non-blockingwarningsand blockingerrors. The UI disables the Start button whenerrorsis non-empty. - Start the restore.
POST /api/external-services/{id}/restorereturns202 Acceptedwith aRestoreRunViewand spawns a background worker. The{id}in the path is the target service — where the data lands. - Poll for progress. Watch
GET /api/restore-runs/{id}(or list a service's runs withGET /api/external-services/{id}/restore-runs) until the run reaches a terminalstatus.
Under the hood, the orchestrator writes a restore_runs row, then dispatches to the engine's ExternalService trait methods: restore_from_s3 (in-place), restore_to_new_service (clone), or restore_pitr (point-in-time).
Restore Modes
The mode is chosen with a JSON mode discriminator (snake_case). On the restore and restore-plan endpoints the mode fields are flattened into the top-level request body alongside backup_id.
mode | What it does | Required fields | Destructive |
|---|---|---|---|
in_place | Restores the backup onto the existing service, overwriting current data | none | Yes |
new_service | Provisions a fresh service and restores into it | name; optional parameter_overrides | No |
pitr | Point-in-time recovery (Postgres / WAL-G only) | to_new_service (bool), target; new_service_name required when to_new_service is true | When to_new_service is false |
parameter_overrides is an optional JSON object on new_service — for example a different port, Docker image, or database name. Parameters you don't override are copied from the source service.
Per-Engine Capabilities
Each engine declares a RestoreCapabilities object: restore_in_place, restore_to_new_service, pitr, earliest_pitr_time, and latest_pitr_time. The framework default is in-place only.
| Engine | restore_in_place | restore_to_new_service | pitr |
|---|---|---|---|
| Postgres | Yes | Yes | Yes |
| S3 / RustFS | Yes | Yes | No |
| Redis | Yes | No (default) | No (default) |
| MongoDB | Yes | No (default) | No (default) |
PITR is only valid on WAL-G backups (Postgres backups stored at an s3:// location). Both the planner and the executor reject a non-WAL-G backup for PITR — the plan returns the error PITR requires a WAL-G backup. Only Postgres declares pitr: true; Redis and MongoDB fall back to the in-place-only default.
When pitr is true, earliest_pitr_time and latest_pitr_time bound the recoverable window (derived from engine archive metadata, e.g. pg_stat_archiver for Postgres).
API Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/external-services/{id}/restore-capabilities | Capabilities + suggested_new_service_name |
GET | /api/external-services/{id}/restore-runs | Recent restore runs for the service |
POST | /api/external-services/{id}/restore-plan | Read-only preview of a restore |
POST | /api/external-services/{id}/restore | Start a restore (returns 202) |
GET | /api/restore-runs/{id} | Poll a restore run's progress |
The restore-plan response (RestorePlan) includes a strategy field with one of these values:
strategy | Meaning |
|---|---|
walg_restore | Restore via WAL-G base-backup replay (Postgres) |
pg_dump_restore | Restore via a pg_dump import (Postgres) |
unsupported | The requested restore is not supported for this engine/backup |
The non-blocking caveats list in the plan is named warnings (not "caveats"). errors is the blocking list; when it is non-empty, the restore should not be started.
Permissions
Each request must carry a valid Temps API key (Authorization: Bearer <temps-api-key>). Endpoints gate on these permissions:
| Endpoint | Required permission(s) |
|---|---|
GET .../restore-capabilities | BackupsRead |
GET .../restore-runs | BackupsRead |
POST .../restore-plan | BackupsRead (planning is read-only) |
GET /api/restore-runs/{id} | BackupsRead |
POST .../restore (in_place) | BackupsWrite + ExternalServicesWrite |
POST .../restore (new_service) | BackupsWrite + ExternalServicesCreate |
POST .../restore (pitr, in place) | BackupsWrite + ExternalServicesWrite |
POST .../restore (pitr to new service) | BackupsWrite + ExternalServicesCreate |
Planning needs only BackupsRead by design: a user can preview a restore before escalating to someone with write permissions to actually run it.
Worked Example
This walkthrough assumes you already have a managed Postgres service (id 42 below) with at least one backup (backup_id 1337) to restore from. The PITR steps additionally require WAL-G backups. Set TEMPS_API_KEY to a key with the permissions listed above.
1. Check what the service supports
curl -s https://your-temps-instance.com/api/external-services/42/restore-capabilities \
-H "Authorization: Bearer $TEMPS_API_KEY"
{
"restore_in_place": true,
"restore_to_new_service": true,
"pitr": true,
"earliest_pitr_time": "2026-05-20T00:00:00Z",
"latest_pitr_time": "2026-05-28T12:00:00Z",
"suggested_new_service_name": "orders-db-restore-20260528-1204"
}
2. Preview an in-place restore (read-only)
curl -s -X POST https://your-temps-instance.com/api/external-services/42/restore-plan \
-H "Authorization: Bearer $TEMPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"backup_id": 1337,
"mode": "in_place"
}'
The response previews the engine, target_service, source_backup, strategy, ordered steps, warnings, errors, destructive, and an echo of mode. If errors is empty, you're clear to start.
3. Clone the backup into a new service
curl -s -X POST https://your-temps-instance.com/api/external-services/42/restore \
-H "Authorization: Bearer $TEMPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"backup_id": 1337,
"mode": "new_service",
"name": "orders-db-restore-20260528-1204",
"parameter_overrides": {}
}'
A 202 Accepted response returns a RestoreRunView. Note its id, then poll:
curl -s https://your-temps-instance.com/api/restore-runs/91 \
-H "Authorization: Bearer $TEMPS_API_KEY"
4. Point-in-time recovery to a new Postgres service
curl -s -X POST https://your-temps-instance.com/api/external-services/42/restore \
-H "Authorization: Bearer $TEMPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"backup_id": 1337,
"mode": "pitr",
"to_new_service": true,
"new_service_name": "orders-db-pitr",
"target": { "kind": "time", "time": "2026-05-27T18:30:00Z" }
}'
Recovery Targets (PITR)
PITR's target is a kind-tagged object (snake_case). Postgres honors all four variants:
kind | Field | Recovers to |
|---|---|---|
time | time (ISO 8601 timestamp) | A specific point in time |
xid | xid (string) | A specific transaction id (Postgres) |
lsn | lsn (string) | A specific log sequence number (Postgres) |
name | name (string) | A named restore point created via pg_create_restore_point (Postgres) |
{ "kind": "xid", "xid": "7654321" }
Disaster Recovery (Orphan Restore)
You can restore a backup that this Temps instance never recorded — for example, a backup produced by another Temps instance and discovered by scanning the S3 bucket. Instead of backup_id, pass backup_location together with backup_engine and s3_source_id:
curl -s -X POST https://your-temps-instance.com/api/external-services/42/restore \
-H "Authorization: Bearer $TEMPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"backup_location": "s3://my-bucket/backups/orders-db/base.tar",
"backup_engine": "postgres",
"s3_source_id": 3,
"mode": "in_place"
}'
| Field | Notes |
|---|---|
backup_location | Raw S3 URL / key of the backup |
backup_engine | One of postgres, redis, mongodb, s3 |
s3_source_id | The S3 source the location lives in |
When you use backup_location, both backup_engine and s3_source_id are required. Omitting backup_engine returns backup_engine is required when using backup_location (orphan restore). Provide either backup_id or backup_location — not neither.
Restore Runs
Every restore is tracked in the restore_runs table and surfaced by the RestoreRunView. Poll GET /api/restore-runs/{id} to follow progress.
| Field | Description |
|---|---|
mode | in_place, new_service, or pitr |
status | pending, running, completed, failed, or cancelled (starts at pending) |
phase | prepare, provision, restore, recover, verify, completed, or failed (starts at prepare) |
recovery_target | The PITR target, if any |
source_backup_id | Backup the restore read from |
source_service_id | Service that produced the backup |
target_service_id | Service the data landed in (set later for clones) |
target_service_name | Name of the target/new service |
error_message | Failure detail when status is failed |
The underlying row also tracks parameter_overrides, resume_token, log_id, attempt, and timestamps.
Every restore run is audit-logged as EXTERNAL_SERVICE_RESTORE_RUN with the source/target service, mode, and backup id. Audit writes are best-effort — a failed audit write is logged but does not fail the restore request.
Next Steps
- Backups — create and schedule the backups you restore from
- Managed Services — the Postgres, Redis, MongoDB, and S3 services this framework restores
- Rollbacks — roll back a bad deployment (distinct from data restore)