bunny sites hosts a site on bunny.net. Each site is three resources, provisioned and wired together for you:
Zones are named
sites-<name>-<suffix>. The prefix groups them in the dashboard. The suffix is there because zone names are global across bunny.net. The commands take the clean site name.
The Edge Script is the router this CLI writes. It maps each request to the deploy that answers it, and it serves the deploy’s own 404.html, _redirects and _headers.
A build that renders pages per request is a different shape, and this command
does not deploy one.
bunny lab deploy astro deploys an
Astro project as an Edge Script, with its client build in Bunny Storage.Deploy this project
1
Log in
2
Run the command in your project
3
Add your domain
The site’s first deploy offers one. To add it later:
Deploy a directory
1
Deploy
2
Roll back, when you need to
Immutable deploys
Every deploy uploads to its own directory in the storage zone, and stays there. Publishing only moves the router’s pointer, so any past deploy goes live again with nothing re-uploaded. A deploy serves from the site’s root, not under a path prefix. So a client-side router and a root-absolute asset work with no configuration. The deploy ID is the git short SHA when the working tree is clean, and a content hash otherwise. So re-deploying identical content is a no-op. Use--force to override that.
What the deploy configures
The site’s router reads three file names out of the deploy it is serving. Cloudflare Pages and Netlify read the same three. So a build that already writes them works here unchanged, and the router knows no framework._redirects
One rule per line: a path, a target, and an optional status. Lines starting with # are comments.
- The default status is
301.302,303,307and308are read too. A rewrite (200) is not supported. - A trailing
*in the path is captured, and:splatin the target is replaced with it. - A rule applies only where the deploy holds no file at that path, so a real file always wins.
!after the status forces the rule ahead of the file. - A path matches with or without its trailing slash, so
/aboutand/about/are one rule.
_headers
A line starting with / opens a block, and the indented Name: value lines below it belong to that block. Where two blocks match one path, the later one wins that header name.
Caching
The router setsCache-Control on every response, because Bunny Storage sends none for HTML:
Publishing purges the whole zone, so a promoted deploy answers at once. A purge does not reach a browser, which is why a page keeps only the one minute. A file whose name carries a content hash never changes. Name that directory in
_headers, and the browser keeps it for a year.
A site created by an earlier CLI has an earlier router.
bunny sites deploy
republishes it when it lags, and bunny sites upgrade-router does it on
demand.Custom domains
A custom domain is the site’s production URL. Previews never depend on one.Every command
Which site a command acts on
Most commands take the site as an optional positional.deploy, ci init, and deployments publish use --site. Either accepts the site name or its storage zone ID.
With none given, the site resolves in this order:
- The directory’s linked site, in
.bunny/site.json sites.nameinbunny.jsonc- An interactive picker, which offers to link the directory
--output json, no TTY, or --force on a destructive command. A --name is an instruction, so an unattended run with one creates the site and needs nothing else.
Preconfigure the sites block in bunny.jsonc and a deploy needs no arguments:
dir here is an instruction: deploy these files. The command then offers no adapter, and it deploys the directory whatever the project holds.
Site state lives at _bunny/site.json inside the storage zone, which the router never serves. .bunny/site.json is only a local pointer. So a fresh clone can bunny sites link, and go on where the last machine stopped.
In a monorepo, run
bunny sites deploy at the repository root, and it finds
the framework projects below it. Or run it in a project’s own directory to
deploy that one. The package manager comes from the workspace, so a pnpm
monorepo gets pnpm.