Skip to content

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.json metadata.
  • 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-destination

For the production layout, first prepare a private parent directory:

sh
sudo install -d -o tinycms -g tinycms -m 0700 /srv/tinycms-recovery

Then, 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-001

The 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:

  1. Point DB_FILE_NAME at the restored database path from the report.
  2. Point all image/document/video/audio, profile-image, and content-backup roots at their restored paths.
  3. Use fresh, distinct, empty paths for cache, active staging, and quarantine. Preserve forensic staging/quarantine as inspection evidence only.
  4. Select this JSON through APP_CONFIG_FILE; keep the frontend build path on the matching release.
  5. Bind to 127.0.0.1 on a separate PORT, such as 3301. Set both public URL variables to http://127.0.0.1:3301 for same-origin local inspection.
  6. Set both jobs.schedules and jobs.webhooks to disabled. Prevent production email or other external side effects using isolated email configuration and network controls.
  7. 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.js

Keep 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-server

Open 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 ​

CheckExpected result
/health and loginMatching build starts and existing users can authenticate
Media and profile picturesRestored originals are delivered
Thumbnail requestA missing cache artifact is regenerated
PermissionsRestricted accounts cannot edit denied content
Published deliveryExisting API keys return the expected live content in each language
Drafts and historyUnpublished work stays unpublished; historical revisions remain
Options, CSS, auditExisting values, templates, and activity are present
Schedules and webhooksRecords 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 ​

  1. Record the exact intended destination and explicit authorization for production replacement in the change record. Complete the isolated drill first.
  2. Preserve the current release, configuration, and secrets for rollback.
  3. Suspend public routing/writes, restarts, external cron, and other writers; stop the service.
  4. Create and validate a fresh pre-restore backup of the current deployment and copy it independently. If this fails, do not replace the deployment.
  5. 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.
  6. 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.
  7. 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.