Next.js: Docker Self-Hosting & Implantação
Última atualização: 2026-08-26
Implantações self-hosted lhe dão controle completo sobre o ambiente de execução da sua aplicação — quando requisitos de conformidade, custo ou rede impedem o uso de uma plataforma em nuvem, o Docker é seu parceiro mais confiável.
1. O Que Você Vai Aprender
- Compreender os principais casos de uso para self-hosting: conformidade de dados / controle de custos / implantação on-premises
- Configurar
next.config.jsno modo de implantação standaloneoutput: 'standalone' - Escrever um Dockerfile multi-estágio (dependências → build → execução) para construir uma imagem mínima
- Usar Nginx como proxy reverso e servidor de conteúdo estático
- Implementar daemonização de processos e reinicialização automática usando PM2
- Usar Docker Compose para orquestrar três containers: App, Nginx e PostgreSQL
2. Uma História Real de um Engenheiro DevOps
(1) Ponto Problemático: O cliente exige que os dados não sejam transferidos para fora do país
Charlie trabalha em uma empresa SaaS que atende instituições financeiras no Oriente Médio. O produto TaskFlow deles precisa ser implantado em um data center local na Arábia Saudita — o cliente exige que todos os dados do usuário sejam armazenados fisicamente dentro da Arábia Saudita.
No entanto, a Vercel não possui um data center na Arábia Saudita. O problema que Charlie enfrenta:
| Problema | Impacto |
|---|---|
| Conformidade de Soberania de Dados | Requisitos Regulatórios Financeiros da Arábia Saudita Proíbem Transferência de Dados para o Exterior |
| Latência de Rede | Latência ao acessar de servidores europeus > 200 ms |
| Vendor Lock-in | Conta Mensal da Vercel de $2.000+ |
| Requisitos de Rede Interna | O cliente deseja implantar a solução na rede interna corporativa |
(2) Soluções para Docker Self-Hosted
Charlie construiu pacotes de implantação portáteis usando Docker:
# Construir Uma Vez, Executar em Qualquer Lugar
docker build -t taskflow:latest .
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-e AUTH_SECRET="..." \
taskflow:latest
(3) Resultados
| Dimensão | Vercel | Docker Self-hosted |
|---|---|---|
| Localização dos Dados | Apenas região Vercel | Qualquer data center |
| Custos Mensais | $2.000+ | $300 (servidor) |
| Latência de Implantação | Global ~100 ms | Local < 20 ms |
| Vendor Lock-in | Alto | Baixo (Migrável) |
3. Configuração output: 'standalone'
O modo output: 'standalone' no Next.js 16 cria um servidor Node.js independente que contém todos os arquivos necessários para executar a aplicação.
graph TB
A[next.config.js] --> B[output: 'standalone']
B --> C[Processo de Build]
C --> D[Diretório .next/standalone/]
D --> E[server.js — Servidor HTTP Independente]
D --> F[.next/static — Recursos Estáticos]
D --> G[node_modules — Dependências Mínimas]
D --> H[package.json — Configuração de Entrada]
style A fill:#cce5ff
style D fill:#d4edda
(1) Configurando next.config.js
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// Dependências que requerem processamento externo
serverExternalPackages: ['@prisma/client'],
// Otimização para Ambiente de Produção
productionBrowserSourceMaps: false,
swcMinify: true,
// Otimização de Imagem Mantida
images: {
unoptimized: false
}
}
module.exports = nextConfig
(2) Estrutura do diretório de saída standalone
.next/standalone/
├── server.js # Servidor HTTP Independente (Entrada)
├── package.json # Declarações de Dependências de Tempo de Execução
├── node_modules/ # Dependências apenas de build
├── .next/
│ ├── server/ # Código do lado do servidor
│ ├── static/ # Recursos Estáticos
│ ├── build-manifest.json
│ └── ...
├── public/ # Recursos Estáticos Públicos
└── trace # Rastreamento de Build
▶ Exemplo: Verificando um build standalone
# Construir Projeto
npm run build
# Ver Tamanho do Diretório standalone
du -sh .next/standalone/
# Iniciar um Servidor Dedicado
node .next/standalone/server.js
# Verificar em outro terminal
curl http://localhost:3000
.next/standalone/ 358M # Tamanho Total
.next/standalone/server.js # Arquivo de Entrada (Gerado Automaticamente)
Saída:
.next/standalone/ 358M
node .next/standalone/server.js
▲ Next.js 16.0.0
- Local: http://localhost:3000
✓ Pronto em 1.2s
4. Builds Docker Multi-estágio
Builds multi-estágio dividem a imagem em três estágios: instalação de dependências → build da aplicação → ambiente de execução mínimo.
# ============================================
# Dockerfile — Construção Multi-estágio Next.js 16
# ============================================
# --- Fase 1: Instalação de Dependências ---
FROM node:20-alpine AS deps
LABEL stage=deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json pnpm-lock.yaml ./
RUN npm ci --only=production && \
npm cache clean --force
# --- Fase 2: Build ---
FROM node:20-alpine AS build
LABEL stage=build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN npm run build
# --- Fase 3: Execução ---
FROM node:20-alpine AS runner
LABEL stage=runner
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
WORKDIR /app
# Copiar os artefatos de build
COPY --from=build --chown=nextjs:nodejs \
/app/.next/standalone ./
COPY --from=build --chown=nextjs:nodejs \
/app/.next/static ./.next/static
COPY --from=build --chown=nextjs:nodejs \
/app/public ./public
# Verificação de Saúde
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
ENV NODE_ENV=production
CMD ["node", "server.js"]
(1) Construindo e Executando
# Construir uma imagem
docker build -t taskflow:latest .
# Ver Tamanho da Imagem
docker images taskflow:latest
# Executar um Container
docker run -d \
--name taskflow-app \
-p 3000:3000 \
-e DATABASE_URL="postgresql://user:pass@host:5432/taskflow" \
-e AUTH_SECRET="sua-chave-secreta" \
-e NEXT_PUBLIC_API_URL="https://api.taskflow.local" \
--restart unless-stopped \
taskflow:latest
(2) Comparação de Tamanhos de Imagem Entre Estágios
| Estágio | Imagem Base | Tamanho | Conteúdo |
|---|---|---|---|
| deps | node:20-alpine | ~150 MB | node_modules + dependências do sistema |
| build | node:20-alpine | ~450 MB | código fonte + node_modules + artefatos de build |
| runner | node:20-alpine | ~358 MB | standalone + dependências de produção |
| Bare node:20-alpine | — | ~126 MB | Sistema base |
▶ Exemplo: Injeção de Variáveis de Ambiente no Docker
Saída:
Sending build context to Docker daemon 4.096kB
Step 1/8 : FROM node:20-alpine AS deps
---> 1dd67de0f936
...
Successfully built a7b3c2d1e0f9
Successfully tagged shophub:latest
Comando Docker concluído com sucesso.
d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4
# Uso de arquivo .env para Injetar Variáveis de Ambiente
cat > .env.production << EOF
DATABASE_URL=postgresql://user:pass@db:5432/taskflow
AUTH_SECRET=chave-super-secreta
NEXT_PUBLIC_API_URL=https://api.taskflow.local
NEXT_PUBLIC_POSTHOG_KEY=phc_xxxx
REDIS_URL=redis://redis:6379
EOF
docker run -d \
--name taskflow-app \
--env-file .env.production \
-p 3000:3000 \
--network taskflow-net \
taskflow:latest
Saída:
Operação Docker concluída.
5. Proxy Reverso Nginx
O Nginx lida com terminação SSL, cache de recursos estáticos e balanceamento de carga, sendo um componente essencial em ambientes de produção.
graph LR
A[Navegador do usuário] --> B[Nginx :443]
B --> C{Correspondência de Caminho}
C -->|/_next/static/*| D[Serviços Diretos Nginx<br/>Cache 1 ano]
C -->|/api/health| E[Next.js :3000]
C -->|/*| E
B --> F[Terminação SSL<br/>Let's Encrypt]
style B fill:#cce5ff
style D fill:#d4edda
Configuração Nginx
# nginx/nginx.conf
upstream nextjs_upstream {
server app:3000;
keepalive 64;
}
server {
listen 80;
server_name taskflow.local;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name taskflow.local;
# Certificado SSL
ssl_certificate /etc/nginx/ssl/taskflow.crt;
ssl_certificate_key /etc/nginx/ssl/taskflow.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Cabeçalhos de Segurança
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Log
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Cache de Recursos Estáticos (processados pelo Next.js)
location /_next/static/ {
proxy_pass http://nextjs_upstream;
expires 365d;
add_header Cache-Control "public, immutable";
}
location /static/ {
proxy_pass http://nextjs_upstream;
expires 30d;
add_header Cache-Control "public";
}
# Endpoints de Verificação de Saúde
location /api/health {
proxy_pass http://nextjs_upstream;
access_log off;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# Todas as outras requisições são encaminhadas para o Next.js
location / {
proxy_pass http://nextjs_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
6. Gerenciador de Processos PM2
O PM2 garante que os processos Node.js reiniciem automaticamente após uma falha e fornece gerenciamento de logs e modo cluster.
(1) Configuração do PM2
// ecosystem.config.js
module.exports = {
apps: [{
name: 'taskflow',
script: 'server.js',
cwd: '/app',
// Modo Cluster (Usar todos os Núcleos de CPU)
exec_mode: 'cluster',
instances: 'max',
// Variáveis de Ambiente
env: {
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '0.0.0.0'
},
// Configuração de Log
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
error_file: '/var/log/pm2/taskflow-error.log',
out_file: '/var/log/pm2/taskflow-out.log',
merge_logs: true,
// Reinicialização Automática
max_restarts: 10,
restart_delay: 1000,
min_uptime: 5000,
// Monitoramento de Memória
max_memory_restart: '500M',
// Verificação de Saúde
listen_timeout: 3000,
kill_timeout: 5000
}]
}
(2) Integração PM2 com Docker
# Instalar PM2 no estágio runner
FROM node:20-alpine AS runner
RUN npm install -g pm2 && \
addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
WORKDIR /app
COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=build --chown=nextjs:nodejs /app/public ./public
COPY --chown=nextjs:nodejs ecosystem.config.js ./
USER nextjs
EXPOSE 3000
# Usar PM2 para Habilitar Modo Cluster
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
▶ Exemplo: Comandos Comuns do PM2
Saída:
Imagem Docker construída e container iniciado com sucesso.
# Ver Todos os Processos
pm2 list
# Ver Logs
pm2 logs taskflow
pm2 logs taskflow --lines 100
# Monitorar Recursos
pm2 monit
# Recarregar (Zero Downtime)
pm2 reload taskflow
# Parar/Reiniciar
pm2 stop taskflow
pm2 restart taskflow
# Salvar a lista atual de processos
pm2 save
pm2 startup
Saída:
Comando PM2 executado com sucesso.
Comando PM2 executado com sucesso.
Comando PM2 executado com sucesso.
Comando PM2 executado com sucesso.
Comando PM2 executado com sucesso.
Comando PM2 executado com sucesso.
┌────┬──────────┬─────────┬─────────┐
│ id │ name │ status │ cpu │
├────┼──────────┼─────────┼─────────┤
│ 0 │ shophub │ online │ 0% │
└────┴──────────┴─────────┴─────────┘
Comando PM2 executado com sucesso.
┌────┬──────────┬─────────┬─────────┐
│ id │ name │ status │ cpu │
├────┼──────────┼─────────┼─────────┤
│ 0 │ shophub │ online │ 0% │
└────┴──────────┴─────────┴─────────┘
7. Docker Compose: Orquestrando Três Containers
O Docker Compose orquestra três containers — App, Nginx e PostgreSQL — para iniciar todo o ambiente com um único clique.
# docker-compose.yml
version: '3.8'
networks:
taskflow-net:
driver: bridge
volumes:
postgres-data:
driver: local
nginx-logs:
driver: local
services:
# === 1. Banco de Dados PostgreSQL ===
db:
image: postgres:16-alpine
container_name: taskflow-db
restart: unless-stopped
networks:
- taskflow-net
volumes:
- postgres-data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d
environment:
POSTGRES_USER: taskflow
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: taskflow
healthcheck:
test: ["CMD-SHELL", "pg_isready -U taskflow"]
interval: 10s
timeout: 5s
retries: 5
ports:
- "5432:5432"
# === 2. Aplicação Next.js ===
app:
build:
context: .
dockerfile: Dockerfile
target: runner
image: taskflow:latest
container_name: taskflow-app
restart: unless-stopped
networks:
- taskflow-net
depends_on:
db:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 3000
HOSTNAME: "0.0.0.0"
DATABASE_URL: postgresql://taskflow:${DB_PASSWORD}@db:5432/taskflow
AUTH_SECRET: ${AUTH_SECRET}
AUTH_URL: ${AUTH_URL}
NEXT_PUBLIC_API_URL: ${PUBLIC_API_URL}
NEXT_PUBLIC_POSTHOG_KEY: ${POSTHOG_KEY:-}
env_file:
- .env.production
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 3s
retries: 3
# === 3. Proxy Reverso Nginx ===
nginx:
image: nginx:alpine
container_name: taskflow-nginx
restart: unless-stopped
networks:
- taskflow-net
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx-logs:/var/log/nginx
depends_on:
app:
condition: service_healthy
(1) Arquivo de Variáveis de Ambiente
# .env.production (Não enviar para o Git)
DB_PASSWORD=SenhaForte123!
AUTH_SECRET=sua-chave-secreta-auth-min-32-caracteres
AUTH_URL=https://auth.taskflow.local
PUBLIC_API_URL=https://api.taskflow.local
POSTHOG_KEY=phc_exemploChave123
(2) Inicialização e Gerenciamento
# Primeiro Lançamento
docker compose up -d
# Ver Logs
docker compose logs -f app
docker compose logs -f nginx
# Reconstruir a aplicação
docker compose build app
docker compose up -d app
# Atualizar Migração do Banco de Dados
docker compose exec app npx prisma migrate deploy
# Ver Status de Funcionamento
docker compose ps
# Parar todos os serviços
docker compose down
# Limpeza Completa (incluindo volumes)
docker compose down -v
▶ Exemplo: docker-compose.override.yml (ambiente de desenvolvimento)
Saída:
Salve a configuração YAML acima no caminho de arquivo especificado. As configurações entrarão em vigor na próxima reinicialização do servidor.
# docker-compose.override.yml
version: '3.8'
services:
app:
build:
target: build # Para uso durante a fase de desenvolvimento build em vez de runner
environment:
NODE_ENV: development
volumes:
- ./src:/app/src:ro
- ./public:/app/public:ro
command: npm run dev # Usando o Servidor de Desenvolvimento
db:
ports:
- "5432:5432" # Expondo a Porta do Banco de Dados Durante o Desenvolvimento
nginx:
ports:
- "3000:80" # Simplificar Mapeamento de Porta Durante o Desenvolvimento
Saída:
Salve a configuração YAML acima no caminho de arquivo especificado. As configurações entrarão em vigor na próxima reinicialização do servidor.
8. Injeção de Variáveis de Ambiente em Tempo de Execução
As variáveis de ambiente para containers Docker são injetadas em tempo de execução, não durante o processo de build — isso permite que uma única imagem seja implantada em múltiplos ambientes.
graph LR
A[Docker Build] --> B[Imagem<br/>(Sem variáveis de ambiente)]
B --> C[Injeção em Tempo de Execução]
C --> D[Ambiente de Desenvolvimento .env.dev]
C --> E[Ambiente de Teste .env.test]
C --> F[Ambiente de Produção .env.prod]
D --> G[Inicialização do Container]
E --> G
F --> G
G --> H[server.js Lê process.env]
style B fill:#cce5ff
style G fill:#d4edda
(1) Variáveis de Build vs. Tempo de Execução
| Tipo de Variável | Momento da Injeção | Exemplo | Local de Armazenamento |
|---|---|---|---|
| Durante o Build | docker build |
NEXT_PUBLIC_*, Número da Versão |
Dockerfile ARG |
| Tempo de Execução | docker run |
DATABASE_URL、AUTH_SECRET |
docker compose env_file |
| Misto | Ambos são necessários | NEXT_PUBLIC_API_URL |
Injetado no front-end durante o build e no back-end em tempo de execução |
(2) Injeção de Variáveis Durante o Build
# Dockerfile Usando ARG para Passar Variáveis de Build
FROM node:20-alpine AS build
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ARG SENTRY_DSN
ENV SENTRY_DSN=$SENTRY_DSN
RUN npm run build
# Variáveis passadas durante a construção
docker build \
--build-arg NEXT_PUBLIC_API_URL=https://api.taskflow.com \
--build-arg SENTRY_DSN=https://xxx@sentry.io/123 \
-t taskflow:latest .
▶ Exemplo: Script de Validação de Ambiente em Tempo de Execução
Saída:
Sending build context to Docker daemon 4.096kB
Step 1/8 : FROM node:20-alpine AS deps
---> 1dd67de0f936
...
Successfully built a7b3c2d1e0f9
Successfully tagged shophub:latest
// src/lib/env.ts
// Validação de Variáveis de Ambiente em Tempo de Execução
function getRequiredEnvVar(name: string): string {
const value = process.env[name]
if (!value) {
throw new Error(`Variável de ambiente obrigatória ausente: ${name}`)
}
return value
}
export const env = {
databaseUrl: getRequiredEnvVar('DATABASE_URL'),
authSecret: getRequiredEnvVar('AUTH_SECRET'),
authUrl: process.env.AUTH_URL || 'http://localhost:3000',
publicApiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
posthogKey: process.env.NEXT_PUBLIC_POSTHOG_KEY,
nodeEnv: process.env.NODE_ENV || 'development',
isProduction: process.env.NODE_ENV === 'production',
port: parseInt(process.env.PORT || '3000', 10)
}
Saída:
Exporta: env.
9. Exemplo Completo: Implantação Docker do TaskFlow
# ============================================
# Scripts de Implantação em Produção: deploy.sh
# Funcionalidades: Build → Migração → Iniciar → Verificação de Saúde
# ============================================
#!/bin/bash
set -euo pipefail
echo "=== Implantação em Produção do TaskFlow ==="
# 1. Carregar Variáveis de Ambiente
if [ ! -f .env.production ]; then
echo "ERRO: .env.production não encontrado"
exit 1
fi
source .env.production
# 2. Construir Imagem Docker
echo "Construindo imagem Docker..."
docker compose build app
# 3. Iniciar o banco de dados (Se não estiver em execução)
echo "Iniciando banco de dados..."
docker compose up -d db
echo "Aguardando o banco de dados ficar pronto..."
sleep 5
# 4. Executar a migração do banco de dados
echo "Executando migrações do banco de dados..."
docker compose run --rm app npx prisma migrate deploy
# 5. Lançar o app e Nginx
echo "Iniciando aplicação e Nginx..."
docker compose up -d app nginx
# 6. Verificação de Saúde
echo "Executando verificação de saúde..."
for i in {1..10}; do
if curl -s -o /dev/null -w "%{http_code}" http://localhost:80/api/health | grep -q 200; then
echo "Verificação de saúde aprovada!"
break
fi
echo "Aguardando... ($i/10)"
sleep 3
done
# 7. Limpar imagens antigas
echo "Limpando imagens antigas..."
docker image prune -f
# 8. Informações de Implantação
echo ""
echo "=== Implantação Concluída ==="
echo "App: http://localhost:80"
echo "API: http://localhost:80/api/health"
echo "DB: postgresql://taskflow@localhost:5432/taskflow"
echo "Logs: docker compose logs -f app"
echo "Reiniciar: docker compose restart app"
// src/app/api/health/route.ts
// ============================================
// API de Verificação de Saúde: chamada pelo Docker HEALTHCHECK
// ============================================
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
const checks = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
checks: {} as Record<string, boolean>
}
try {
// Verificar a conexão com o banco de dados
await prisma.$queryRaw`SELECT 1`
checks.checks.database = true
} catch {
checks.checks.database = false
checks.status = 'degraded'
}
try {
// Verificar Redis (Se configurado)
// await redis.ping()
checks.checks.redis = true
} catch {
checks.checks.redis = false
if (!checks.checks.database) {
checks.status = 'unhealthy'
}
}
const statusCode = checks.status === 'healthy' ? 200 : 503
return NextResponse.json(checks, { status: statusCode })
}
❓ Perguntas Frequentes
P: Qual é a diferença entre
output: 'standalone'eoutput: 'export'? R:standalonegera um pacote independente que inclui um servidor Node.js e suporta todos os recursos do Next.js, como SSR, ISR e API Routes.exportgera HTML puramente estático (com SSR desabilitado), adequado para hospedagem CDN. Para self-hosting com Docker, você deve usar o modostandalone.
P: Por que um build multi-estágio é melhor que um build de estágio único? R: (1) Imagens menores — o estágio de execução contém apenas os arquivos mínimos necessários para execução (358 MB vs. 1.2 GB); (2) Mais seguro — ferramentas de build e código fonte não estão incluídos na imagem final; (3) Cache de build mais eficiente — como as camadas de dependência raramente mudam, as camadas de cache do Docker podem ser reutilizadas.
P: O Nginx é obrigatório ou opcional? R: O Nginx é fortemente recomendado para ambientes de produção: (1) terminação SSL — tratamento de certificados HTTPS; (2) cache de recursos estáticos — reduzindo a carga no Node.js; (3) Injeção de cabeçalhos de segurança — XSS, CSP, HSTS, etc.; (4) Balanceamento de carga — distribuindo requisições entre múltiplas instâncias. Em ambientes simples de rede interna, você pode pular esta etapa e expor a porta do Next.js diretamente.
P: Preciso usar tanto o PM2 quanto a política de restart do Docker? R: Recomendamos usar ambos. O
restart: unless-stoppeddo Docker trata falhas no nível do container (como OOM), enquanto o PM2 trata falhas no nível do processo Node.js (como exceções não capturadas). O PM2 também fornece recursos que o Docker por si só não oferece, como rotação de logs, modo cluster e reinicializações com zero downtime.
P: Como o ambiente de execução garante que as variáveis NEXT_PUBLIC_ sejam injetadas durante o build?* R: Variáveis com o prefixo
NEXT_PUBLIC_*são incorporadas ao bundle JS durante o build e não podem ser modificadas em tempo de execução. Para implantações self-hosted, a solução é: (1) Passar o valorNEXT_PUBLIC_*durante o build; (2) Ou colocar variáveis que requerem configuração em tempo de execução na resposta da API route (por exemplo,/api/config), que o frontend obtém viafetch.
P: Qual devo escolher, Docker Compose ou Kubernetes? R: Para implantações em servidor único, escolha Docker Compose (configuração simples, baixa curva de aprendizado); para clusters multi-servidor, escalonamento automático e descoberta de serviços, escolha Kubernetes. Para equipes pequenas a médias (1–5 servidores), o Docker Compose em modo Swarm é suficiente para a maioria dos cenários.
📖 Resumo
- A configuração
next.config.jsparaoutput: 'standalone'é um pré-requisito para Docker self-hosted e gera um servidor Node.js independente - Um build Docker multi-estágio (deps → build → runner) comprime a imagem para ~358MB, reduzindo custos de transmissão e armazenamento
- O Nginx atua como proxy reverso, fornecendo terminação SSL, cache estático e injeção de cabeçalhos de segurança, sendo essencial para ambientes de produção
- O PM2 fornece daemons de processo, modo cluster, reinicializações com zero downtime e gerenciamento de logs para aumentar a confiabilidade da aplicação
- O Docker Compose orquestra três containers: App, Nginx e PostgreSQL;
docker compose up -dinicialização com um clique - Variáveis de ambiente em tempo de execução são injetadas via
docker run -eouenv_file, permitindo que uma única imagem seja implantada em múltiplos ambientes.
📝 Exercícios
-
Problema Básico (⭐): Crie um
next.config.jsque contenhaoutput: 'standalone', escreva um Dockerfile multi-estágio, construa e execute com sucessodocker rune depois verifique usandocurl localhost:3000. -
Exercício Avançado (⭐⭐): Adicione um container proxy reverso Nginx ao Docker Compose: (1) Configure um certificado SSL autoassinado; (2) Adicione regras de cache para recursos estáticos; (3) Configure
/_next/staticpara ser armazenado em cache por 365 dias; (4) Verifique se o acesso HTTPS funciona corretamente. -
Desafio (⭐⭐⭐): Construa um pipeline completo de CI/CD self-hosted + Docker: (1) Use GitHub Actions para construir automaticamente imagens Docker e enviá-las para o GHCR; (2) Puxe as novas imagens para o servidor de destino via SSH; (3) Use Docker Compose para atualizações com zero downtime (
docker compose up -d --no-deps --build app); (4) Configure o modo cluster PM2 e rotação de logs.