Nasazení Next.js 16 s Dockerem: standalone output, multi-stage build a self-hosting (2026)
Nasaďte Next.js 16 s Dockerem od nuly: multi-stage Dockerfile, standalone output pod 200 MB, Docker Compose s Postgres a Redisem, Caddy pro HTTPS a health checky pro produkci.
Self-hosting Next.js 16 s Dockerem znamená nasadit produkční build v kontejneru s aktivovaným output: 'standalone', který zabalí jen nutné závislosti. Výsledný image se vejde pod 200 MB a běží kdekoliv, kde spustíte Node.js 22. V praxi napíšete multi-stage Dockerfile, který odděluje instalaci závislostí, build a runtime, a doplníte ho o .dockerignore, health check a mapování proměnných prostředí. Tenhle článek vás provede kompletní konfigurací od nuly až po produkční nasazení s Docker Compose, Caddy proxy a self-hostingem next/image.
Aktivujte v next.config.ts volbu output: 'standalone'. Bez ní se do image dostane celý node_modules a build naroste na 1,5+ GB.
Multi-stage Dockerfile s Node.js 22 Alpine dostane produkční image Next.js 16 typicky mezi 150 a 190 MB.
Proměnné s prefixem NEXT_PUBLIC_ se zapékají do buildu, ostatní čtěte až v runtime přes process.env v serverovém kódu.
Pro next/image v containerovém prostředí nainstalujte sharp ručně, jinak Next.js spadne zpět na neoptimalizovaný fallback.
Před Node.js proces postavte reverzní proxy (Caddy nebo Nginx). Next.js server nemá vestavěné TLS ani rate limiting.
Statické assety z public/ a .next/static musíte kopírovat ručně, standalone output je nezahrne.
Proč self-hostovat Next.js 16 mimo Vercel
Honestly, s Vercelem jsem začínal už ve chvíli, kdy se ještě jmenoval Zeit Now, a pořád je to nejrychlejší cesta na produkci. Jenže pages-router dev ve mně si pamatuje, že Next.js je „jen Node.js server", a to platí i v roce 2026. Pokud musíte řešit rezidenci dat v EU, jednotné náklady bez per-invocation billingu, air-gapped prostředí, nebo prostě chcete stejný stack na dev laptopu i v produkci, self-hosting s Dockerem je vyzrálá cesta.
Next.js 16 tomu jde naproti víc než starší verze. output: 'standalone' je stabilní od 12.1 a s příchodem Turbopacku v produkčním buildu se výrazně zkrátila i doba samotného next build. Podle oficiální dokumentace Next.js k nasazení je Docker plnohodnotná cesta pro App Router (včetně Server Components, Server Actions i ISR), jen si musíte pár věcí ohlídat sami.
Praktické důvody, které v posledních měsících vidím u klientů nejčastěji: přenesení workloadu z Vercel Pro na vlastní Hetzner CX22 kvůli fakturaci, integrace do stávajícího Kubernetes clusteru a nutnost běhu za korporátním VPN. Ve všech třech případech dostanete stejnou App Router aplikaci, jen jinak zabalenou.
Co dělá standalone output a proč ho v Dockeru potřebujete
Výchozí next build vygeneruje .next adresář, který si za běhu tahá závislosti z node_modules. Když tenhle výstup zabalíte do Dockeru „naivně", skončíte s image přes 1 GB, a většinu z toho tvoří dev závislosti, které za běhu vůbec nepotřebujete.
output: 'standalone' spustí trasování závislostí (postavené nad nftem od Vercelu) a do .next/standalone zkopíruje jen ty node_modules, které váš serverový kód opravdu importuje. Vygeneruje i minimální server.js, který nastartuje Next.js bez potřeby npm start.
Zapíná se to jednou řádkou v next.config.ts:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
// Volitelně omezte trasování na monorepo root,
// pokud máte pnpm workspace nebo Turborepo.
outputFileTracingRoot: process.env.NODE_ENV === 'production'
? undefined
: undefined,
};
export default nextConfig;
Po buildu najdete v .next/standalone/ kompletní runtime aplikaci. Statické assety (soubory z public/ a .next/static) tam ale nejsou. Musíte je do image zkopírovat ručně, jinak se stránka načte bez CSS a bez obrázků. To je klasický footgun, na který jsem se sám napálil při prvním pokusu, a ještě mě to stálo dvacet minut googlení.
Multi-stage Dockerfile pro Next.js 16 krok za krokem
Tohle je Dockerfile, který používám v produkci. Je založený na Node.js 22 Alpine (Next.js 16 minimum je Node 20.9, ale 22 je LTS s lepším perf profilem) a odděluje instalaci závislostí, build a runtime. Detailní vysvětlení multi-stage přístupu najdete v Docker docs o multi-stage buildu.
# syntax=docker/dockerfile:1.7
# Stage 1: instalace zavislosti
FROM node:22-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Zkopirujeme jen lock file kvuli cache layeru.
COPY package.json pnpm-lock.yaml* package-lock.json* yarn.lock* ./
RUN \
if [ -f pnpm-lock.yaml ]; then \
corepack enable pnpm && pnpm i --frozen-lockfile; \
elif [ -f package-lock.json ]; then \
npm ci; \
elif [ -f yarn.lock ]; then \
corepack enable yarn && yarn --frozen-lockfile; \
else \
echo "Chybi lock soubor." && exit 1; \
fi
# Stage 2: build
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# NEXT_TELEMETRY_DISABLED zabrani odesilani telemetrie z buildu.
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN \
if [ -f pnpm-lock.yaml ]; then \
corepack enable pnpm && pnpm build; \
else \
npm run build; \
fi
# Stage 3: minimalni runtime image
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
# Nepouzivejte root. Vytvorte dedikovaneho uzivatele.
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Staticke assety musime zkopirovat rucne.
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
# Health check pro reverzni proxy nebo orchestrator.
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
K tomu potřebujete .dockerignore, jinak si do image přenesete lokální .next, git historii a případné .env.local s produkčními tajemstvími:
První verze mého self-hostovaného image měla 640 MB a mě to štvalo. Postupně jsem se dostal pod 180 MB a tady je checklist, co reálně zabralo, v pořadí od největšího dopadu.
Zapněte output: 'standalone'. Bez toho zbytek nemá smysl řešit, ušetří 60 až 70 % velikosti.
Multi-stage build. Runtime stage nesmí obsahovat dev závislosti, TypeScript kompilátor ani build cache.
Nezakládejte cache pro next/image do image. Cache se plní až za běhu a patří na volume, ne do read-only vrstvy.
Buildx s --platform jen pro architekturu, kterou potřebujete. Multi-arch build vytvoří 2× větší manifest, což se počítá do pull time.
Ověřit můžete přes docker image ls a analyzovat vrstvy nástrojem dive, který vezme Dockerfile a řekne, ve které vrstvě sedí každý MB. U jednoho klientského projektu odhalil, že jsme si do runtime tahali celý @sentry/nextjs source map balík, který stačilo přesunout na uploads-only.
Proměnné prostředí: build-time vs. runtime
Tohle je oblast, kde se pages-router dev jako já popálil nejvíc. Next.js má dva odlišné světy proměnných a v Dockeru je snadné je pomíchat.
Build-time proměnné jsou ty s prefixem NEXT_PUBLIC_ a všechny proměnné, které čtete v serverových Server Components během buildu (např. při generování statických stránek). Zapíšou se natvrdo do bundle a nemůžete je za běhu změnit, takže musíte přebuildit image.
Runtime proměnné se čtou při každém requestu ze process.env ve Route Handlerech, Server Actions nebo dynamických Server Components. Ty můžete měnit přes docker run -e nebo Kubernetes Secrets bez rebuildu.
# docker-compose.yml, spravne rozdeleni
services:
web:
build:
context: .
args:
# Build-time: musi byt dostupne pri docker build
NEXT_PUBLIC_APP_URL: https://app.example.cz
NEXT_PUBLIC_ANALYTICS_ID: G-XXXXXXX
environment:
# Runtime: ctou se pri kazdem requestu
DATABASE_URL: postgresql://user:pass@postgres:5432/app
AUTH_SECRET: ${AUTH_SECRET}
REDIS_URL: redis://redis:6379
OPENAI_API_KEY: ${OPENAI_API_KEY}
V Dockerfile potřebujete build args deklarovat, aby se přenesly do buildu:
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_APP_URL
ARG NEXT_PUBLIC_ANALYTICS_ID
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
ENV NEXT_PUBLIC_ANALYTICS_ID=$NEXT_PUBLIC_ANALYTICS_ID
RUN npm run build
V praxi rozdělení kontroluju gitignorovaným .env.build pro build args a runtime tajemství drží Docker secrets nebo HashiCorp Vault. Pro autentizaci a session management doporučuju kombinovat s Auth.js v5 průvodcem, který má self-hosting explicitně podporovaný.
Docker Compose s PostgreSQL a Redisem
Pro jednoduchý deployment na jednom VPS je Docker Compose úplně dostačující. Tady je konfigurace, kterou používám jako startovací šablonu pro Next.js 16 + Postgres + Redis (pro cache a rate limiting):
Připojení Next.js aplikace k Postgresu řeším přes Prisma nebo Drizzle. Porovnání obou najdete v článku Prisma vs. Drizzle v Next.js 16. Klíčové je nastavit connection pool size podle limitů Postgresu a v Prismě zapnout directUrl pro migrace.
Reverzní proxy s Caddy pro HTTPS a HTTP/2
Node.js server běžící z server.js nemá TLS. Můžete ho postavit i za nginx, ale pro self-hosting mám nejraději Caddy. Vyřídí Let's Encrypt certifikáty automaticky a Caddyfile má pět řádků místo padesáti.
Aby Next.js správně detekoval HTTPS (např. při generování absolutních URL a v redirect()), musíte v next.config.ts nastavit trustHostHeader nebo použít middleware, který přepíše protokol podle X-Forwarded-Proto. Pro pokročilé úpravy requestů před doručením do App Routeru je v Next.js 16 nový soubor proxy.ts. Detailní rozbor najdete v průvodci migrací z middleware na proxy.ts.
Health checks, graceful shutdown a logování
V produkci vždycky přidejte lightweight health check endpoint. Používám tenhle vzor, který vrací JSON s verzí, timestampem a stavem důležitých závislostí:
Pro graceful shutdown (aby Next.js dokončil běžící requesty při docker stop) není potřeba nic extra. server.js ze standalone outputu SIGTERM zpracuje sám a čeká na dokončení pending handlerů. Ověříte to nastavením stop_grace_period: 30s v Compose.
Logování směřuju na stdout ve strukturovaném JSON formátu (Pino je moje volba) a docker daemon pak posílá logy do Loki nebo CloudWatch. V žádném případě nezapisujte logy do souborů uvnitř kontejneru, v ephemerálním prostředí je ztratíte při restartu.
Časté otázky
Můžu nasadit Next.js 16 na Kubernetes se stejným Dockerfile?
Ano, image z tohoto článku běží beze změny v Kubernetes. Přidejte jen livenessProbe a readinessProbe mířící na /api/health, nastavte resources.requests na 128 MB paměti a 100 m CPU jako startovací hodnoty a pro ISR cache použijte perzistentní volume místo emptyDir.
Proč je můj Next.js Docker image tak velký?
Nejčastější důvod je chybějící output: 'standalone' v next.config.ts. Bez něj se do image dostane celý node_modules včetně dev závislostí. Druhý viník je použití plného node:22 místo node:22-alpine. Po opravě obou bodů obvykle padne velikost z 1 GB+ pod 200 MB.
Jak řeším ISR a on-demand revalidaci v containerovém prostředí?
ISR cache Next.js 16 sedí ve filesystému kontejneru a při restartu se ztratí. Pro produkci připojte perzistentní volume na .next/cache nebo použijte custom cache handler ukládající do Redisu. V next.config.ts nastavíte cacheHandler a cacheMaxMemorySize: 0, aby se všechno delegovalo na Redis.
Musím vypnout next/image, když self-hostuju bez Vercelu?
Ne. next/image funguje mimo Vercel plně, jen potřebuje nainstalovaný sharp a případně další paměť (300 MB+ pro server). Pokud chcete odlehčit runtime, můžete v next.config.ts nastavit images.loader: 'custom' a delegovat optimalizaci na CDN jako Cloudflare Images nebo imgproxy.
Jak zajistím HTTPS bez Vercel edge?
Nejjednodušší je postavit před Next.js kontejner Caddy nebo Traefik. Oba vyřizují Let's Encrypt certifikáty automaticky včetně obnovování. Alternativa je Cloudflare v proxy módu, kdy TLS terminuje Cloudflare a Next.js server běží na HTTP v interní síti.