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