Deploy a Next.js App
Run a Next.js app on Cosmoner from your repository — server rendering, API routes and NEXT_PUBLIC_ variables included — or ship it as a static export.
Overview
A Next.js app deploys as a web service with no configuration: Cosmoner
detects Next.js from your package.json, installs your dependencies,
runs next build, and starts the app with next start. Server components,
route handlers, middleware, image optimization and incremental static
regeneration all work, because it is the same Node.js server you run locally.
If your app is fully static — output: 'export' — you can deploy it as a
static site instead, for a flat price.
This guide covers what is specific to Next.js. For the deploy flow itself, see Deploy an App from Git.
Deploy it as a web service
- In your project, go to Apps → New app and choose a domain.
- Under Git Repository, connect your provider and pick the repository and
branch. With a
next.config.jsornext.config.ts, the Automatic strategy is labelled Detected: Next.js; any other config file builds the same way without the label. - Leave the app type as Web Service and the strategy as Automatic.
Build command and Run command are detected from your
package.json— thebuildandstartscripts — so leave them empty unless you use different ones. - Leave Port empty.
next startlistens on the port inPORT, which the app is given. - Add your environment variables (see below), then Continue, choose a plan and Launch.
For a monorepo, set App directory to the Next.js app's folder, e.g.
apps/web.
Choosing a plan
next build runs on the build machine, not on your plan, so the plan only has
to fit the running server. Shared S (1 GB of memory) is a comfortable start
for most apps; move up when Metrics shows memory close to the limit or
restarts.
Environment variables
Next.js reads variables at two different times, and Cosmoner provides both:
| Variable | Read when | What to do |
|---|---|---|
NEXT_PUBLIC_* | At build time, and written into the JavaScript sent to the browser. | Add it to the app. It is available to next build. Changing it needs a new build — Redeploy after saving. |
| Everything else | At run time, on the server only. | Add it to the app. Changing it takes effect on the next deploy. |
Anything prefixed NEXT_PUBLIC_ is public by design — it ends up in your
users' browsers. Never put a secret behind that prefix. Keep secrets such as
API keys and database URLs unprefixed, and link them from the vault with
Link secret so their values never sit in the app's settings — see
Secrets.
Deploy it with your own Dockerfile
Use Dockerfile as the strategy when you want full control over the image,
for example with Next.js's standalone output. Two things differ from building
locally:
- Build-time variables arrive as build arguments. Declare each
NEXT_PUBLIC_*variable your build needs withARGbeforenext build, or it will be empty in the bundle. - Listen on all interfaces. The standalone server binds to
HOSTNAME; set it to0.0.0.0.
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
ARG NEXT_PUBLIC_API_URL
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production 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
EXPOSE 3000
CMD ["node", "server.js"]With output: 'standalone' set in next.config. The server listens on PORT
when it is set, which it always is on Cosmoner; EXPOSE 3000 is only used when
no port is stated anywhere else.
Variables you mark as secret are not passed as build arguments. They are
mounted for the build step instead, which keeps them out of the image layers:
read one with RUN --mount=type=secret,id=NAME,env=NAME.
Static export
An app built with output: 'export' produces plain files and can be deployed as
a Static Site:
- In
next.config, setoutput: 'export'. Pages are found at their clean URLs either way —/aboutservesabout.html— sotrailingSlashis your choice. - In the wizard, choose Static Site.
- Set Build command to
npm ci && npm run build— a static build starts from a clean Node.js environment, so it has to install dependencies itself. - Set Output directory to
out.
A static export cannot use server-side features: route handlers, server
actions, middleware, or image optimization at request time. Dynamic routes need
generateStaticParams so every page exists as a file.
Connecting a database or cache
Store the connection string as a secret and link it as an environment variable,
e.g. DATABASE_URL or REDIS_URL, then read it from process.env in server
code only. See Connect to Redis for a cache.
Keep the settings in your repository
Commit a .cosmoner/deployment.yaml and the wizard fills itself in from it:
# yaml-language-server: $schema=https://cosmoner.com/schemas/app.schema.json
services:
- name: web
type: service
autodeploy: true
envs:
- key: NEXT_PUBLIC_API_URL
value: https://api.example.com
- key: DATABASE_URL
from_secret: DATABASE_URLSee App Configuration Files for every field.
Troubleshooting
NEXT_PUBLIC_ values are undefined in the browser. The variable was added
after the last build, or — with a Dockerfile — is not declared with ARG.
Add it, then Redeploy.
The deploy never becomes healthy with a Dockerfile. The server is listening
on a port or interface other than the one it was given. Check that
HOSTNAME=0.0.0.0 is set and that nothing overrides PORT.
Pages of a static export return 404. Check that Output directory is
out and that the page exists as a file in it — dynamic routes need
generateStaticParams.
Deploy an App from Git
Connect GitHub, GitLab or Bitbucket, pick a repository, and have your app built and running on a cosmoner.com address — then redeployed on every push.
Deploy a Container Image
Run any Docker image as an app — from your Cosmoner registry, Docker Hub or the GitHub container registry — and redeploy it from CI when you push a new tag.