Restore and recovery
Restore first into isolation. The CLI never implicitly overwrites an existing deployment. Use the exact matching application build and original secrets before considering upgrades.
1. Gather recovery material
- A completed bundle and its validation result.
- The exact application release and built
dist/backup/build.jsonmetadata. - Original authentication secret, webhook encryption key, environment settings, and complete deployment JSON from the separate secrets/configuration store.
- A new empty destination outside deployment data, backup storage, and public assets, with space for restoration and validation.
Build compatibility uses deterministic application and schema fingerprints. There is no forced mismatch bypass or automatic migration. Do not modify manifests to bypass checks. Expired sessions or keys stay expired, and hashed delivery keys cannot be converted back into plaintext.
2. Validate and restore to an empty destination
With the matching extracted release and appropriate backend .env, run from the release root:
sh
pnpm --filter backend run backup:validate -- --bundle /absolute/path/to/tinycms-EXACT-NAME
pnpm --filter backend run backup:restore -- --bundle /absolute/path/to/tinycms-EXACT-NAME --destination /absolute/path/to/empty-recovery-destinationFor the production layout, first prepare a private parent directory:
sh
sudo install -d -o tinycms -g tinycms -m 0700 /srv/tinycms-recoveryThen, from the matching release's backend workspace:
sh
sudo -u tinycms /usr/bin/node --env-file=/etc/tinycms/backend.env dist/backup/cli.js validate --bundle /var/backups/tinycms/tinycms-EXACT-NAME
sudo -u tinycms /usr/bin/node --env-file=/etc/tinycms/backend.env dist/backup/cli.js restore --bundle /var/backups/tinycms/tinycms-EXACT-NAME --destination /srv/tinycms-recovery/drill-001The destination must be absent or empty. The command validates before destination writes, copies into private sibling scratch storage, validates the copy, and atomically finalizes it. It returns status: restored and a report mapping restored paths. Preserve that report. Existing destination contents are never overwritten; investigate a failure and retry with an empty destination.
Never start the application against database.db inside the original bundle. Keep the bundle immutable for future recovery.
3. Configure the isolated instance
Create separate protected environment and complete JSON files, for example /etc/tinycms/recovery.env and /etc/tinycms/recovery.json:
- Point
DB_FILE_NAMEat the restored database path from the report. - Point all image/document/video/audio, profile-image, and content-backup roots at their restored paths.
- Use fresh, distinct, empty paths for cache, active staging, and quarantine. Preserve forensic staging/quarantine as inspection evidence only.
- Select this JSON through
APP_CONFIG_FILE; keep the frontend build path on the matching release. - Bind to
127.0.0.1on a separatePORT, such as 3301. Set both public URL variables tohttp://127.0.0.1:3301for same-origin local inspection. - Set both
jobs.schedulesandjobs.webhookstodisabled. Prevent production email or other external side effects using isolated email configuration and network controls. - Supply the original auth secret and webhook encryption key securely. Omit the bootstrap token.
From the matching backend directory, start the isolated instance manually:
sh
sudo -u tinycms /usr/bin/node --env-file=/etc/tinycms/recovery.env dist/index.jsKeep it outside public proxy routing. Use an SSH tunnel from your workstation if browser inspection is needed:
sh
ssh -L 3301:127.0.0.1:3301 operator@your-serverOpen http://127.0.0.1:3301 locally. Do not copy production cookies into reports. Testing an existing still-valid session also requires matching cookie origin/security conditions; a new local login alone does not prove existing production sessions survive.
4. Verify recovery
| Check | Expected result |
|---|---|
/health and login | Matching build starts and existing users can authenticate |
| Media and profile pictures | Restored originals are delivered |
| Thumbnail request | A missing cache artifact is regenerated |
| Permissions | Restricted accounts cannot edit denied content |
| Published delivery | Existing API keys return the expected live content in each language |
| Drafts and history | Unpublished work stays unpublished; historical revisions remain |
| Options, CSS, audit | Existing values, templates, and activity are present |
| Schedules and webhooks | Records exist while disabled jobs avoid production effects |
Record bundle and release identity, operator, start/end time, recovery duration, and pass/fail evidence without secrets. Stop the isolated server gracefully afterward.
Production cutover
- Record the exact intended destination and explicit authorization for production replacement in the change record. Complete the isolated drill first.
- Preserve the current release, configuration, and secrets for rollback.
- Suspend public routing/writes, restarts, external cron, and other writers; stop the service.
- Create and validate a fresh pre-restore backup of the current deployment and copy it independently. If this fails, do not replace the deployment.
- Keep existing database and storage intact. Prefer changing the stopped service's explicit configuration to the verified restored database and media paths rather than overwriting live files individually.
- Restore the matching application build and original secrets. Use the real production URLs and choose production job modes deliberately; isolate job side effects until verification is complete.
- Restart privately and repeat recovery checks. Restore public routing, writes, and job processing only after they pass.
A restored database can contain pending notifications that were already delivered after its backup point. Webhook receivers must deduplicate event IDs. Pending schedules can also be overdue; inspect them before re-enabling processing.
If cutover fails
Stop the restored instance before making further changes. Switch back to the preserved database/storage paths, release, configuration, and secrets, then restart and verify. If original files are damaged, restore the validated pre-restore bundle into another empty destination using its matching build.
Preserve the failed restoration for investigation. Never combine database or media files from different recovery points. Once new writes are admitted, returning to the earlier snapshot loses those writes and needs a deliberate reconciliation decision.