Publicar um projeto Next.js
O Next.js pode virar um site de arquivos prontos ou um servidor Node que roda o tempo todo (páginas renderizadas no servidor, rotas de API, middleware, ISR, otimização de imagens). O painel publica os dois, e ainda deixa você assumir o controle com um Dockerfile seu. Esta página mostra os três caminhos, quando usar cada um e o que o painel faz em cada um.
Se você usa um compose ou um Dockerfile seu, veja também as regras do compose.
Os três caminhos
Seção intitulada “Os três caminhos”O painel olha o repositório nesta ordem e para no primeiro que casa:
| Prioridade | O que ele encontra | Caminho | Tipo na tela |
|---|---|---|---|
| 1 | Um arquivo compose.yml / docker-compose.yml (ou variantes .prod) | Seu compose é adotado | compose adotado |
| 2 | Um Dockerfile na raiz e nenhum package.json | Seu Dockerfile é usado | dockerfile |
| 3 | next nas dependências e output: "export" no next.config | Site estático (pasta out/), mesmo que exista um Dockerfile: nesse caso o Dockerfile é ignorado | estático (Node) |
| 4 | next nas dependências, sem output: "export" e sem Dockerfile | Servidor automático | Next.js (servidor) |
| 5 | next nas dependências, sem output: "export", com Dockerfile e script build no package.json | Seu Dockerfile é usado | dockerfile |
Atenção a dois casos: um projeto Next.js com output: "export" e um Dockerfile vira site estático (se você quer o
seu Dockerfile, tire o output: "export"); e um projeto com Dockerfile mas sem script build no package.json
não é reconhecido (tipo desconhecido): acrescente o script build.
1. Site estático (output: "export")
Seção intitulada “1. Site estático (output: "export")”Use quando o site não precisa de servidor: páginas que podem ser geradas no build, sem rotas de API,
sem middleware, sem next/image otimizado em tempo real, sem ISR. O next build gera a pasta out/ e o painel
a serve com um servidor web simples.
- É o mais leve e o mais barato: não há processo Node rodando.
- Variáveis de ambiente não existem em tempo de execução (não há servidor para lê-las). As
NEXT_PUBLIC_*valem só se estiverem no código ou no build. - O painel lê o
next.config.js,.mjs,.cjs,.tsou.mtscomo texto (nunca executa o arquivo) e procuraoutput: "export"escrito direto. Se o valor vier de uma expressão (process.env.X ? "export" : …), o painel não sabe qual é o resultado e trata o projeto como servidor: escreva o valor direto no arquivo.
2. Servidor automático (o caminho novo)
Seção intitulada “2. Servidor automático (o caminho novo)”Use quando o projeto usa o servidor do Next.js e você não quer escrever Dockerfile. É o caso de quase todo
projeto criado com create-next-app que usa rotas de API, middleware.ts, Server Actions, páginas dinâmicas,
revalidate (ISR) ou next/image.
O painel:
- gera o Dockerfile fora do seu repositório (nada é gravado no seu código; ele fica na pasta de trabalho do
painel e é refeito a cada deploy). Nesse modo o
.dockerignoredo seu repositório é substituído pelo do painel (que deixa de fora.env,.git,node_modulese.next): o que você tinha nele não vale nesse build; - constrói a imagem (multi-stage) com a instalação congelada pelo lockfile e roda o
next build; - sobe um container com a imagem final mínima, como usuário comum (não root), sem capabilities e sem publicar porta no servidor: o acesso é só pelo domínio, pelo Caddy, com HTTPS;
- confere se o site respondeu (health check) como nos outros tipos.
O que ele detecta sozinho:
| Detecção | De onde vem |
|---|---|
| Gerenciador de pacotes | campo packageManager do package.json, depois o lockfile (pnpm-lock.yaml, yarn.lock, package-lock.json), senão npm |
| Versão do Node | engines.node do package.json, depois .nvmrc, depois .node-version; sem nada, Node 22 (LTS). O painel oferece 20, 22 e 24; outra versão volta ao 22 e avisa no log |
| Modo standalone | output: "standalone" escrito direto no next.config |
| Porta | 3000 por padrão; dá para trocar no assistente |
Standalone ou next start?
- Com
output: "standalone"a imagem final leva só o necessário e rodanode server.js. É a imagem bem menor (dezenas de MB em vez de centenas) e começa mais rápido. Recomendado. - Sem standalone, o painel roda
next startcom o projeto construído dentro da imagem. Funciona, só é maior. - Para ligar o standalone, acrescente ao
next.config:output: "standalone".
Limites do servidor automático
- O app precisa estar na raiz do repositório (o
package.jsoncomnextna raiz). Monorepo com o app emapps/web: o painel avisa na detecção e você usa o caminho 3 (Dockerfile seu). - Com
output: "standalone"em monorepo (outputFileTracingRoot), o caminho doserver.jsmuda: o build para com uma mensagem explicando. Use o caminho 3. - Yarn Plug’n’Play (sem
node_modules) não é suportado; usenodeLinker: node-modules. - Sem lockfile no repositório, a instalação não é reprodutível (cada deploy pode trazer versões novas): o painel avisa. Commite o lockfile.
- O build não tem acesso à rede interna do projeto (por exemplo, ao banco no mesmo compose). Páginas geradas no build que leem banco precisam de um banco alcançável de fora ou devem ser geradas em tempo de execução.
- O
.gitnão entra no build (se o seunext.configrodagit rev-parsepara ogenerateBuildId, defina o id de outro jeito).
3. Dockerfile ou compose seu
Seção intitulada “3. Dockerfile ou compose seu”Use quando você precisa de controle: monorepo, pacotes do sistema (apk add), banco no mesmo compose,
outro runtime, build em etapas diferentes. Coloque um Dockerfile (ou compose.yml) na raiz: ele tem
prioridade sobre o servidor automático, e o painel o usa como está.
Isso inclui o caso comum de quem já tinha um Dockerfile do Next.js no repositório: nada muda para esse projeto.
Como escolher
Seção intitulada “Como escolher”| Situação | Caminho |
|---|---|
| Site de conteúdo, sem API nem middleware, que pode ser pré-gerado | 1. Estático |
| App com rotas de API, middleware, Server Actions, ISR ou imagens, repositório de um app só | 2. Servidor automático |
| Monorepo, banco no mesmo compose, dependências do sistema, imagem própria | 3. Dockerfile/compose seu |
Dúvida entre 1 e 2? Se o next build já gera tudo que você precisa em out/ com output: "export", fique no 1:
é mais barato e mais simples de operar.
Variáveis de ambiente
Seção intitulada “Variáveis de ambiente”Na seção Variáveis do projeto:
- Em tempo de execução (o servidor rodando), todas as variáveis do painel valem, como em qualquer projeto.
NEXT_PUBLIC_*precisam existir no build. O Next.js trocaprocess.env.NEXT_PUBLIC_Xpelo valor dentro do JavaScript que vai para o navegador. Por isso o painel passa asNEXT_PUBLIC_*ao build. Elas são públicas por definição: qualquer visitante lê o valor no código do site. Nunca coloque segredo numaNEXT_PUBLIC_*.- Mudou uma
NEXT_PUBLIC_*? Faça um novo deploy: reiniciar o container não basta, porque o valor está dentro do build. - As demais variáveis (senhas, chaves,
DATABASE_URL) também ficam disponíveis aonext build, caso uma página gerada no build precise delas. O painel as entrega por um segredo do BuildKit, montado só durante o passo donext build: elas não entram em nenhuma camada da imagem nem aparecem nodocker history. O log do deploy lista só os nomes, nunca os valores. - Não entram no build (e valem só na execução):
PORT,HOSTNAME,NODE_ENV,NODE_OPTIONS,PATH,HOME,LD_*,npm_*e nomes que não são identificadores válidos de shell; também valores com mais de 32 KB. PORTnas Variáveis é ignorada: o servidor escuta na porta do projeto (3000 por padrão) e o painel entrega essa porta ao Next.js. Para mudar, use o campo de porta do projeto.- O painel sempre define
HOSTNAME=0.0.0.0,NODE_ENV=productione desliga a telemetria do Next.js.
Memória e tempo do build
Seção intitulada “Memória e tempo do build”O next build é o passo mais pesado de um deploy. Em uma VPS pequena ele pode estourar a memória.
- O painel limita o heap do Node no build com
--max-old-space-sizeigual a 60% da RAM do servidor (o limite é por processo: o Next.js usa vários processos no build, então o total pode passar disso; para reduzir, limiteexperimental.cpusnonext.config), com mínimo de 768 MB e máximo de 4096 MB (1 GB de RAM → 768 MB; 2 GB → 1229 MB; 4 GB → 2458 MB; 8 GB ou mais → 4096 MB). O valor aparece no log do deploy. - Se o build morrer por falta de memória, o log termina com “O build ficou sem memória” e o que fazer.
O que costuma resolver, nesta ordem:
- Criar um arquivo de swap na VPS (2 GB já ajudam muito). Em Ubuntu:
sudo fallocate -l 2G /swapfile,sudo chmod 600 /swapfile,sudo mkswap /swapfile,sudo swapon /swapfile, e a linha/swapfile none swap sw 0 0em/etc/fstabpara sobreviver ao reinício. - Parar outros containers pesados durante o deploy.
- Usar uma VPS com mais RAM.
- No Next.js 15 ou mais novo, ligar
experimental.webpackMemoryOptimizationsnonext.configpara baixar o pico.
- Criar um arquivo de swap na VPS (2 GB já ajudam muito). Em Ubuntu:
- O site que já estava no ar continua no ar durante o build. Depois do build o container antigo é parado e guardado (não apagado) e o novo sobe; se o novo não subir ou o health check falhar, o painel liga o antigo de volta e o log diz isso. O antigo e a imagem anterior só são removidos depois que o novo responde. Depois de um deploy revertido, o botão Recriar (e a troca de fuso que recria containers) é recusado: a imagem mais recente é a que falhou. A saída é um novo deploy que fique saudável, que também libera o Recriar. Se a Porta do projeto mudou nesse deploy, o site segue na porta da versão antiga até lá.
- Um deploy sem mudança nas dependências costuma ser bem mais rápido: o painel guarda o cache de instalação do gerenciador de pacotes por projeto, com um nome imprevisível (uma chave aleatória guardada na pasta de trabalho do projeto, fora do seu código): o cache de build do Docker é do servidor inteiro, e o nome imprevisível impede que o Dockerfile de outro projeto o monte, leia ou envenene.
Se o deploy falhar por causa do lockfile
Seção intitulada “Se o deploy falhar por causa do lockfile”A instalação é congelada: se o package.json e o lockfile não combinam, o log explica. Rode pnpm install
(ou npm install / yarn install) no seu computador, commite o lockfile atualizado e faça o deploy de novo.
Disco: cache e imagens
Seção intitulada “Disco: cache e imagens”O cache de dependências e as camadas do build ficam no BuildKit do Docker e crescem com o uso.
- Automático: depois de cada deploy que ficou saudável o painel remove a imagem anterior do projeto e o cache de build com mais de 7 dias.
- Manual, quando precisar de espaço (na VPS):
Limpar o cache só deixa o próximo build mais lento; nada que está no ar é afetado.
Janela do terminal docker system dfdocker builder prune --filter until=24hdocker image prune
Segurança
Seção intitulada “Segurança”O servidor automático segue as mesmas regras dos outros tipos:
- As travas de segurança do deploy rodam sobre o seu código antes do build.
- O container roda como
node(não root), comcap_drop ALLeno-new-privileges, sem pastas do servidor montadas e sem porta publicada: só a rede interna do painel e o Caddy. - A imagem base é a oficial
node:<versão>-alpine, fixada por digest. - O código dentro da imagem pertence ao root e é somente leitura para o processo; só
.next/cache(cache de imagens do Next.js) é gravável pelo usuárionode. Se o seu app grava arquivos em runtime, use um volume (Dockerfile ou compose seu). - Arquivos
.env*do repositório não entram no build (nem o.envque o painel gravou quando o projeto era compose, que é apagado). AsNEXT_PUBLIC_*vêm das Variáveis do projeto, não de.env.productionversionado. - As imagens dos projetos não passam por varredura de vulnerabilidades. O Trivy roda no CI só sobre a imagem do
próprio painel. As imagens
node:*-alpinesão oficiais e fixadas por digest, mas as dependências do seu projeto são suas: mantenha-as atualizadas (por exemplo, rodandotrivy imagevocê mesmo). A varredura das imagens dos projetos está no roadmap. - O Dockerfile gerado não vai para o seu repositório e o segredo do build é um arquivo temporário (
0600) apagado logo depois do build. - O build roda o seu código (scripts de instalação,
next.config), como qualquer build Docker: só publique repositórios em que você confia.
Quando algo dá errado
Seção intitulada “Quando algo dá errado”| Sintoma | O que fazer |
|---|---|
| “O build ficou sem memória” | Crie swap, libere memória ou use mais RAM (veja acima) |
| “O lockfile não bate com o package.json” | Atualize e commite o lockfile |
| “pede output standalone, mas o build não gerou .next/standalone/server.js” | Monorepo ou output vindo de expressão: escreva o valor direto ou use um Dockerfile seu |
| O site abre com a página de erro 502 logo após o deploy | O servidor demora a subir: confira a aba Logs do projeto |
Mudei uma NEXT_PUBLIC_ e o site mostra o valor antigo | Faça um novo deploy (reiniciar não basta) |
| O tipo aparece como “dockerfile” mas você queria o automático | Há um Dockerfile na raiz: remova-o (ou renomeie) e rode a detecção de novo |