Skip to content

Operations, backup, and recovery ​

Readiness ​

GET /healthz answers liveness only. It intentionally does not prove that the configured interpreter, cgroup controller, private rootfs, or event store can complete a job.

A readiness check should:

  1. call authenticated /v1/status and compare the sandbox/backend posture to policy;
  2. submit a small canary under a dedicated tenant;
  3. wait through /result;
  4. verify the terminal status, output, receipt/evidence completeness, bootstrap_ready, isolation facts, network posture, and every expected limit_enforcement flag;
  5. obtain the authoritative tenant from authenticated /v1/whoami or the submitted JobDetail.tenant, wait for attestation.available, download the exact envelope/result, compare attestation.tenant, declared digests, and fields, then run rookhold-verify verify --tenant "$EXPECTED_TENANT" with an independently pinned public key;
  6. alert if latency, queue depth, or signing convergence exceeds policy.

Never route untrusted production traffic to a server reporting the plain subprocess backend. Its only effective resource control is wall time; requested CPU, memory, process, and file limits are not enforced.

Signals and shutdown ​

Drain the reverse proxy before stopping Rookhold. On SIGTERM/Ctrl-C, Rookhold stops accepting HTTP work, requests cancellation of active executions, and gives workers up to 30 seconds to finalize. Accepted jobs that are still queued remain durable and are re-admitted on startup. A job that had reached running cannot be resumed after a process/host failure; boot recovery finalizes it as error with an interruption record. Inspect those receipts before deleting anything.

Compose and the systemd template allow a 45-second stop grace period. Keep the service-manager timeout longer than Rookhold's 30-second worker grace so HTTP draining and final persistence have headroom. If the worker grace expires, the lease drop synchronously requests a whole-cgroup kill, waits up to two seconds for populated 0, and removes the leaf before returning. A hard process or host kill can interrupt that bounded cleanup and SQLite checkpointing; verify no populated or stale Rookhold cgroup remains before restart, and let boot recovery finalize interrupted jobs.

Monitoring ​

/v1/metrics is scoped to the authenticated tenant. Configure a separate ROOKHOLD_METRICS_TOKEN and scrape global /metrics from the operator network for bounded process/admission/storage telemetry. Neither surface is a durable billing record. Combine them with JSON logs and alerts for:

  • queue rejection and sustained queueing
  • runsc/provider bootstrap, digest, rootfs-manifest, or OCI-config failures
  • cgroup cleanup failures or leaked descendants
  • output truncation and policy violations
  • timeout, OOM, cancellation, and internal-error rates
  • SQLite busy, I/O, migration, or disk-capacity errors
  • unexpected restarts and boot-recovered jobs
  • persistent attestation retries, unavailable terminal signatures, or key-load failures
  • rate-limit pressure by tenant
  • tenant/global queue, aggregate-memory, logical-storage, and disk-reserve pressure
  • response-capacity pressure, incomplete-response retries, and HTTP write-progress timeouts

Keep host metrics for memory, CPU, cgroup count, disk latency/free space, inode use, and kernel audit events. The server process itself is outside each job's cgroup, so host-level capacity matters.

Capacity validation ​

From a source checkout, use a dedicated benchmark tenant against the exact VM/rootfs shape:

bash
python scripts/bench.py \
  --url http://127.0.0.1:7300 \
  --key BENCHMARK_TENANT_KEY \
  --jobs 50 \
  --concurrency 4 \
  --wait-seconds 60

The default run submits two warmups plus 50 measured jobs and normally makes 104 authenticated requests, below the default 120-request tenant budget. The benchmark honors Retry-After and uses one server-side result wait per job. For larger trials, schedule a controlled window and set an intentional rate budget; do not disable admission controls on a live tenant. Record the Rookhold/image/rootfs digest, VM shape, worker/concurrency settings, language versions, and outcome mix with the latency table.

Retention ​

ROOKHOLD_RETENTION_HOURS controls deletion of terminal jobs and their events. A value of 0 disables automatic deletion. Retention is not archival: copy required evidence to your controlled archive before its deadline.

Capacity planning must include the main SQLite file plus -wal and -shm companions, exact result artifacts, DSSE envelopes, the temporary signing reserve, logs, backups, and job/runtime staging. Transactional logical quotas bound retained rows; ROOKHOLD_STORAGE_FREE_RESERVE_MB protects real filesystem headroom. Neither replaces host disk/inode monitoring.

Retention tombstones and deletes terminal jobs in bounded batches. Foreign keys cascade events, idempotency mappings, attestations, and signing-outbox rows. Export any required envelope/result/public-key history before the job's deadline.

Online backup ​

Use SQLite's online backup mechanism rather than copying only the main file while Rookhold is running:

bash
sqlite3 /var/lib/rookhold/rookhold.db \
  ".timeout 10000" \
  ".backup '/secure-backups/rookhold-$(date -u +%Y%m%dT%H%M%SZ).db'"

Run PRAGMA integrity_check; against the backup, encrypt it, record a checksum, and transfer it to access-controlled storage. Database contents include submitted code, stdin-derived behavior, stdout/stderr, tenant identifiers, and evidence metadata. Treat backups as sensitive.

Inside Compose, either install/use sqlite3 on the host against a carefully exposed backup path, or stop the service cleanly and copy the named volume. Do not add broad host mounts to the Rookhold container merely to simplify backup.

Offline backup ​

For a filesystem-level copy:

  1. drain and stop Rookhold;
  2. verify no rookhold process has the database open;
  3. copy the database and any WAL/SHM files together, or checkpoint first;
  4. copy configuration metadata, Rookhold/image/runtime/rootfs digests, release version, signed envelopes, and public-key history; back up the private signing key separately under a stronger key-custody policy;
  5. restart and run a canary.

Restore test ​

Restores should be rehearsed on an isolated host:

  1. verify backup checksum and decrypt to a private path;
  2. start the same Rookhold version against a copy of the restored database;
  3. run PRAGMA integrity_check;;
  4. verify several jobs, ordered event chains, receipts, exact artifacts, and signatures against the historical public keys;
  5. upgrade the copy if required and repeat verification;
  6. destroy the rehearsal copy securely.

Never point two live Rookhold instances at the same SQLite file. Rookhold takes an adjacent process lock and rejects symlinked or hard-linked database aliases, but that guard is not a substitute for operator discipline around bind mounts or network filesystems. Before replacing production data, stop the service and preserve the failed/current database for forensics.

Rootfs maintenance ​

Treat the private rootfs as an immutable deployment input, not as a Rookhold GitHub release asset. Build a new tree, patch interpreters and libraries, compute and preserve its manifest/digest and package inventory, run all language canaries and hostile tests, then switch ROOKHOLD_ROOTFS during a controlled restart. Do not mutate the live tree while jobs are running.

Short jobs. Hard limits. Receipts.