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



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:

BASH
# 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.

100%
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

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

TEXT 📖 Somente leitura
.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

BASH
# 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
💻 Saída:

TEXT 📖 Somente leitura
.next/standalone/    358M    # Tamanho Total
.next/standalone/server.js   # Arquivo de Entrada (Gerado Automaticamente)

Saída:

TEXT 📖 Somente leitura
.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
# ============================================
# 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

BASH
# 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:

TEXT 📖 Somente leitura
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
BASH
# 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:

TEXT 📖 Somente leitura
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.

100%
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/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

JS
// 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

DOCKERFILE
# 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:

TEXT 📖 Somente leitura
Imagem Docker construída e container iniciado com sucesso.
BASH
# 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:

TEXT 📖 Somente leitura
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.

YAML
# 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

BASH
# .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

BASH
# 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:

TEXT 📖 Somente leitura
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.
YAML
# 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:

TEXT 📖 Somente leitura
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.

100%
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_URLAUTH_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
# 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
BASH
# 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:

TEXT 📖 Somente leitura
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
TS
// 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:

TEXT 📖 Somente leitura
Exporta: env.


9. Exemplo Completo: Implantação Docker do TaskFlow

BASH
# ============================================
# 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"
TS
// 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' e output: 'export'? R: standalone gera um pacote independente que inclui um servidor Node.js e suporta todos os recursos do Next.js, como SSR, ISR e API Routes. export gera HTML puramente estático (com SSR desabilitado), adequado para hospedagem CDN. Para self-hosting com Docker, você deve usar o modo standalone.

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-stopped do 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 valor NEXT_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 via fetch.

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


📝 Exercícios

  1. Problema Básico (⭐): Crie um next.config.js que contenha output: 'standalone', escreva um Dockerfile multi-estágio, construa e execute com sucesso docker run e depois verifique usando curl localhost:3000.

  2. 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/static para ser armazenado em cache por 365 dias; (4) Verifique se o acesso HTTPS funciona corretamente.

  3. 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%