devsafe backup
Create an encrypted, user-owned backup of a git repository. Your code is bundled, encrypted on your machine, and uploaded to storage you control. Paid
What it does
devsafe backup performs three operations in sequence:
- Bundle. Creates a git bundle from the repository's object database. This captures all commits, branches, and tags in a single portable file. The command reads from git's internal structures and never writes to
.git/. - Encrypt. Encrypts the bundle using AES-256-GCM with a fresh, random nonce (nonce-unique AEAD). The encryption key is derived from your master key using HKDF-SHA256. Your key never leaves your machine.
- Upload. Sends the encrypted bundle to your user-owned storage (any S3-compatible provider such as Cloudflare R2, AWS S3, MinIO, or Backblaze B2). DevSafe never stores your data on its own servers.
The result is a single encrypted file in your storage bucket. Only someone with your key can read it. The storage provider sees random bytes.
Usage
$ devsafe backup <repo-path> [--key <hex>]
If you have already configured a default key with devsafe key new, the --key flag is optional. DevSafe will use the active key automatically.
Example
$ devsafe backup ~/projects/api-server → creating git bundle (all refs)... → bundle size: 24.7 MB → encrypting with AES-256-GCM (key: prod-2026)... → uploading to r2://devsafe-backups/api-server/committed/000042.enc ✓ backup complete (24.7 MB, 1.2s) ✓ chain hash: 7f3a...c891
Flags
| Flag | Description |
|---|---|
| --key <hex> | Use a specific encryption key instead of the default active key. |
| --dry-run | Show what would be backed up without creating or uploading anything. |
| --verbose | Print detailed progress, including bundle creation, encryption, and upload steps. |
| --incremental | Create an incremental git bundle containing only objects added since the last backup. Reduces upload size for large repositories. |
| --snapshot | Also capture uncommitted work (index, working tree, untracked files, stash, and in-progress operations). See Snapshots below. |
devsafe backup-all
devsafe backup-all discovers every git repository under one or more root directories and backs them all up in a single run. It uses the same discovery logic as devsafe scan.
$ devsafe backup-all ~/projects ~/work → discovered 23 repositories → backing up api-server... ✓ api-server (24.7 MB, 1.2s) → backing up design-tokens... ✓ design-tokens (3.1 MB, 0.4s) ... ✓ 23/23 repositories backed up (142 MB total, 18.3s)
backup-all accepts the same flags as backup, including --incremental and --snapshot. It skips repositories that are mid-garbage-collection or have locked pack files.
Capturing uncommitted work
A git bundle only contains committed data. If you have uncommitted changes you want to protect, use devsafe snapshot or pass --snapshot to the backup command.
devsafe snapshot captures five namespaces of uncommitted state:
- Index (staged files)
- Working tree (modified but unstaged files)
- Untracked files (new files not yet added to git)
- Stash (any stashed changes)
- Operation state (in-progress merge, rebase, cherry-pick, or bisect)
Snapshots are encrypted and uploaded separately from the git bundle. They use the same key and the same user-owned storage. Like backup, snapshot reads from git's object database and never writes to .git/.
$ devsafe snapshot ~/projects/api-server → capturing 5 namespaces... ✓ index: 3 staged files ✓ working tree: 7 modified files ✓ untracked: 2 new files ✓ stash: 1 entry ✓ operation state: none ✓ snapshot encrypted and uploaded (0.8 MB, 0.6s)
How encryption works
Every backup uses nonce-unique AEAD. In plain terms: each time DevSafe encrypts a bundle, it generates a fresh random nonce (a one-time number). This nonce is combined with your key to produce a unique encryption for that specific backup. Even if you back up the same repository twice with no changes, the encrypted output will be different.
The key hierarchy works like this:
- Your master key is generated locally and stored in your keychain.
- A per-repo key is derived from the master key using HKDF-SHA256 and the repository's ID.
- A per-bundle key is derived from the per-repo key using the sequence number and timestamp.
This means that even if a single per-bundle key were compromised, it could not decrypt any other backup. For large repositories, DevSafe uses streaming encryption so the entire bundle never needs to fit in memory.
For a deeper explanation, see Encryption model.
Read-only by design
devsafe backup reads from git's object database. It never writes to .git/, never creates commits in your repository, never advances HEAD, and never touches the reflog. Your repository is exactly the same before and after a backup runs.
This is what makes DevSafe immune to the documented corruption problems with iCloud, Dropbox, and OneDrive. Those services corrupt .git/ directories through lockfile races, partial pack writes, and ref pointer overwrites. DevSafe avoids all of that by reading the object database and producing a single encrypted file that goes to your user-owned storage.
You can run devsafe backup while you are actively working, during a rebase, or in the middle of a merge. It will not interfere with your workflow.
Install
If you have not installed DevSafe yet:
$ brew install hxalabs/tap/devsafe
See the Quickstart for full installation instructions on all platforms.