Setup

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.

Platform Path
Linux ~/.config/dropbear/env
macOS ~/Library/Application Support/dropbear/env
$ 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.