Setup
Pre-1.0
Setup is opinionated: credentials live in environment variables only, and the recommended path is to install the daemon as a user service and let it bootstrap your roots from daemon.toml. The shape below is what works today.
1. Get a binary
Either:
- Download a release binary from the releases page and put it on your
PATH. - Build from source: clone the repo, run
just install(requires Go 1.25+).
Confirm:
$ dropbear version
2. Get a bucket
Any S3-compatible bucket will do. Common picks:
- Cloudflare R2 — cheap, zero egress, region is
auto. - Backblaze B2 — cheap, simple S3-compatible interface.
- AWS S3 — the reference.
- MinIO / Garage / SeaweedFS — self-hosted.
Note the endpoint URL, region, and bucket name.
3. Put credentials in the environment
For interactive use of dropbear sync, dropbear init, and friends, Dropbear reads credentials from environment variables. Use whichever name pair your provider expects; Dropbear honors DROPBEAR_S3_* first, then falls back to AWS_*.
export DROPBEAR_S3_ACCESS_KEY=...
export DROPBEAR_S3_SECRET_KEY=...
The daemon installed in step 5 cannot inherit your interactive shell environment — it reads credentials from a separate env file (next step).
4. Drop credentials in the env file
Create the canonical env file the daemon will read at start. systemd and launchd do not inherit interactive shells, so dropbear daemon install refuses to write a service file without it.
$ mkdir -p "$(dirname ~/.config/dropbear/env)" # Linux
# or: mkdir -p ~/Library/Application\ Support/dropbear # macOS
$ install -m 600 /dev/null ~/.config/dropbear/env
$ cat > ~/.config/dropbear/env <<EOF
DROPBEAR_S3_ACCESS_KEY=...
DROPBEAR_S3_SECRET_KEY=...
EOF
$ chmod 600 ~/.config/dropbear/env
The file must be mode 0600 and define non-empty values for both DROPBEAR_S3_ACCESS_KEY and DROPBEAR_S3_SECRET_KEY. KEY=VALUE and export KEY=VALUE are both accepted.
5. Install the daemon
The daemon is the recommended way to run dropbear. It supervises every root in one process, watches the filesystem, polls the bucket for changes from other devices, and syncs on its own. Installing it as a user-level service makes it start at login and restart on crash.
$ dropbear daemon install
This writes the platform-appropriate service file (~/.config/systemd/user/dropbear.service on Linux, ~/Library/LaunchAgents/net.tfks.dropbear.plist on macOS), activates it, and drops a commented-out starter daemon.toml at os.UserConfigDir()/dropbear/daemon.toml if you don't already have one. See Daemon for the full reference. Windows is not supported (yet).
Pick a per-device identifier and export it — daemon.toml will reference it via $DROPBEAR_DEVICE_ID so the file itself stays portable across machines. Both root-id and device-id must match ^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$.
export DROPBEAR_DEVICE_ID=laptop # alongside your DROPBEAR_S3_* exports
6. Declare your roots in daemon.toml
Edit the starter daemon.toml and add a [[roots]] block per folder you want synced. Include a [roots.init] sub-table so the daemon can create the root on first start — no dropbear init required.
[[roots]]
path = "~/Pictures"
[roots.init]
root_id = "photos"
device_id = "$DROPBEAR_DEVICE_ID"
mode = "bidirectional"
[roots.init.remote]
bucket = "my-bucket"
endpoint = "https://abc.r2.cloudflarestorage.com"
region = "auto"
Every string field — including path — runs through ~ and $VAR/${VAR} expansion. An unset env var that leaves a field empty is rejected at parse time. The device_id env-var pattern is the load-bearing reason this file is portable: you can commit it to a dotfiles repo, and each host fills in its own identifier from the environment.
7. Start syncing
Restart the daemon to pick up the new config:
# macOS
$ launchctl kickstart -k gui/$UID/net.tfks.dropbear
# Linux
$ systemctl --user restart dropbear
On startup the daemon MkdirAlls any missing root path, writes root.toml + .dropbear/state.sqlite, emits a "bootstrapped root" log record at WARN, and starts ticking. The first tick uploads every file in the root as a content-addressed blob, writes a manifest, and publishes a head; subsequent ticks only upload changes.
Watch it work:
$ dropbear daemon status # exit 0 = healthy
# macOS: tail -F ~/Library/Logs/dropbear/out.log
# Linux: journalctl --user -u dropbear -f
8. Add a second device
On the other machine: export your credentials and a fresh DROPBEAR_DEVICE_ID, install the daemon, and reuse (or rewrite) the same daemon.toml with the same root_id and remote.* but pointing at a new, empty local path. On startup the bootstrap creates root.toml and the first tick downloads every blob the first device's manifest references. After that, normal ticks are bidirectional.
If you'd rather pre-populate the new device before letting the daemon take over: skip the [roots.init] block on this host, run dropbear init + dropbear restore --from laptop <path> manually, then add the plain [[roots]] entry to daemon.toml and restart.
9. Alternative: manual dropbear init
If you'd rather not use the daemon — or want explicit control over root creation — you can run dropbear init per root and trigger dropbear sync yourself:
$ dropbear init \
--root-id photos \
--device-id laptop \
--mode bidirectional \
--remote-bucket my-bucket \
--remote-endpoint https://abc.r2.cloudflarestorage.com \
--remote-region auto \
~/Pictures
$ dropbear sync ~/Pictures
Drive subsequent syncs however you like: cron / systemd timer / launchd / shell hooks / external file watcher wrapping dropbear sync / manually. The daemon does the watcher path better, but the CLI works fine on its own.
Verification
$ dropbear status ~/Pictures # show configured state
$ dropbear scan ~/Pictures # what's changed since last sync
$ dropbear plan ~/Pictures # what next sync would do (dry-run)
Where things live
~/Pictures/ your synced folder
├── ... your files
└── .dropbear/
├── root.toml identity + remote config (committed to git? no — it has the bucket endpoint)
├── state.sqlite local state (never commit; never share between devices)
├── ignore optional .gitignore-shaped exclude file
└── restore-tmp/ transient; cleaned automatically
In the bucket:
<prefix>/roots/<root-id>/
├── blobs/<ab>/<cd>/<sha256> content-addressed blobs (deduplicated, sharded)
└── devices/
├── registry.json active + retired devices
└── <device-id>/
├── head.json pointer to this device's latest manifest
├── manifests/<ts>.json full JSON snapshots, one per sync run
└── tombstones/<seq>.json explicit-delete records (per-device monotonic seq)
If you ever need to walk away, this layout is open. You can aws s3 cp the bucket somewhere else and the manifests are plain JSON you can parse with any tool.