Docker para Desenvolvedores: Além do Básico

Docker se tornou essencial no desenvolvimento moderno, mas muitos desenvolvedores ficam apenas no básico. Neste post, compartilho técnicas avançadas que uso no dia a dia para otimizar workflows e melhorar a eficiência.

Multi-Stage Builds: O Game Changer

Multi-stage builds permitem criar imagens menores e mais seguras:

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine AS production
WORKDIR /app

# Copiar apenas dependências de produção
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

EXPOSE 3000
CMD ["node", "dist/index.js"]

Resultado: 800MB → 150MB

Docker Compose Avançado

1. Profiles para Diferentes Ambientes

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis
    profiles: ["app"]

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    profiles: ["app", "db-only"]

  redis:
    image: redis:7-alpine
    profiles: ["app", "cache"]

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app
    profiles: ["production"]

volumes:
  postgres_data:

Uso:

# Desenvolvimento
docker-compose --profile app up

# Apenas banco para testes
docker-compose --profile db-only up

# Produção com proxy
docker-compose --profile production up

2. Override Files para Ambientes

# docker-compose.override.yml (desenvolvimento)
version: '3.8'
services:
  app:
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    command: npm run dev
# docker-compose.prod.yml (produção)
version: '3.8'
services:
  app:
    restart: unless-stopped
    environment:
      - NODE_ENV=production
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

Otimizações de Performance

1. Layer Caching Inteligente

FROM node:18-alpine

WORKDIR /app

# Copiar apenas package.json primeiro (caching)
COPY package*.json ./
RUN npm ci --only=production

# Copiar código depois
COPY . .

# Build apenas se código mudou
RUN npm run build

EXPOSE 3000
CMD ["npm", "start"]

2. .dockerignore Otimizado

# Dependências
node_modules
npm-debug.log

# Arquivos de desenvolvimento
.git
.gitignore
README.md
.env
.nyc_output
coverage
.npm

# Arquivos de build local
dist
build

# Arquivos de IDE
.vscode
.idea
*.swp
*.swo

# Logs
logs
*.log

# Cache
.cache
.parcel-cache

Desenvolvimento com Hot Reload

Dockerfile para desenvolvimento:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

# Instalar nodemon globalmente
RUN npm install -g nodemon

COPY . .

EXPOSE 3000

# Usar nodemon em desenvolvimento
CMD ["nodemon", "--legacy-watch", "src/index.js"]

Docker Compose com volumes:

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules  # Volume anônimo para node_modules
    environment:
      - NODE_ENV=development

Health Checks e Monitoring

FROM node:18-alpine

WORKDIR /app
COPY . .
RUN npm install

EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["npm", "start"]

Implementando endpoint de health:

// src/health.js
app.get('/health', (req, res) => {
  const healthcheck = {
    uptime: process.uptime(),
    message: 'OK',
    timestamp: Date.now(),
    checks: {
      database: checkDatabase(),
      redis: checkRedis(),
      memory: process.memoryUsage()
    }
  };
  
  res.status(200).json(healthcheck);
});

BuildKit e Advanced Features

1. Parallel Builds

# syntax=docker/dockerfile:1
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./

FROM base AS dependencies
RUN npm ci --only=production

FROM base AS build
RUN npm ci
COPY . .
RUN npm run build

FROM dependencies AS final
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]

2. Cache Mounts

# syntax=docker/dockerfile:1
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./

# Mount cache para npm
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY . .
CMD ["npm", "start"]

Segurança em Containers

1. Non-root User

FROM node:18-alpine

# Criar usuário não-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

WORKDIR /app
COPY . .
RUN npm ci --only=production

# Mudar ownership dos arquivos
RUN chown -R nextjs:nodejs /app

# Mudar para usuário não-root
USER nextjs

EXPOSE 3000
CMD ["npm", "start"]

2. Scan de Vulnerabilidades

# Scan de imagem
docker scan myapp:latest

# Usando Trivy
trivy image myapp:latest

# Integração com CI/CD
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image myapp:latest

Scripts de Automação

Makefile para Docker:

.PHONY: build run test clean

IMAGE_NAME = myapp
VERSION = $(shell git rev-parse --short HEAD)

build:
    docker build -t $(IMAGE_NAME):$(VERSION) .
    docker tag $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest

run:
    docker-compose up -d

test:
    docker run --rm $(IMAGE_NAME):latest npm test

clean:
    docker system prune -f
    docker volume prune -f

push:
    docker push $(IMAGE_NAME):$(VERSION)
    docker push $(IMAGE_NAME):latest

dev:
    docker-compose -f docker-compose.yml -f docker-compose.dev.yml up

prod:
    docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Debugging em Containers

1. Debug com Node.js

FROM node:18-alpine

WORKDIR /app
COPY . .
RUN npm install

# Expor porta de debug
EXPOSE 3000 9229

# Comando com debug habilitado
CMD ["node", "--inspect=0.0.0.0:9229", "src/index.js"]

2. Docker Compose para debug:

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
      - "9229:9229"  # Debug port
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development

Monitoramento e Logs

1. Centralizando Logs

version: '3.8'
services:
  app:
    build: .
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    
  fluentd:
    image: fluent/fluentd:latest
    volumes:
      - ./fluent.conf:/fluentd/etc/fluent.conf
    depends_on:
      - elasticsearch

2. Métricas com Prometheus

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
      - "9090:9090"  # Métricas

  prometheus:
    image: prom/prometheus
    ports:
      - "9091:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

Conclusão

Docker oferece muito mais do que containerização básica. Com essas técnicas avançadas, você pode:

  • Reduzir tamanho das imagens significativamente
  • Acelerar builds com caching inteligente
  • Melhorar segurança com práticas adequadas
  • Automatizar workflows de desenvolvimento e deploy
  • Monitorar aplicações efetivamente

O investimento em aprender essas técnicas se paga rapidamente em produtividade e qualidade do código.


Quer discutir mais sobre Docker e DevOps? Me encontre no LinkedIn ou confira meus projetos no GitHub.