Deploy a Next.js app on Bahriya

This quickstart deploys a Next.js application as an HTTP container using Next.js's standalone output. It listens on port 3000, ships as a compact self-contained image, and serves over HTTPS on its default hostname.

Updated 3 Aug 20263 min read

This quickstart deploys a Next.js application as an HTTP container using Next.js's standalone output. It listens on port 3000, ships as a compact self-contained image, and serves over HTTPS on its default hostname.

Standalone output is the right choice for a container: Next.js traces exactly the files your app needs and produces a small server.js you can run with node, so you do not have to copy the whole node_modules tree into the runtime image.

Enable standalone output

In next.config.js, set the output mode:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
};
 
module.exports = nextConfig;

The standalone server listens on port 3000 by default and reads PORT from the environment, so it works with Bahriya without further changes.

Dockerfile

A multi-stage build installs dependencies, builds the app, then assembles a minimal runtime image from the standalone output:

# Dependencies
FROM node:22-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
 
# Build
FROM node:22-slim AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# Runtime
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=build /app/public ./public
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]

Build-time versus runtime environment

Next.js reads two kinds of configuration, and the distinction matters when you containerise:

  • Build-time values are baked into the compiled output during npm run build. Anything exposed to the browser with the NEXT_PUBLIC_ prefix is fixed at build time — set it in your CI build, not on the container.
  • Runtime values are read when the server starts. Server-only variables (database URLs, API keys) can be supplied as environment variables and secrets on the container, so the same image runs across regions and environments.

Because public variables are frozen at build time, rebuild the image if you need to change them.

Deploy from the Console

  1. Create a new HTTP container in your project.
  2. Set the image to your built reference and select a registry if it is private.
  3. Set the port to 3000. Next.js does not ship a health endpoint by default — add a route handler at /healthz that returns 200, or point the health check at a lightweight page that always renders.
  4. Choose regions, set CPU and memory, and create the container.

Deploy with Reis

reis container:create \
  --type http \
  --name "Web" \
  --handle web \
  --image ghcr.io/myorg/web:1.0.0 \
  --project my-project \
  --port 3000 \
  --healthcheck /healthz \
  --cpu 500 \
  --memory 512 \
  --replicas 2 \
  --max_replicas 8 \
  --regions falkenstein-1 \
  --regions virginia-1 \
  --env NODE_ENV=production

Environment variables, secrets, and domains

Next steps