Introdução: do monolito aos microsserviços com Docker e JavaScript
O desenvolvimento web moderno em JavaScript (especialmente no ecossistema Node.js) é impensável sem conteinerização. O Docker deixou de ser apenas uma ferramenta para "empacotar" aplicações — é o padrão de facto para entrega, escalabilidade e gerenciamento de microsserviços. Se você já domina os básicos de Dockerfile e docker run, é hora de avançar para o nível avançado.
Neste artigo, vamos explorar como criar imagens Docker eficientes, seguras e leves para Node.js, configurar ambientes multicontêiner com Docker Compose, organizar a comunicação entre serviços através de redes e gerenciar dados adequadamente com volumes. O material é voltado para desenvolvedores que desejam construir arquiteturas prontas para produção.
1. Otimização do Dockerfile para Node.js: camadas, cache e segurança
Um Dockerfile clássico para Node.js geralmente se parece com isso: FROM node:18, COPY . /app, RUN npm install. No entanto, essa abordagem leva a imagens gigantescas (mais de 1 GB) e builds demorados. A otimização avançada baseia-se em três princípios:
1.1 Builds multiestágio (Multi-stage builds)
Divida o processo em um estágio de build (com compiladores e dependências de desenvolvimento) e um estágio de execução (apenas runtime). Isso reduz a imagem final em 5 a 10 vezes.
# ---- Estágio de build ----FROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY . .RUN npm run build
# ---- Estágio de execução ----FROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/main.js"]
1.2 Uso de .dockerignore e cache de camadas
O Docker armazena em cache as camadas. Para evitar reconstruir o npm install a cada alteração no código, copie o package.json separadamente. Adicione um .dockerignore:
node_modules.git*.md.envdist
Agora, o npm install só é executado quando as dependências mudam, e não o projeto inteiro.
1.3 Segurança: usuário não-root e imagem base mínima
Use node:18-alpine (ou até mesmo scratch com um binário compilado via pkg). Nunca execute Node.js como root:
RUN addgroup -S appgroup && adduser -S appuser -G appgroupUSER appuser
Isso impede a escalada de privilégios em caso de comprometimento da aplicação.
2. Docker Compose: aplicação multicontêiner (Node.js + Redis + Nginx)
Uma aplicação real raramente consiste em um único serviço. O Docker Compose permite, com um único comando docker-compose up, levantar toda a infraestrutura. Vamos considerar um exemplo avançado: API Node.js, Redis para cache e Nginx como proxy reverso.
2.1 Estrutura do docker-compose.yml
version: '3.8'services: app: build: context: ./app dockerfile: Dockerfile.prod ports: - "3000:3000" environment: - REDIS_URL=redis://redis:6379 - NODE_ENV=production depends_on: - redis restart: unless-stopped
redis: image: redis:7-alpine volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3
nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - app
volumes: redis-data:
2.2 Configurações avançadas: healthcheck e dependências
depends_on garante a ordem de inicialização, mas não espera o serviço ficar pronto. Use healthcheck para o Redis e um script de espera na aplicação:
// Em index.js do Node.jsconst co
```