> ## 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.

# Astro

> Deploy an Astro application to Magic Containers

This guide walks you through building and deploying an Astro application to Magic Containers with GitHub Container Registry. You'll need:

* A GitHub account for source code and container registry

> [!TIP]
>
> Don't have a bunny.net account yet? [Sign up](https://dash.bunny.net/auth/register) and enable Magic Containers to get started.

## Create the Astro app

Create a new Astro project:

```bash theme={null}
npm create astro@latest app-astro
cd app-astro
```

### Add the Node adapter

Astro requires an adapter for server-side rendering. Install the Node adapter:

```bash theme={null}
npx astro add node
```

This updates your `astro.config.mjs` to include the adapter:

```javascript astro.config.mjs theme={null}
import { defineConfig } from "astro/config";
import node from "@astrojs/node";

export default defineConfig({
  output: "server",
  adapter: node({
    mode: "standalone",
  }),
});
```

### Create an API route

Create a simple endpoint to test the deployment:

```typescript src/pages/index.json.ts theme={null}
import type { APIRoute } from "astro";

export const GET: APIRoute = () => {
  return new Response(JSON.stringify({ message: "Hello from Bunny 🐰" }));
};
```

## Run locally

Start the development server:

```bash theme={null}
npm run dev
```

Visit [http://localhost:4321](http://localhost:4321) in your browser, or test the API route:

```bash theme={null}
curl http://localhost:4321/index.json
```

## Create the Dockerfile

```dockerfile Dockerfile theme={null}
FROM node:22-alpine AS base
WORKDIR /app
COPY package*.json ./

FROM base AS deps
RUN npm install

FROM deps AS build
COPY . .
RUN npm run build

FROM base AS runtime
COPY package*.json ./
RUN npm install --omit=dev
COPY --from=build /app/dist ./dist

ENV HOST=0.0.0.0
ENV PORT=80
EXPOSE 80

CMD ["node", "./dist/server/entry.mjs"]
```

## Build and push to GitHub Container Registry

**GitHub Actions**

Create `.github/workflows/build.yml` to automatically build and push on every commit to `main`:

```yaml .github/workflows/build.yml theme={null}
name: Build and Push

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Update container image on Magic Containers
        uses: BunnyWay/actions/container-update-image@main
        with:
          app_id: ${{ vars.APP_ID }}
          api_key: ${{ secrets.BUNNYNET_API_KEY }}
          container: app
          image_tag: "${{ github.sha }}"
```

Push your code to trigger the workflow:

```bash theme={null}
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/YOUR_USERNAME/app-astro.git
git push -u origin main
```

**Docker CLI**

Build and push manually from your local machine.

**1. Create a Personal Access Token**

Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) and create a token with `write:packages` scope.

**2. Log in to GitHub Container Registry**

```bash theme={null}
export CR_PAT=your_personal_access_token
echo $CR_PAT | docker login ghcr.io -u YOUR_USERNAME --password-stdin
```

**3. Build the image**

```bash theme={null}
docker build --platform linux/amd64 -t ghcr.io/YOUR_USERNAME/app-astro:latest .
```

> [!NOTE]
>
> Magic Containers only supports images built for the **linux/amd64** architecture. The `--platform` flag ensures compatibility regardless of your local machine's architecture.

**4. Push to registry**

```bash theme={null}
docker push ghcr.io/YOUR_USERNAME/app-astro:latest
```

> [!NOTE]
>
> If your package is private, set the visibility to **Public** in GitHub or
> [configure Magic Containers with registry
> credentials](/docs/magic-containers/image-registries.md).

## Deploy to Magic Containers

**1. Create a new app**

In the bunny.net dashboard, go to **Magic Containers** and click **Add
App**. Enter a name and select your deployment option.

**2. Add a container**

Click **Add Container**, then configure:

| Field | Value |
| - | - |
| Registry | GitHub Container Registry |
| Image | `YOUR_USERNAME/{imageName}` |
| Tag | `latest` for Docker CLI, or the commit SHA from your GitHub Actions workflow |

**3. Add an endpoint**

Go to the **Endpoints** tab, click **Add New Endpoint**, and set the
container port to `80`.

**4. Deploy**

Click **Add Container**, then **Next Step**, and **Confirm and Create**.

For more details, see the [quickstart guide](/docs/magic-containers/quickstart.md).

## Test your app

Visit your container URL in the browser to see your Astro site, or test the API route:

```bash theme={null}
curl https://mc-xxx.bunny.run/index.json
```

```json Response theme={null}
{ "message": "Hello from Bunny 🐰" }
```

> [!NOTE]
>
> You can [add a custom hostname](/docs/magic-containers/endpoints.md) from the **Endpoints** section in your app settings.

## Add Optimizer

Your CDN endpoint is already a pull zone in front of the app. Turn on [Bunny Optimizer](/docs/optimizer/dynamic-images/overview.md) there, and a custom image service sends every `<Image>` width to the edge for resizing.

**1. Configure the endpoint's pull zone**

Open the pull zone that serves your CDN endpoint under **CDN → Pull Zones**. Turn on [URL Query String](/docs/cdn/vary-cache.md#url-query-string) vary, since endpoint pull zones ignore query strings by default and `?width=1080` would come back as the cached `?width=640` image.

Enable Optimizer, then turn on the Dynamic Image API and automatic WebP under **Optimizer → Settings**. Astro already minifies 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. Without a `baseURL`, it returns relative URLs that go through the same pull zone.

```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;
```

**3. Point Astro at the service**

```javascript astro.config.mjs theme={null}
import { defineConfig } from "astro/config";
import node from "@astrojs/node";

export default defineConfig({
  output: "server",
  adapter: node({ mode: "standalone" }),
  image: { service: { entrypoint: "./src/bunny-image-service.ts" } },
});
```

Rebuild and push the image. [Smart Cache](/docs/cdn/smart-cache.md) is on for endpoint pull zones, so the edge skips HTML and deploys need no purge.

To put a pull zone in front of Astro on another host, see [Serve Astro from Bunny CDN](/docs/cdn/integrations/frameworks/astro.md).

## Connect a database

You can connect your app to [Bunny Database](/docs/database.md) directly from the dashboard:

1. Go to **Database > \[Your Database] > Access**
2. Click **Generate Tokens**
3. Click **Add Secrets to Magic Container App**
4. Select your app

The `BUNNY_DATABASE_URL` and `BUNNY_DATABASE_AUTH_TOKEN` environment variables are now available in your app:

```typescript src/pages/api/users.ts theme={null}
import type { APIRoute } from "astro";
import { createClient } from "@libsql/client/web";

const client = createClient({
  url: import.meta.env.BUNNY_DATABASE_URL,
  authToken: import.meta.env.BUNNY_DATABASE_AUTH_TOKEN,
});

export const GET: APIRoute = async () => {
  const result = await client.execute("SELECT * FROM users");

  return new Response(JSON.stringify(result.rows), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
    },
  });
};
```

See the [TypeScript SDK documentation](/docs/database/connect/typescript.md) for more details.

## Next steps

1. [Automate deploys with GitHub Actions](/docs/magic-containers/deploy-with-github-actions.md)
2. [Add a custom hostname](/docs/magic-containers/endpoints.md)
3. [Add a persistent volume](/docs/magic-containers/persistent-volumes.md)


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