Preview Environments for Next.js: Automated Per-PR Deployments with Bunnyshell
Why Preview Environments for Next.js?
Every Next.js team hits the same wall: you build a feature with server components and API routes, test it locally with next dev, push to staging — and it breaks because staging still has someone else's middleware changes. Or your ISR pages cache stale data because two feature branches share the same Redis instance. Or the reviewer asks you to "deploy it somewhere" so they can test the OAuth flow, and you spend an hour setting up a Vercel preview that doesn't match your production Kubernetes setup.
This guide is not about Next.js Preview Mode (the CMS draft content feature). This is about deploying a full, isolated copy of your entire Next.js application — app server, database, Redis — for every pull request.
With Bunnyshell, you get:
- Automatic deployment — A new environment spins up for every PR
- Production parity — Same Docker images, same database engine, same server-side rendering behavior
- Isolation — Each PR environment is fully independent, no shared staging conflicts
- Automatic cleanup — Environments are destroyed when the PR is merged or closed
Choose Your Approach
Bunnyshell supports three ways to set up preview environments for Next.js. Pick the one that fits your workflow:
| Approach | Best for | Complexity | CI/CD maintenance |
|---|---|---|---|
| Approach A: Bunnyshell UI | Teams that want the fastest setup with zero pipeline maintenance | Easiest | None — Bunnyshell manages webhooks automatically |
| Approach B: Docker Compose Import | Teams already using docker-compose.yml for local development | Easy | None — import converts to Bunnyshell config automatically |
| Approach C: Helm Charts | Teams with existing Helm infrastructure or complex K8s needs | Advanced | Optional — can use CLI or Bunnyshell UI |
All three approaches end the same way: a toggle in Bunnyshell Settings that enables automatic preview environments for every PR. No GitHub Actions, no GitLab CI pipelines to maintain — Bunnyshell adds webhooks to your Git provider and listens for PR events.
Prerequisites: Prepare Your Next.js App
Regardless of which approach you choose, your Next.js app needs a proper Docker setup and the right configuration for running in Kubernetes.
1. Enable Standalone Output
This is the most critical step. In your next.config.js (or next.config.mjs), enable standalone output:
1/** @type {import('next').NextConfig} */
2const nextConfig = {
3 output: 'standalone',
4}
5
6module.exports = nextConfigWithout output: 'standalone', your Docker image will include the entire node_modules directory (hundreds of megabytes). Standalone mode produces a self-contained server with only the files needed to run — typically under 50 MB.
2. Create a Production-Ready Dockerfile
Next.js with standalone output runs as a single Node.js process — no Nginx sidecar needed. Here is a multi-stage Dockerfile:
1# ── Stage 1: Install dependencies ──
2FROM node:20-alpine AS deps
3RUN apk add --no-cache libc6-compat
4WORKDIR /app
5
6COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
7
8RUN \
9 if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
10 elif [ -f package-lock.json ]; then npm ci; \
11 elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm install --frozen-lockfile; \
12 else echo "No lockfile found." && exit 1; \
13 fi
14
15# ── Stage 2: Build the application ──
16FROM node:20-alpine AS builder
17WORKDIR /app
18
19COPY /app/node_modules ./node_modules
20COPY . .
21
22# Generate Prisma client if schema exists
23RUN if [ -f prisma/schema.prisma ]; then npx prisma generate; fi
24
25ENV NEXT_TELEMETRY_DISABLED=1
26RUN npm run build
27
28# ── Stage 3: Production image ──
29FROM node:20-alpine AS runner
30WORKDIR /app
31
32ENV NODE_ENV=production
33ENV NEXT_TELEMETRY_DISABLED=1
34
35RUN addgroup --system --gid 1001 nodejs
36RUN adduser --system --uid 1001 nextjs
37
38# Copy static assets and standalone server
39COPY /app/public ./public
40COPY /app/.next/standalone ./
41COPY /app/.next/static ./.next/static
42
43# Copy Prisma migrations if they exist
44COPY /app/prisma ./prisma 2>/dev/null || true
45COPY /app/node_modules/.prisma ./node_modules/.prisma 2>/dev/null || true
46COPY /app/node_modules/@prisma ./node_modules/@prisma 2>/dev/null || true
47
48USER nextjs
49
50EXPOSE 3000
51
52# HOSTNAME must be set to 0.0.0.0 for Next.js 14+ to listen on all interfaces
53ENV HOSTNAME=0.0.0.0
54ENV PORT=3000
55
56CMD ["node", "server.js"]The HOSTNAME=0.0.0.0 environment variable is critical for Next.js 14 and later. Without it, the standalone server only listens on 127.0.0.1, making it unreachable from outside the container. This is the number one reason Next.js containers fail health checks in Kubernetes.
3. Create a Health Check API Route
Create app/api/health/route.ts (App Router) or pages/api/health.ts (Pages Router):
1// app/api/health/route.ts (App Router)
2import { NextResponse } from 'next/server'
3
4export async function GET() {
5 return NextResponse.json({ status: 'ok', timestamp: Date.now() })
6}
7
8export const dynamic = 'force-dynamic'This gives Kubernetes a reliable endpoint for liveness and readiness probes.
4. Environment Variables
Next.js has a split environment variable model:
NEXT_PUBLIC_*— Bundled into the client-side JavaScript at build time- All other variables — Available only on the server (API routes, server components, middleware)
Update your .env.example:
1# Server-side only
2DATABASE_URL=postgresql://postgres:password@postgres:5432/nextapp
3NEXTAUTH_URL=https://example.com
4NEXTAUTH_SECRET=
5REDIS_URL=redis://redis:6379
6
7# Client-side (bundled at build time)
8NEXT_PUBLIC_API_URL=https://example.com/api
9NEXT_PUBLIC_APP_NAME=MyAppBecause NEXT_PUBLIC_* variables are inlined at build time, Bunnyshell must set them as build args or rebuild the image when the preview URL changes. For truly dynamic client-side config, use a runtime approach like fetching from an /api/config endpoint instead.
Next.js Deployment Checklist
-
output: 'standalone'enabled innext.config.js - Multi-stage Dockerfile with Node 20 Alpine
-
HOSTNAME=0.0.0.0set for container networking - Health check endpoint at
/api/health -
NEXT_PUBLIC_*vars documented (they are build-time only) -
NEXTAUTH_URLor equivalent will use Bunnyshell interpolation - Prisma client generation included in build stage
- Static assets copied to standalone output
- Non-root user (
nextjs) for security
Approach A: Bunnyshell UI — Zero CI/CD Maintenance
This is the easiest approach. You connect your repo, paste a YAML config, deploy, and flip a toggle. No CI/CD pipelines to write or maintain — Bunnyshell automatically adds webhooks to your Git provider and creates/destroys preview environments when PRs are opened/closed.
Step 1: Create a Project and Environment
- Log into Bunnyshell
- Click Create project and name it (e.g., "Next.js App")
- Inside the project, click Create environment and name it (e.g., "nextjs-main")
Step 2: Define the Environment Configuration
Click Configuration in your environment view and paste this bunnyshell.yaml:
1kind: Environment
2name: nextjs-preview
3type: primary
4
5environmentVariables:
6 NEXTAUTH_SECRET: SECRET["your-nextauth-secret"]
7 DB_PASSWORD: SECRET["your-db-password"]
8
9components:
10 # ── Next.js Application ──
11 - kind: Application
12 name: nextjs-app
13 gitRepo: 'https://github.com/your-org/your-nextjs-repo.git'
14 gitBranch: main
15 gitApplicationPath: /
16 dockerCompose:
17 build:
18 context: .
19 dockerfile: Dockerfile
20 args:
21 NEXT_PUBLIC_API_URL: 'https://{{ components.nextjs-app.ingress.hosts[0] }}/api'
22 NEXT_PUBLIC_APP_NAME: 'MyApp Preview'
23 environment:
24 NODE_ENV: production
25 HOSTNAME: '0.0.0.0'
26 PORT: '3000'
27 DATABASE_URL: 'postgresql://nextapp:{{ env.vars.DB_PASSWORD }}@postgres:5432/nextapp'
28 NEXTAUTH_URL: 'https://{{ components.nextjs-app.ingress.hosts[0] }}'
29 NEXTAUTH_SECRET: '{{ env.vars.NEXTAUTH_SECRET }}'
30 REDIS_URL: 'redis://redis:6379'
31 ports:
32 - '3000:3000'
33 dependsOn:
34 - postgres
35 - redis
36 hosts:
37 - hostname: 'app-{{ env.base_domain }}'
38 path: /
39 servicePort: 3000
40
41 # ── PostgreSQL Database ──
42 - kind: Database
43 name: postgres
44 dockerCompose:
45 image: 'postgres:16-alpine'
46 environment:
47 POSTGRES_DB: nextapp
48 POSTGRES_USER: nextapp
49 POSTGRES_PASSWORD: '{{ env.vars.DB_PASSWORD }}'
50 ports:
51 - '5432:5432'
52
53 # ── Redis (Caching / Sessions) ──
54 - kind: Service
55 name: redis
56 dockerCompose:
57 image: 'redis:7-alpine'
58 ports:
59 - '6379:6379'
60
61volumes:
62 - name: postgres-data
63 mount:
64 component: postgres
65 containerPath: /var/lib/postgresql/data
66 size: 1GiKey architecture notes:
- No Nginx sidecar needed — Next.js standalone output includes its own HTTP server on port 3000
NEXT_PUBLIC_*as build args — These are inlined at build time, so they must be set during the Docker build viaargsHOSTNAME: '0.0.0.0'— Required for Next.js 14+ to accept connections from outside the containerNEXTAUTH_URLinterpolation — Ensures OAuth callbacks point to the correct preview URL
Replace your-org/your-nextjs-repo with your actual repository. Save the configuration.
Step 3: Deploy
Click the Deploy button, select your Kubernetes cluster, and click Deploy Environment. Bunnyshell will:
- Build your Next.js Docker image (multi-stage, standalone output)
- Pull PostgreSQL and Redis images
- Deploy everything into an isolated Kubernetes namespace
- Generate HTTPS URLs automatically with DNS
Monitor the deployment in the environment detail page. When status shows Running, click Endpoints to access your live Next.js app.
Step 4: Run Post-Deploy Commands
After deployment, run database setup commands:
1export BUNNYSHELL_TOKEN=your-api-token
2bns components list --environment ENV_ID --output json | jq '._embedded.item[] | {id, name}'
3
4# Run Prisma migrations
5bns exec COMPONENT_ID -c nextjs-app -- npx prisma migrate deploy
6
7# Seed the database (if you have a seed script)
8bns exec COMPONENT_ID -c nextjs-app -- npx prisma db seedStep 5: Enable Automatic Preview Environments
This is the magic step — no CI/CD configuration needed:
- In your environment, go to Settings
- Find the Ephemeral environments section
- Toggle "Create ephemeral environments on pull request" to ON
- Toggle "Destroy environment after merge or close pull request" to ON
- Select the Kubernetes cluster for ephemeral environments
That's it. Bunnyshell automatically adds a webhook to your Git provider (GitHub, GitLab, or Bitbucket). From now on:
- Open a PR — Bunnyshell creates an ephemeral environment with the PR's branch
- Push to PR — The environment redeploys with the latest changes
- Bunnyshell posts a comment on the PR with a link to the live deployment
- Merge or close the PR — The ephemeral environment is automatically destroyed
The primary environment must be in Running or Stopped status before ephemeral environments can be created from it.
Approach B: Docker Compose Import
Already have a docker-compose.yml for local development? Bunnyshell can import it directly and convert it to its environment format. No manual YAML writing required.
Step 1: Add a docker-compose.yml to Your Repo
If you don't already have one, create docker-compose.yml in your repo root:
1version: '3.8'
2
3services:
4 nextjs-app:
5 build:
6 context: .
7 dockerfile: Dockerfile
8 args:
9 NEXT_PUBLIC_API_URL: 'http://localhost:3000/api'
10 NEXT_PUBLIC_APP_NAME: 'MyApp'
11 ports:
12 - '3000:3000'
13 environment:
14 NODE_ENV: production
15 HOSTNAME: '0.0.0.0'
16 PORT: '3000'
17 DATABASE_URL: 'postgresql://nextapp:secret@postgres:5432/nextapp'
18 NEXTAUTH_URL: 'http://localhost:3000'
19 NEXTAUTH_SECRET: 'dev-secret-change-in-production'
20 REDIS_URL: 'redis://redis:6379'
21 depends_on:
22 - postgres
23 - redis
24
25 postgres:
26 image: postgres:16-alpine
27 environment:
28 POSTGRES_DB: nextapp
29 POSTGRES_USER: nextapp
30 POSTGRES_PASSWORD: secret
31 volumes:
32 - postgres-data:/var/lib/postgresql/data
33 ports:
34 - '5432:5432'
35
36 redis:
37 image: redis:7-alpine
38 ports:
39 - '6379:6379'
40
41volumes:
42 postgres-data:Step 2: Import into Bunnyshell
- Create a Project and Environment in Bunnyshell (same as Approach A, Step 1)
- Click Define environment
- Select your Git account and repository
- Set the branch (e.g.,
main) and the path todocker-compose.yml(use/if it's in the root) - Click Continue — Bunnyshell parses and validates your Docker Compose file
Bunnyshell automatically detects:
- All services (nextjs-app, postgres, redis)
- Exposed ports
- Build configurations (Dockerfiles)
- Volumes
- Environment variables
It converts everything into a bunnyshell.yaml environment definition.
The docker-compose.yml is only read during the initial import. Subsequent changes to the file won't auto-propagate — edit the environment configuration in Bunnyshell instead.
Step 3: Adjust the Configuration
After import, go to Configuration in the environment view and update:
Replace hardcoded secrets with SECRET["..."] syntax:
1environmentVariables:
2 NEXTAUTH_SECRET: SECRET["your-nextauth-secret"]
3 DB_PASSWORD: SECRET["your-db-password"]Add dynamic URLs using Bunnyshell interpolation:
NEXTAUTH_URL: 'https://{{ components.nextjs-app.ingress.hosts[0] }}'
DATABASE_URL: 'postgresql://nextapp:{{ env.vars.DB_PASSWORD }}@postgres:5432/nextapp'Update build args for NEXT_PUBLIC_* variables:
1build:
2 args:
3 NEXT_PUBLIC_API_URL: 'https://{{ components.nextjs-app.ingress.hosts[0] }}/api'Step 4: Deploy and Enable Preview Environments
Same as Approach A — click Deploy, then go to Settings and toggle on ephemeral environments.
Best Practices for Docker Compose with Bunnyshell
- No Nginx sidecar needed — Unlike PHP-FPM apps, Next.js standalone output includes its own HTTP server. Keep it as a single container
- Remove local volumes —
volumes: ['.:/app']is for local dev (hot reload). Remove these in Bunnyshell — the Docker image already contains the built app - Use Bunnyshell interpolation for dynamic values:
1# Local docker-compose.yml
2NEXTAUTH_URL: http://localhost:3000
3
4# Bunnyshell environment config (after import)
5NEXTAUTH_URL: 'https://{{ components.nextjs-app.ingress.hosts[0] }}'- Handle
NEXT_PUBLIC_*carefully — These are build-time variables. Pass them as buildargsin the Bunnyshell config so the correct preview URLs are baked into the client bundle
Approach C: Helm Charts
For teams with existing Helm infrastructure or complex Kubernetes requirements (custom ingress, service mesh, advanced scaling). Helm gives you full control over every Kubernetes resource.
Step 1: Create a Helm Chart
Structure your Next.js Helm chart in your repo:
1helm/nextjs/
2├── Chart.yaml
3├── values.yaml
4└── templates/
5 ├── deployment.yaml
6 ├── service.yaml
7 ├── ingress.yaml
8 ├── configmap.yaml
9 └── migration-job.yamlA minimal values.yaml:
1replicaCount: 1
2image:
3 repository: ""
4 tag: latest
5service:
6 port: 3000
7ingress:
8 enabled: true
9 className: bns-nginx
10 host: ""
11env:
12 NODE_ENV: production
13 HOSTNAME: "0.0.0.0"
14 PORT: "3000"
15 DATABASE_URL: ""
16 NEXTAUTH_URL: ""
17 NEXTAUTH_SECRET: ""
18 REDIS_URL: ""Step 2: Define the Bunnyshell Configuration
Create a bunnyshell.yaml using Helm components:
1kind: Environment
2name: nextjs-helm
3type: primary
4
5environmentVariables:
6 NEXTAUTH_SECRET: SECRET["your-nextauth-secret"]
7 DB_PASSWORD: SECRET["your-db-password"]
8 PG_DATABASE: nextapp
9 PG_USER: nextapp
10
11components:
12 # ── Docker Image Build ──
13 - kind: DockerImage
14 name: nextjs-image
15 context: /
16 dockerfile: Dockerfile
17 gitRepo: 'https://github.com/your-org/your-nextjs-repo.git'
18 gitBranch: main
19 gitApplicationPath: /
20 args:
21 NEXT_PUBLIC_API_URL: 'https://app-{{ env.base_domain }}/api'
22 NEXT_PUBLIC_APP_NAME: 'MyApp Preview'
23
24 # ── PostgreSQL via Helm (Bitnami) ──
25 - kind: Helm
26 name: postgres
27 runnerImage: 'dtzar/helm-kubectl:3.8.2'
28 deploy:
29 - |
30 cat << EOF > pg_values.yaml
31 global:
32 storageClass: bns-network-sc
33 auth:
34 postgresPassword: {{ env.vars.DB_PASSWORD }}
35 database: {{ env.vars.PG_DATABASE }}
36 username: {{ env.vars.PG_USER }}
37 password: {{ env.vars.DB_PASSWORD }}
38 EOF
39 - 'helm repo add bitnami https://charts.bitnami.com/bitnami'
40 - 'helm upgrade --install --namespace {{ env.k8s.namespace }}
41 --post-renderer /bns/helpers/helm/bns_post_renderer
42 -f pg_values.yaml postgres bitnami/postgresql --version 13.4.4'
43 - |
44 PG_HOST="postgres-postgresql.{{ env.k8s.namespace }}.svc.cluster.local"
45 destroy:
46 - 'helm uninstall postgres --namespace {{ env.k8s.namespace }}'
47 start:
48 - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }}
49 statefulset/postgres-postgresql'
50 stop:
51 - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }}
52 statefulset/postgres-postgresql'
53 exportVariables:
54 - PG_HOST
55
56 # ── Next.js App via Helm ──
57 - kind: Helm
58 name: nextjs-app
59 runnerImage: 'dtzar/helm-kubectl:3.8.2'
60 deploy:
61 - |
62 cat << EOF > nextjs_values.yaml
63 replicaCount: 1
64 image:
65 repository: {{ components.nextjs-image.image }}
66 service:
67 port: 3000
68 ingress:
69 enabled: true
70 className: bns-nginx
71 host: app-{{ env.base_domain }}
72 env:
73 NODE_ENV: production
74 HOSTNAME: '0.0.0.0'
75 PORT: '3000'
76 DATABASE_URL: 'postgresql://{{ env.vars.PG_USER }}:{{ env.vars.DB_PASSWORD }}@{{ components.postgres.exported.PG_HOST }}:5432/{{ env.vars.PG_DATABASE }}'
77 NEXTAUTH_URL: 'https://app-{{ env.base_domain }}'
78 NEXTAUTH_SECRET: '{{ env.vars.NEXTAUTH_SECRET }}'
79 REDIS_URL: 'redis://redis:6379'
80 EOF
81 - 'helm upgrade --install --namespace {{ env.k8s.namespace }}
82 --post-renderer /bns/helpers/helm/bns_post_renderer
83 -f nextjs_values.yaml nextjs-{{ env.unique }} ./helm/nextjs'
84 destroy:
85 - 'helm uninstall nextjs-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
86 start:
87 - 'helm upgrade --namespace {{ env.k8s.namespace }}
88 --post-renderer /bns/helpers/helm/bns_post_renderer
89 --reuse-values --set replicaCount=1 nextjs-{{ env.unique }} ./helm/nextjs'
90 stop:
91 - 'helm upgrade --namespace {{ env.k8s.namespace }}
92 --post-renderer /bns/helpers/helm/bns_post_renderer
93 --reuse-values --set replicaCount=0 nextjs-{{ env.unique }} ./helm/nextjs'
94 gitRepo: 'https://github.com/your-org/your-nextjs-repo.git'
95 gitBranch: main
96 gitApplicationPath: /helm/nextjs
97
98 # ── Redis ──
99 - kind: Service
100 name: redis
101 dockerCompose:
102 image: 'redis:7-alpine'
103 ports:
104 - '6379:6379'Always include --post-renderer /bns/helpers/helm/bns_post_renderer in your helm commands. This adds labels so Bunnyshell can track resources, show logs, and manage component lifecycle.
Step 3: Deploy and Enable Preview Environments
Same flow: paste the config in Configuration, hit Deploy, then enable ephemeral environments in Settings.
Enabling Preview Environments (All Approaches)
Regardless of which approach you used, enabling automatic preview environments is the same:
- Ensure your primary environment has been deployed at least once (Running or Stopped status)
- Go to Settings in your environment
- Toggle "Create ephemeral environments on pull request" to ON
- Toggle "Destroy environment after merge or close pull request" to ON
- Select the target Kubernetes cluster
What happens next:
- Bunnyshell adds a webhook to your Git provider automatically
- When a developer opens a PR, Bunnyshell creates an ephemeral environment cloned from the primary, using the PR's branch
- Bunnyshell posts a comment on the PR with a direct link to the running deployment
- When the PR is merged or closed, the ephemeral environment is automatically destroyed
No GitHub Actions. No GitLab CI pipelines. No maintenance. It just works.
Optional: CI/CD Integration via CLI
If you prefer to control preview environments from your CI/CD pipeline (e.g., for custom migration or seed scripts), you can use the Bunnyshell CLI:
1# Install
2brew install bunnyshell/tap/bunnyshell-cli
3
4# Authenticate
5export BUNNYSHELL_TOKEN=your-api-token
6
7# Create, deploy, and run migrations in one flow
8bns environments create --from-path bunnyshell.yaml --name "pr-123" --project PROJECT_ID --k8s CLUSTER_ID
9bns environments deploy --id ENV_ID --wait
10bns exec COMPONENT_ID -c nextjs-app -- npx prisma migrate deployRemote Development and Debugging
Bunnyshell makes it easy to develop and debug directly against any environment — primary or ephemeral:
Port Forwarding
Connect your local tools to the remote database:
1# Forward PostgreSQL to local port 15432
2bns port-forward 15432:5432 --component PG_COMPONENT_ID
3
4# Connect with psql, pgAdmin, or any DB tool
5psql -h 127.0.0.1 -p 15432 -U nextapp -d nextapp
6
7# Forward Redis to local port 16379
8bns port-forward 16379:6379 --component REDIS_COMPONENT_ID
9redis-cli -p 16379Execute Commands
1# Run Prisma migrations
2bns exec COMPONENT_ID -c nextjs-app -- npx prisma migrate deploy
3
4# Check migration status
5bns exec COMPONENT_ID -c nextjs-app -- npx prisma migrate status
6
7# Open Prisma Studio (forward port 5555 first)
8bns exec COMPONENT_ID -c nextjs-app -- npx prisma studio --port 5555
9
10# Seed the database
11bns exec COMPONENT_ID -c nextjs-app -- npx prisma db seed
12
13# Check Next.js build info
14bns exec COMPONENT_ID -c nextjs-app -- node -e "console.log(require('./.next/BUILD_ID', 'utf-8'))"Live Logs
1# Stream logs in real time
2bns logs --component COMPONENT_ID -f
3
4# Last 200 lines
5bns logs --component COMPONENT_ID --tail 200
6
7# Logs from the last 5 minutes
8bns logs --component COMPONENT_ID --since 5mLive Code Sync
For active development, sync your local code changes to the remote container in real time:
1bns remote-development up --component COMPONENT_ID
2# Edit files locally — changes sync automatically
3# When done:
4bns remote-development downLive code sync works best for server-side changes (API routes, server components). Changes to NEXT_PUBLIC_* variables or client components require a full rebuild since they are bundled at build time.
Troubleshooting
| Issue | Solution |
|---|---|
| 502 Bad Gateway | Next.js server not listening. Verify HOSTNAME=0.0.0.0 is set — without it, Next.js 14+ only binds to 127.0.0.1. Check port 3000 is exposed and matches servicePort in hosts config. |
| Health check fails | Missing /api/health route, or it's cached. Add export const dynamic = 'force-dynamic' to prevent ISR caching of the health endpoint. |
NEXT_PUBLIC_* shows wrong URL | These are build-time variables. Pass them as Docker build args, not runtime environment. Rebuild the image after changing values. |
| OAuth callback fails | NEXTAUTH_URL doesn't match the preview URL. Use interpolation: 'https://{{ components.nextjs-app.ingress.hosts[0] }}' |
| Prisma: "Can't reach database server" | Check DATABASE_URL uses postgres (the component name) as host, not localhost. Verify PostgreSQL is running before running migrations. |
| ISR pages serve stale content | Each preview environment has its own cache. If using Redis for ISR cache, verify REDIS_URL points to the environment's Redis instance, not a shared one. |
| Static assets return 404 | Missing COPY --from=builder /app/.next/static ./.next/static in the Dockerfile. The standalone output does not include static files by default — they must be copied explicitly. |
| Middleware redirect loops | Middleware may be checking for HTTPS but the K8s ingress terminates TLS. Check x-forwarded-proto header and trust proxies in your middleware logic. |
| Image optimization fails | Next.js Image Optimization requires sharp. Add RUN npm install sharp to the production stage, or set images: { unoptimized: true } in next.config.js. |
| Server Actions timeout | Large payloads or slow DB queries. Increase the Kubernetes ingress timeout annotations in your Helm chart or Bunnyshell config. |
What's Next?
- Add database migrations to deploy hooks — Automate
npx prisma migrate deployas a post-deploy step - Add Storybook — Deploy Storybook alongside your Next.js app for component review
- Configure ISR with Redis — Use the Redis component for on-demand revalidation across replicas
- Add S3/MinIO — For file uploads and image storage (
minio/minioas a Service component) - Monitor with OpenTelemetry — Add
@vercel/otelfor request tracing in preview environments
Related Resources
- Bunnyshell Quickstart Guide
- Docker Compose with Bunnyshell
- Helm with Bunnyshell
- Bunnyshell CLI Reference
- Preview Environments for Nuxt.js — Same pattern for Nuxt.js/Nitro
- Ephemeral Environments — Learn more about the concept
- All Guides — More technical guides
Ship faster starting today.
14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.