Voltar ao blog Tutoriais

Como fazer deploy de Next.js com Docker no Brasil

Guia direto para publicar uma aplicação Next.js em container usando o modo standalone, com Dockerfile multi-stage e infraestrutura brasileira na Guara Cloud.

9 min de leitura

Por Guara Cloud Editorial

Testado com Node.js 20 / Next.js 14 / Docker / Guara Cloud

Next.js tem dois caminhos pra deploy: o managed (Vercel) e o self-hosted (Docker). O managed funciona bem se você não se importa com vendor lock-in e custo em dólar. O self-hosted te dá controle total sobre o runtime, a infraestrutura e a conta no fim do mês.

O problema é que a maioria dos tutoriais de Next.js com Docker ignora o output standalone. Copiam a pasta .next inteira pra imagem e geram um container de 1.2GB que demora pra subir. Não precisa ser assim. Com o modo standalone e um Dockerfile multi-stage, a imagem fica abaixo de 150MB e o app roda sem o node_modules completo.

Vou mostrar o caminho inteiro, do next.config.js até o serviço respondendo com HTTPS na Guara Cloud. Sem pressa, sem enrolação.

Resposta rápida

Para publicar um app Next.js com Docker no Brasil, ative output: 'standalone' no next.config.js, use um Dockerfile multi-stage que copia apenas o diretório standalone e os assets estáticos, exponha a porta 3000 e publique o container na Guara Cloud. A plataforma cuida de HTTPS, domínio público, logs e cobra em Real.

Principais pontos

  • Use output: 'standalone' no next.config.js. Isso gera um servidor minimalista que não depende do node_modules inteiro.
  • O Dockerfile multi-stage separa install, build e produção. A imagem final carrega cerca de 130MB.
  • O server.js gerado pelo standalone escuta na porta 3000 por padrão. Use HOSTNAME=0.0.0.0 para aceitar conexões externas.
  • Assets estáticos (public/ e .next/static) precisam ser copiados manualmente para o diretório do standalone na imagem final.
  • Configure variáveis de ambiente pelo painel da Guara Cloud, não no código.

Quando este tutorial se aplica

Use este fluxo para aplicações Next.js com App Router ou Pages Router que precisam rodar self-hosted. Funciona com rotas de API, Server Actions, SSR, ISR e rotas estáticas. Se o projeto usa next/image com otimização local, o container suporta via sharp (incluído no standalone).

Quando não usar este fluxo

Se o projeto é 100% estático (next export ou output: 'export'), você não precisa de um container rodando Node.js. Qualquer CDN serve os arquivos HTML. Se o app precisa de Edge Runtime (middleware com Edge, geolocation no edge), o self-hosted perde essa capacidade, já que o Edge Runtime é específico de plataformas serverless. Para esses casos, considere export estático pra CDN e um container separado só pras rotas que precisam de SSR.

Antes de começar

  • Um projeto Next.js 14+ criado com create-next-app ou existente
  • Node.js 20+ instalado localmente para testes
  • Docker instalado para validar a imagem
  • Uma conta na Guara Cloud (app.guaracloud.com)

1. Ative o output standalone

Abra next.config.js (ou next.config.mjs) e adicione a opção:

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

export default nextConfig;

Quando você roda next build, o Next.js gera o diretório .next/standalone com um server.js mínimo que carrega apenas as dependências necessárias. Sem o node_modules de 500MB.

Se o projeto usa @next/font ou next/image com sharp, eles são incluídos automaticamente no bundle standalone.

2. Dockerfile multi-stage para Next.js

Esse é o Dockerfile que eu uso nos meus projetos Next.js na Guara Cloud. Quatro estágios, cada um com uma função clara.

Dockerfile
FROM node:20-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /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
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

Alguns pontos sobre esse Dockerfile:

O npm ci no estágio deps é separado do build. Isso significa que quando você muda código mas não muda dependências, o Docker reusa a layer de deps e o build é mais rápido.

O usuário nextjs não-root é criado no estágio final. Sei que muita gente ignora isso, mas rodar container como root é pedir problema, mesmo com a plataforma sandboxizando o processo.

Já o HOSTNAME=0.0.0.0 faz o servidor aceitar conexões de fora do container. Sem isso, o Next.js escuta só em 127.0.0.1 e o health check da plataforma falha. Esse é um erro que vejo muita gente cometer na primeira vez.

3. Adicione o .dockerignore

Sem isso, o Docker copia node_modules e .next pro contexto de build, o que torna cada build 30 segundos mais lento.

.dockerignore
node_modules
.next
.git
.gitignore
docker-compose*.yml
Dockerfile
*.md
.env*.local

4. Configure variáveis de ambiente

No painel da Guara Cloud, adicione as variáveis que o app Next.js consome:

Variáveis comuns para Next.js

Nome Valor
NODE_ENV production
DATABASE_URL postgres://user:***@host:5432/mydb
NEXT_PUBLIC_APP_URL https://meu-app.guaracloud.com
JWT_SECRET string-aleatoria-longa

Variáveis com prefixo NEXT_PUBLIC_ são embutidas no bundle JavaScript durante o build. Se você mudar essas variáveis, precisa fazer um novo deploy. Variáveis sem o prefixo (como DATABASE_URL) são lidas em runtime no servidor e podem ser alteradas sem rebuild.

5. Crie uma rota de health check

Guara Cloud usa probes HTTP para verificar se o container está respondendo. Crie uma rota simples:

app/health/route.ts (App Router)
import { NextResponse } from 'next/server';

export const dynamic = 'force-dynamic';

export async function GET() {
return NextResponse.json({ status: 'ok' });
}

Se usa Pages Router, crie pages/api/health.ts:

pages/api/health.ts (Pages Router)
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ status: 'ok' });
}

O force-dynamic garante que a rota não seja cacheada pelo ISR. O probe precisa de uma resposta fresca.

6. Deploy na Guara Cloud

Com o repositório no GitHub e o Dockerfile na raiz, o processo é:

Passo a passo no painel

  1. Crie um novo serviço e conecte o repositório do GitHub
  2. A plataforma detecta o Dockerfile automaticamente
  3. Confirme a porta HTTP (3000, que é o padrão do Next.js)
  4. Adicione as variáveis de ambiente pelo painel
  5. Inicie o deploy e acompanhe os logs de build em tempo real

O primeiro build demora entre 3 e 5 minutos, incluindo npm ci, next build e a geração da imagem. Os deploys seguintes são mais rápidos por causa do cache de layers do Docker. A layer de deps só é refeita quando o package-lock.json muda.

7. Valide o serviço em produção

Depois que o deploy concluir, verifique:

  • A URL pública responde com 200.
  • A rota /health retorna { status: 'ok' }.
  • As páginas SSR renderizam corretamente (não retorna HTML vazio).
  • A rota de API funciona se o projeto tem endpoints em app/api/.
  • Os logs mostram o servidor Next.js escutando na porta 3000.

Se algo falha, os logs de build e runtime ficam disponíveis no painel. A maioria dos problemas aparece nos primeiros segundos.

Imagem final e performance

Com o Dockerfile acima, a imagem final fica em torno de 130MB. Isso inclui o Alpine, o Node.js 20, o server.js standalone e os assets estáticos. Para referência, um Dockerfile ingênuo que copia node_modules inteiro passa de 900MB.

O tempo de cold start do container é cerca de 1 a 2 segundos na Guara Cloud. O Next.js standalone já vem com as rotas pré-compiladas, então a primeira requisição não tem overhead adicional de compilação.

Problemas comuns

Problema O container inicia mas o health check falha
Solução Verifique se HOSTNAME está definido como "0.0.0.0". Sem isso, o Next.js escuta apenas em loopback. Confirme também que a porta no painel está configurada como 3000.
Problema Imagens quebradas ou CSS não carrega
Solução Os arquivos de public/ e .next/static não foram copiados para o diretório correto. No Dockerfile, confirme as duas linhas COPY que movem esses diretórios para dentro do standalone.
Problema Rotas de API retornam 500 com "Module not found"
Solução Alguma dependência usada em API routes não foi incluída no bundle standalone. Verifique se está importada corretamente no código. O standalone rastreia imports automaticamente, mas re-exports de pacotes com resolução dinâmica podem falhar.
Problema O build do Next.js falha com erro de memória
Solução O next build precisa de mais RAM durante a compilação. Adicione NODE_OPTIONS="--max-old-space-size=4096" como variável de ambiente de build na Guara Cloud ou no estágio builder do Dockerfile.
Problema Variáveis NEXT_PUBLIC_ não aparecem no cliente
Solução Essas variáveis são embutidas no bundle JavaScript durante o build. Se você mudou o valor depois do deploy, precisa fazer um novo build. Elas não são lidas em runtime.
Problema next/image retorna 500 no container
Solução O sharp pode não ter sido incluído. Confirme que está usando next/image padrão (não um loader customizado) e que o package-lock.json inclui sharp nas dependências.

Diferenças entre self-hosted e Vercel

Algumas coisas mudam quando você roda Next.js em container ao invés da Vercel:

Edge Runtime não funciona. Middleware definido com runtime: 'edge' precisa de uma plataforma que suporte o runtime do Edge (Vercel, Cloudflare Workers). Em container, use o Node.js runtime para middleware.

ISR funciona, mas a invalidação é local. No Vercel, o revalidate é global e distribuído pela edge network. Self-hosted, cada container mantém seu próprio cache. Se você tem múltiplas réplicas, a revalidation não propaga automaticamente entre elas.

Preview deploys precisam de configuração manual. O Vercel cria um deploy por PR automaticamente. Na Guara Cloud, você pode criar ambientes separados para staging, mas o fluxo de preview deploy por branch é diferente.

Custo. Aqui é onde o self-hosted ganha de verdade. Um app Next.js na Guara Cloud com 512MB de RAM custa uma fração do que a Vercel cobra no plano Pro em dólar, com IOF de 6.38% por cima. E a cobrança vem em Real, com nota fiscal.

Posso usar App Router e Pages Router juntos?

Sim. O Next.js suporta ambos no mesmo projeto, e o output standalone funciona com os dois. O Dockerfile não muda.

O modo standalone suporta Server Actions?

Sim. Server Actions funcionam normalmente no runtime Node.js. Elas só não funcionam no Edge Runtime, que não existe em container.

Como faço para escalar horizontalmente?

Na Guara Cloud, aumente o número de réplicas do serviço. Cada réplica roda uma instância independente do server.js. Para compartilhar cache de ISR entre réplicas, considere usar o Redis como cache backend.

A cobrança é em Real?

Sim. A Guara Cloud cobra em BRL via Stripe. Sem conversão em dólar, sem IOF no cartão.

Preciso de Nginx na frente do Next.js?

Não. A Guara Cloud fornece HTTPS, domínio público e roteamento sem que você precise configurar Nginx ou certificados TLS manualmente.

Publique seu Next.js na Guara Cloud

Deploy com Docker, HTTPS gerenciado, logs em tempo real e cobrança em Real. Infraestrutura em São Paulo.

Começar grátis