> ## Documentation Index
> Fetch the complete documentation index at: https://bunny.net/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy Astro to Bunny Storage

> Build an Astro site and serve it from Bunny Storage through Bunny CDN, with the Bunny CLI or by hand.

Astro builds static HTML by default, and writes every page to `dist`.

| Setting | Value |
| - | - |
| Build command | `npm run build` |
| Output directory | `dist` |

## Deploy your site

**Bunny CLI**

> [!WARNING]
>
> `bunny sites` is in [Public Preview](/docs/product-release-stages.md#stage-2-public-preview)
> and available only in the Bunny CLI. It's in active development, so commands,
> flags, and behaviour may change. Share feedback on
> [Discord](https://discord.gg/bunnynet). The storage zone and pull zone behind
> each site appear in the dashboard under **Storage** and **CDN**.

**1. Install the Bunny CLI and log in**

```bash theme={null}
curl -fsSL https://cli.bunny.net/install.sh | sh
bunny login
```

See [Installation](/docs/cli/installation.md) for npm and other options.

**2. Create a site**

From your project root, run:

```bash theme={null}
bunny sites create my-site
```

Replace `my-site` with a name for your site. The CLI creates a storage zone for your files and a pull zone to serve them, then links this directory to the new site. The site is served at a `b-cdn.net` address, and the CLI prints the URL.

**3. Build and deploy**

```bash theme={null}
bunny sites deploy --build
```

The CLI runs the build command, uploads the output directory from the table above, and publishes it. Every deploy is kept, so you can roll back with `bunny sites deployments publish --previous`.

**4. Deploy on every push (optional)**

```bash theme={null}
bunny sites ci init
```

This writes a GitHub Actions workflow to `.github/workflows/bunny-sites.yml` that deploys the site on every push to `main`. Add your API key to the repository as a `BUNNYNET_API_KEY` secret. The CLI offers to run `gh secret set BUNNYNET_API_KEY` for you.

**Manually**

**1. Build your site**

From your project root, run `npm run build`. The build writes your site to the output directory listed in the table above.

**2. Create a storage zone**

In the [bunny.net dashboard](https://dash.bunny.net), go to **Storage** and click **Add Storage Zone**. Give it a name, pick the main region closest to your audience, and click **Add Storage Zone**.

**3. Upload your files**

Open the storage zone's file manager and upload the contents of your output directory to the root of the zone. To script uploads, use the [Storage HTTP API](/docs/storage/http.md).

**4. Create a pull zone**

Go to **CDN** and click **Add Pull Zone**. Set **Origin Type** to **Storage Zone**, select the zone you created, and click **Add Pull Zone**.

Your site is live at `https://<pull-zone-name>.b-cdn.net`.

## Resize images with Bunny Optimizer

Astro's `<Image>` component can hand every width to [Bunny Optimizer](/docs/optimizer/dynamic-images/overview.md) through a custom image service. The build then skips image processing, and the edge resizes each width in the `srcset`.

**1. Turn on Optimizer**

`bunny sites create` leaves query strings and Optimizer off, so open the site's pull zone under **CDN → Pull Zones**:

* Turn on [URL Query String](/docs/cdn/vary-cache.md#url-query-string) vary, so each `?width=` gets its own cache entry.
* Enable [Bunny Optimizer](/docs/optimizer/quickstart.md), then turn on the Dynamic Image API and automatic WebP under **Optimizer → Settings**. Your build has already minified CSS and JavaScript, so turn Optimizer's minification off.

**2. Add the image service**

The service spreads Astro's `baseService`, so Astro still works out the `srcset` widths, and only `getURL` changes. The [example app](https://github.com/BunnyWay/examples/tree/main/cdn/optimizer-astro-static) has the full setup.

```ts src/bunny-image-service.ts theme={null}
import type { ExternalImageService } from "astro";
import { baseService } from "astro/assets";

const service: ExternalImageService<{ baseURL?: string }> = {
  ...baseService,
  getURL(options, imageConfig) {
    const base = imageConfig.service.config?.baseURL;
    const src = typeof options.src === "string" ? options.src : options.src.src;

    const absolute = /^(https?:)?\/\//.test(src);
    if (absolute && !(base && src.startsWith(base))) return src;

    const url = new URL(src, base || "http://localhost");
    if (options.width) url.searchParams.set("width", String(options.width));
    url.searchParams.set("quality", String(options.quality ?? 75));
    return base || absolute ? url.href : url.pathname + url.search;
  },
};

export default service;
```

```js astro.config.mjs theme={null}
import { defineConfig } from "astro/config";

export default defineConfig({
  image: { service: { entrypoint: "./src/bunny-image-service.ts" } },
});
```

**3. Add images**

Import images from `src/assets`, or reference files in `public` by path:

```astro theme={null}
---
import { Image } from "astro:assets";
import photo from "../assets/photo.jpg";
---
<Image src={photo} alt="Photo" layout="constrained" />
<Image src="/images/banner.jpg" alt="Banner" width={1200} height={600} layout="constrained" />
```

`layout` picks the `srcset` widths, and each one becomes a URL such as `/_astro/photo.a1b2c3.jpg?width=828&quality=75`. The build copies each image once and resizes nothing.

## Add a custom domain

With the Bunny CLI, add the domain to your site:

```bash theme={null}
bunny sites domains add example.com
```

The CLI prints the `CNAME` record to create with your DNS provider. Once the record resolves, issue a free certificate with `bunny sites domains ssl example.com`.

For a manual deploy, add the hostname to your pull zone by following [Custom hostnames](/docs/cdn/custom-hostname.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.