Deploy a Next.js app

Full-stack Next.js (pages, API routes, and server rendering) on a live HTTPS URL.

Dockhold runs Next.js as a real Node server, so server components, API routes, and server-side rendering all work, so you don't have to flatten the app to a static export. This recipe is the whole path.

In a hurry? Start from the ready-madenextjs-starter template: click Use this template on GitHub, thendeploy it. Or follow the steps below to set up a project you already have.

1. Bind next start to the assigned port

Make the start script use $PORT and bind all interfaces. In package.json:

{
  "scripts": {
    "build": "next build",
    "start": "next start -H 0.0.0.0 -p $PORT"
  }
}

Your app must listen on 0.0.0.0 and read its port from thePORT environment variable. Never localhost, never a hardcoded port. Dockhold assigns PORT at runtime; an app that ignores it can't receive traffic.

Don't set output: 'export' in next.config.jsunless you specifically want a static-only build. It disables API routes and server rendering. For a normal Next.js app, leave it off.

2. Connect the repo and deploy

  1. Push the project to a GitHub repository.
  2. Open the dashboard, connect GitHub, and pick the repo.
  3. On a paid plan (any account with compute added), Dockhold detects Next.js, runs npm install andnpm run build, then starts it with npm run start. Nothing else to do.
  4. On a free account, add the Dockerfile below at the root of your repo first. Auto-detection is a paid feature, so without one the deploy stops right away and tells you.

Next.js images get large fast, so this one uses standalone output: the build works out which modules the server actually needs instead of copying all of node_modules. Add this tonext.config.js:

const nextConfig = {
  output: process.env.BUILD_STANDALONE ? 'standalone' : undefined,
}

module.exports = nextConfig

The env var keeps standalone on for the image and off locally, sonpm run dev and npm start keep working the way they always did. Then the Dockerfile:

# Dockerfile
FROM node:22-alpine AS build
WORKDIR /app
ENV BUILD_STANDALONE=1
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && mkdir -p public

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
CMD ["node", "server.js"]

HOSTNAME=0.0.0.0 is the one line people miss. Without it the server can bind the wrong interface and no traffic reaches you. The standalone server reads $PORT on its own.

Your app goes live at https://<your-app>.dockhold.appwith HTTPS handled for you. Every later push to your main branch redeploys.

3. Environment variables

Set variables in the dashboard. Dockhold doesn't read a committed.env. Server-side code reads them from process.envat runtime (route handlers, server components, getServerSideProps); keep API keys and tokens in the Vault. Variables that reach the browser need the NEXT_PUBLIC_ prefix and are baked in when the app is built, and the build doesn't see your dashboard variables, so set those in code (e.g. next.config.js), for public values only, never a secret.

4. Add a database (optional)

Enable the managed Postgres add-on and Dockhold injectsDATABASE_URL into your app. Read it fromprocess.env.DATABASE_URL in server code. Don't run your own database, and don't commit a connection string.

Troubleshooting

  • App builds but the URL times out: the startscript must include -H 0.0.0.0 -p $PORT. A defaultnext start may bind the wrong host or port.
  • API routes 404 in production: check you haven't enabledoutput: 'export': static export drops the server.
  • A public env var is undefined: it must be prefixedNEXT_PUBLIC_ and set in code, because the build can't read dashboard variables.

Next