Pular para o conteúdo
devops

Docker para Desenvolvedores Backend: Do Zero à Produção

Guia prático de Docker para desenvolvedores backend: Dockerfile otimizado, Docker Compose, variáveis de ambiente, volumes e boas práticas de imagem.

Douglas M. Pereira4 min de leitura
dockerdevopscontainerizaçãobackenddeploy

Por que todo desenvolvedor backend precisa saber Docker

Docker eliminou a frase "funciona na minha máquina". Com container, o ambiente de desenvolvimento, staging e produção são idênticos. Isso resolve problemas de dependência, facilita onboarding de novos devs e torna o deploy consistente e reproduzível.

Dockerfile otimizado para aplicações Node.js

A maioria dos Dockerfiles que aparecem em tutoriais não são prontos para produção. Este é:

# Multi-stage build: builder separado do runtime
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Imagem final: apenas o necessário
FROM node:22-alpine AS runner
WORKDIR /app

# Usuário não-root por segurança
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nodeuser

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

USER nodeuser

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

Por que multi-stage?

  • Imagem final não contém devDependencies (segurança e tamanho)
  • Código fonte não está na imagem de produção
  • Cache de layers mais eficiente

.dockerignore obrigatório

node_modules
.git
.env
.env.*
coverage
dist
*.log
.DS_Store
README.md

Sem isso, COPY . . copia node_modules inteiro para o contexto de build — lento e problemático.

Docker Compose para desenvolvimento

# docker-compose.yml
version: '3.9'

services:
  app:
    build:
      context: .
      target: builder  # usa o stage de build para dev (com devdeps)
    volumes:
      - ./src:/app/src  # hot reload
    ports:
      - "3000:3000"
    env_file:
      - .env.local
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    command: npm run dev

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
      - ./db/init:/docker-entrypoint-initdb.d
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  db_data:
  redis_data:

Variáveis de ambiente com segurança

# .env.example (versionado — sem valores reais)
DATABASE_URL=mysql://user:password@db:3306/app
REDIS_URL=redis://redis:6379
JWT_SECRET=your-secret-here
RESEND_API_KEY=re_your-key

# .env.local (não versionado)
DATABASE_URL=mysql://myuser:secret@localhost:3306/myapp
JWT_SECRET=dev-secret-very-long-string

Nunca faça ENV SECRET_KEY=hardcoded_value no Dockerfile — isso armazena o segredo em todas as layers da imagem.

Comandos essenciais do dia a dia

# Build
docker build -t my-app:latest .

# Executar com variáveis de ambiente
docker run --env-file .env -p 3000:3000 my-app:latest

# Compose — fluxo de desenvolvimento
docker compose up -d          # sobe tudo em background
docker compose logs -f app    # acompanha logs da aplicação
docker compose exec app bash  # abre shell no container
docker compose restart app    # reinicia apenas a aplicação
docker compose down -v        # remove containers e volumes

# Inspecionar
docker inspect <container_id>
docker stats                  # uso de CPU e memória em tempo real
docker system prune -af       # limpa tudo não utilizado

Boas práticas de imagem

Use Alpine ou Distroless

# Alpine: menor, mais seguro que imagens full
FROM node:22-alpine

# Distroless: só runtime, sem shell (máxima segurança)
FROM gcr.io/distroless/nodejs22-debian12

Fixe versões

# Ruim: pode mudar comportamento a qualquer momento
FROM node:latest

# Bom: comportamento determinístico
FROM node:22.11.0-alpine3.20

Ordene layers por frequência de mudança

# Dependências mudam pouco — cacheia mais
COPY package*.json ./
RUN npm ci

# Código-fonte muda frequentemente — no fim
COPY . .
RUN npm run build

Health check na aplicação

// Endpoint de health que o Docker e o load balancer verificam
app.get('/health', async (req, res) => {
  try {
    await db.query('SELECT 1')  // verifica conexão com banco
    await redis.ping()           // verifica Redis
    res.json({ status: 'ok', uptime: process.uptime() })
  } catch (error) {
    res.status(503).json({ status: 'degraded', error: String(error) })
  }
})

Conclusão

Docker bem usado garante paridade entre ambientes, facilita onboarding e torna o deploy confiável. O investimento em aprender Dockerfile multi-stage, Docker Compose e boas práticas de segurança se paga na primeira vez que você evita um "funciona em dev mas quebra em produção".