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

# Next.js

> Deploy a Next.js application to Magic Containers

This guide walks you through building and deploying a Next.js 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 Next.js app

Create a new Next.js project:

```bash theme={null}
npx create-next-app@latest app-nextjs
cd app-nextjs
```

### Configure standalone output

Update your `next.config.ts` to enable standalone output mode for Docker:

```typescript next.config.ts theme={null}
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;
```

### Create an API route

Create a simple API route to test the deployment:

```typescript app/api/route.ts theme={null}
import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({ message: "Hello from Bunny 🐰" });
}
```

## Run locally

Start the development server:

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

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

```bash theme={null}
curl http://localhost:3000/api
```

## Create the Dockerfile

```dockerfile Dockerfile theme={null}
FROM node:22-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
RUN if [ -d "/app/public" ]; then cp -r /app/public ./public; fi

ENV PORT=80
EXPOSE 80

CMD ["node", "server.js"]
```

### Pass build-time variables

`next build` inlines `NEXT_PUBLIC_` variables and bakes in anything `next.config.ts` reads, and `docker build` doesn't read your shell's environment. Declare each one as an `ARG` in the `builder` stage, or the build runs without it:

```dockerfile Dockerfile theme={null}
FROM base AS builder
WORKDIR /app
ARG NEXT_PUBLIC_CDN_URL
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
```

Pass the value with `docker build --build-arg NEXT_PUBLIC_CDN_URL=https://myapp.b-cdn.net`, or with `build-args` in `docker/build-push-action`.

## Build and push to GitHub Container Registry

> [tab] 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-nextjs.git
> git push -u origin main
> ```

> [tab] 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-nextjs: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-nextjs:latest
> ```

> [!NOTE]
>
> If your package is private, set the visibility to **Public** in GitHub or
> [configure Magic Containers with registry
> credentials](https://bunny.net/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](https://bunny.net/docs/magic-containers/quickstart.md).

## Test your app

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

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

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

> [!NOTE]
>
> You can [add a custom hostname](https://bunny.net/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](https://bunny.net/docs/optimizer/dynamic-images/overview.md) there, and `next/image` resizes images at the edge.

**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](https://bunny.net/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**. Next.js already minifies CSS and JavaScript, so turn Optimizer's minification off.

**2. Add the image loader**

The loader turns each `next/image` width into an Optimizer URL. With `NEXT_PUBLIC_CDN_URL` unset, it returns relative URLs that go through the same pull zone.

```typescript lib/bunny-loader.ts theme={null}
"use client";

import type { ImageLoaderProps } from "next/image";

const cdn = process.env.NEXT_PUBLIC_CDN_URL;

export default function bunnyLoader({ src, width, quality }: ImageLoaderProps) {
  const url = new URL(src, cdn || "http://localhost");
  const params = url.searchParams;

  if (!params.has("width")) params.set("width", String(width));

  if (!params.has("quality")) params.set("quality", String(quality ?? 75));

  const absolute = cdn || /^https?:\/\//.test(src);
  return absolute ? url.href : url.pathname + url.search;
}
```

**3. Point Next.js at the loader**

```typescript next.config.ts theme={null}
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
  images: {
    loader: "custom",
    loaderFile: "./lib/bunny-loader.ts",
  },
};

export default nextConfig;
```

Rebuild and push the image. [Smart Cache](https://bunny.net/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 Next.js on another host, see [Serve Next.js from Bunny CDN](https://bunny.net/docs/cdn/integrations/frameworks/nextjs.md).

## Connect a database

You can connect your app to [Bunny Database](https://bunny.net/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 app/api/users/route.ts theme={null}
import { NextResponse } from "next/server";
import { createClient } from "@libsql/client/web";

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

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

  return NextResponse.json(result.rows);
}
```

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

## Next steps

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


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