Skip to main content

crewship backup

Create, list, inspect and restore workspace / crew backups. This page is the flat flag reference; for the conceptual overview (bundle layout, lock semantics, encryption chain, admin UI, instance-scope details) see the Backup & Restore guide.
Every subcommand that talks to the server requires the OWNER or ADMIN role on the target workspace, or an instance administrator — other roles are refused. The commands name the workspace explicitly (--workspace, or the configured one), so an instance administrator works on any workspace without being a member of it: crewship backup list --workspace lab. convert works offline on a bundle file.

Subcommands


crewship backup create

Produce a new AGE-encrypted .tar.zst bundle for a workspace or crew.
--recipient is mutually exclusive with --passphrase-file. --no-encrypt is refused before anything reaches the server: vault secrets stay sealed, but agent home folders inside a bundle can hold credentials the vault never sealed (~/.aws, ~/.ssh, tool logins), so a plaintext bundle would be a plaintext copy of them. A workspace bundle also carries the sources, artifacts and Git checkpoints behind any custom Page applications as a page-projects/ section, and a restore refuses the bundle as incomplete when the rows reference one it does not hold — see the bundle layout. The CLI prompts twice for a passphrase to guard against typos. In a non-interactive session without --passphrase-file / --use-keyring, a single line is read from stdin.

crewship backup list

List bundles on disk for the current workspace’s default backup directory.
Output columns: FILE, SCOPE, SIZE, ENCRYPTED, FORMAT, CREATED_AT.

crewship backup download

Stream a backup bundle from the server’s backup directory to a local file. The argument is the bundle’s full path as returned by backup list. Used when the bundle lives on the server (e.g. nightly create on a remote instance) and you need a local copy before restore.
The endpoint sends no-store/no-cache headers and a half-written file is removed on copy failure — partial bundles never land on disk. An instance bundle (from crewship admin instance backups run --scope instance, or a Complete recovery plan) belongs to no workspace, so the per-workspace route answers 404 for it. download, inspect and verify then ask the instance route (GET /api/v1/admin/instance/backups/bundles/download, …/inspect, …/verify), which serves any bundle in the catalog to an instance administrator — use the path crewship admin instance backups bundles or runs shows.

crewship backup inspect

Print the plaintext MANIFEST of a bundle — format_version, scope, timestamps, source instance, SHA-256 of the sealed payload. Never touches the encrypted payload, so no passphrase is needed.
An instance bundle is read through the instance route (see download); the manifest is plaintext there too.

crewship backup verify

Recompute the SHA-256 of the sealed payload and check it against the manifest — confirms the bundle was not truncated or tampered with. For an unencrypted bundle it also checks completeness: the payload’s actual per-table row counts against the counts the manifest recorded when the bundle was written. A table whose counts diverge (a create-side scoping bug, or corruption that kept the checksum) makes the bundle INVALID, and each mismatch is listed as table: manifest recorded N row(s), payload carries M. Never restores anything.
Three outcomes, and the difference matters:
checksum only is printed whenever the row-count comparison could not run — an encrypted bundle (verify never decrypts), or a bundle written before the manifest carried table_row_counts — and the warning names why. It means not evaluated, never “complete”. backup restore --dry-run decrypts and makes the same comparison. --format json carries the raw fields: valid, size_bytes, error, completeness_checked, completeness_skip_reason, table_row_count_mismatches, scope_shortfalls (see the API reference). Exit status is non-zero on INVALID. A workspace bundle also records, at create time, whether every row the schema ties to the workspace was exported (manifest.contents.scope_reconciliation). verify fails a bundle that recorded a shortfall, encrypted or not, and lists each as table: N of M workspace row(s) in the source were not exported. That is a scope bug in the backup code, not damage to the file. See What verify checks.

crewship backup convert

Convert a bundle written by an older Crewship to the current format version so the current restore can read it. Runs locally on the bundle file: no server, login or workspace role is involved. restore, verify and inspect print this exact command when they meet a bundle older than they read directly.
The original is only read. A new bundle is written to --out and sealed to the same age recipients the original records, or to the passphrase that opened it, unless --recipient names new ones. The report lists each version step, what it changed, and what the older format never carried and so cannot be recovered; the converted manifest keeps that list under conversion. A bundle already at the current version is reported and nothing is written. Use the global -f json for the report as JSON. See Compatibility for what each version step converts and what it reports.

crewship backup drill

Test-restore an instance bundle in isolation and check it. Runs locally: the same restore crewship recover performs, into a throwaway directory that is deleted afterwards. No server, scheduler, container or network client is started, so no message is sent, no webhook called and no model run paid for.
It then checks what a real recovery is judged on: The result is ok, partial (every check that did not pass is listed; a bundle without a recovery kit is partial unless --env-keys unlocks it) or failed (the restore itself did not complete; exit code non-zero). --post records the result on the server this CLI is logged in to: the bundle becomes proof level 3 (test restore) and the drill lands in the restore history. The bundle must be catalogued there under the same path. --post never sends the stored token to a server other than the one it belongs to.

crewship backup restore

Restore a workspace or crew from a bundle. The server refuses a restore if a workspace (or crew) with the same slug already exists; pass --as-workspace / --as-crew to land the payload under a fresh identity.
Quota service restores also support --recover-services. Ordinary retries keep unknown maintenance fenced. Inspect backup status first: a live producer or writer lease rejects recovery. Recovery stops exactly owned writers before verified image import; a failure never resumes desired service state. The flag does not unlock an unrelated live backup.

Finishing a renamed restore

--as-workspace / --as-crew fork the bundle’s rows under a new identity and cannot land container state in the same call — the crews they create have no containers yet. Three steps:
Step 2 is crew start, not crew provision: --files-only writes each section by exec’ing into the crew’s container, so the container has to be running. Provision only builds the image, which left this recipe one command short. crew start builds the image too when there is none, so it covers both. --files-only is authorised by provenance, not by the flag: step 1 records which bundle the new workspace came from (matched on the bundle’s payload digest), and step 3 is permitted only for a workspace that record covers. A bundle never restored into your current workspace is refused with the same 403 as before. It also writes to the crews step 1 created, not the crews the bundle names — the manifest still carries the source crew’s slug, so on a same-instance restore the distinction is what keeps a sibling’s backup from being written over the source crew’s live data. A fork regenerates identity, not just primary keys: it also mints a new value for every column that must be unique across the whole instance rather than within a workspace — a mission’s trace_id today — so the fork can live alongside the source instead of colliding with it and losing the row. Columns that are unique only per workspace (a crew slug, an issue identifier like ENG-14) carry across unchanged, because the workspace id in their key was remapped already. A forked restore also re-signs the workspace’s journal hash chain. --as-workspace / --as-crew regenerate the ids that chain’s HMAC commits to, so the bundle’s signatures can no longer be checked against the rows they cover; the restore recomputes them under this installation’s key and the fork starts at a new genesis. It prints what it re-signed, and writes the same fact into the fork’s own journal as a backup.chain_resigned entry:
--dry-run reports the same counts as “would be re-signed” and writes nothing, so you can see whether a restore is about to give the workspace a new genesis before you commit to it. The practical consequence: crewship journal verify -w <fork> reports ok (before this it called every restored row tampered), but it is attesting to this instance from the restore onward, not to the source workspace’s history. Verify the source on the source. A plain or --replace restore remaps nothing, re-signs nothing, and keeps the bundle’s original signatures — the counts are 0 there. If the chain cannot be re-signed the restore fails outright, before any row lands, rather than producing a fork whose journal reads as tampered. See Backup & restore.
The server refuses a restore if a workspace (or crew) with the same slug already exists. Use --as-workspace / --as-crew to land the payload under a fresh identity, or --dry-run to validate before cutover.
A dry-run leaves only the audit row — no DB rows, no docker changes. Use it to prove a bundle is restorable before cutover. Restore also warns when the bundle carries columns this instance’s schema does not have — they are dropped, and any row that needed one of them to satisfy a NOT NULL or a primary key is dropped with them, silently, by INSERT OR IGNORE. The warning names each table.column and how many rows carried it; --dry-run reports the same thing before anything is written. See Backup & restore.

crewship backup rotate

Retention sweep. Either flag on its own is valid. --keep-last is a floor: the N newest bundles are always kept, however old. --keep-days applies only beyond that floor; on its own the floor is one bundle, so a rotation never deletes the last copy. Pinned bundles are never deleted.
Rotation applies per workspace — it never touches another workspace’s bundles, and counts crew bundles separately from workspace bundles.

crewship backup delete

Remove a single bundle from disk.
A bundle may be the only authoritative copy of a workspace at that point in time. Interactive confirmation is required unless --force is passed.

crewship backup status

Show the advisory lock state for the current workspace — who holds it, when it was acquired, and when the 1-hour TTL expires.
Output columns: WORKSPACE, ACQUIRED_BY, ACQUIRED_AT, EXPIRES_AT.

crewship backup unlock

Force-release a stale backup lock. Emergency only — use when the previous CLI session crashed and the 1-hour TTL has not yet fired.
Forcibly releasing a live backup’s lock lets a second backup start alongside it; the two will race on the Docker pause / unpause sequence. Confirm no backup is actually running before invoking this.

crewship backup self-test

Run the server-side canary round-trip (collect → destroy → restore → verify → cleanup) on a crew’s container without producing a bundle on disk. Quick way to validate the backup pipeline end-to-end after upgrading the agent runtime image or after restoring an instance.
Output is the server’s raw JSON: an "ok" boolean and per-stage timings. Non-zero exit if any stage of the round-trip fails.

crewship backup metrics

Dump the in-memory backup counters — created / failed totals, duration quantiles, lock-held gauge. These are process-lifetime values that reset on a restart of crewshipd, so they’re useful for a quick health read after a long-running run, not for historical reporting (use the journal for that).
Output is pretty-printed JSON.
Instance administrators only, server-side.

See also