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