I run this website and a few other projects on Bunny CDN: the production setup is a Storage Zone for hosting all assets and a Pull Zone for CDN services. After each build I need the same loop over and over — walk the local output tree, compare it to what Bunny already has, upload what changed, and optionally remove objects that no longer exist locally. That workflow is second nature with aws s3 sync; for Bunny’s HTTP storage API I wanted something equally boring and reliable, not a one-off shell script.
I wrote bunsy (bunny sync) for that: a small Rust CLI that talks to Bunny’s storage API, does size-based diffing, runs uploads and deletes with bounded concurrency, and can show a live progress UI on a real terminal. A Docker image sbstjn/bunsy (based on debian:bookworm-slim) is on Docker Hub so CI can run the same binary without installing Rust. The sources live on Codeberg under the MIT licence.
Getting Started
The storage zone password is the same value Bunny shows under FTP & API Access in the dashboard. Export it as BUNNY_STORAGE_ZONE_PASSWORD (bunsy does not read CLI flags for secrets).
Basic usage: pass the local directory and the remote target. The remote argument is either the zone name alone or zone-name/path/inside/zone:
$ > BUNNY_STORAGE_ZONE_PASSWORD='…' \
bunsy ./dist my-website-files
$> BUNNY_STORAGE_ZONE_PASSWORD='…' \
bunsy ./dist my-website-files/assets/v2
The client uses HTTPS against storage.bunnycdn.com by default; you can point --endpoint at a regional hostname if Bunny assigns one for your zone.
Sync Behaviour
bunsy lists the local tree and fetches the remote listing (via the storage API), then builds a plan keyed by relative path and compared by file size only. --checksum verifies uploaded bytes with Bunny, but it does not change diffing. If you want a full re-upload path, use --ignore-size.
In practice:
- Upload if the object is missing remotely, or if local and remote sizes differ.
- Skip if the object exists and the size matches (unless you pass
--ignore-size, which queues every local file for upload even when sizes match — useful when content changed without a size change). - Delete remote objects that have no corresponding local path, unless
--no-deleteis set (then orphans stay on the server).
Uploads run first; deletes run after all uploads finish, so you do not remove remote files while replacements are still in flight.
--dry prints the plan (counts and per-file lines when not quiet) without mutating storage.
Concurrency and Progress
Parallel work is bounded by a fixed worker pool: --concurrency defaults to 8.
For long runs on an interactive terminal, --progress draws an overall bar plus one line per active slot on stderr (via indicatif). --progress and --quiet / -q are mutually exclusive — the application exits with a configuration error if both are set. For the opposite direction, --verbose / -v raises the log level to debug (retries, per-file tracing spans) and conflicts with --quiet.
$ > BUNNY_STORAGE_ZONE_PASSWORD='…' \
bunsy ./dist my-zone --progress --concurrency 12
Checksum Verification
Bunny accepts a Checksum header on PUT with uppercase SHA-256 hex. With --checksum, bunsy hashes each file before upload and sends that header so the API can reject corrupted transfers.
$ > BUNNY_STORAGE_ZONE_PASSWORD='…' \
bunsy ./dist my-zone --checksum
There is a real cost: every queued upload does an extra full-file read for hashing before the streamed upload begins. For large trees, use it when you care about integrity guarantees or debugging flaky networks.
CI Integration
The Docker image packages the same CLI as the published crate. In Woodpecker (or similar), wire the password from a secret and invoke bunsy after your build step:
# Woodpecker: publish artefacts after build
publish:
image: sbstjn/bunsy
commands:
- bunsy ./dist my-website-files
environment:
BUNNY_STORAGE_ZONE_PASSWORD:
from_secret: BUNNY_STORAGE_ZONE_PASSWORD
when:
event: [push]
depends_on:
- build
./dist is whatever directory your pipeline produces; adjust my-website-files to your storage zone name (and optional sub-path).
If Bunny-powered static hosting is part of your workflow, bunsy is available on Codeberg.