Deploy em containers não é só empurrar um botão
A maioria dos times que eu vejo tentando colocar serviços em produção com Docker e Kubernetes erra na configuração inicial, não na execução. O problema não é saber rodar docker compose ou criar um manifest de deployment. O problema é entender o ciclo de vida completo desde o código-fonte até um container rodando estável dentro de um cluster. Eu costumo começar explicando o pipeline antes da teoria. Sem isso, as definições ficam soltas e o pessoal não vê onde cada peça se encaixa.
Comece pelo pipeline, depois entenda as peças
O fluxo básico de técnicas e tecnologias aplicadas a deploy containerizado segue esta sequência: código commitado no repositório dispara um build automatizado, o build gera uma imagem do container com tag versionada, essa imagem é enviada para um registry, e finalmente o Kubernetes ou a ferramenta de orquestração escolhida puxa a imagem e sobe os pods com as configurações de health check, resource limits e rolling update. Isso parece simples na teoria. Na prática, cada etapa tem armadilhas que só aparecem em produção.
O primeiro passo é configurar o Dockerfile de forma eficiente. Use multi-stage builds. Um Dockerfile que eu uso como base para projetos Node.js em produção tem aproximadamente 12 linhas de build stage e 8 de runtime stage. Isso reduz a imagem final de cerca de 900MB para algo na casa dos 180MB. Menos tamanho significa menor tempo de pull durante os deployments e menos consumo de rede. Aqui vai algo que pouco gente leva a sério no início: a ordem dos comandos no Dockerfile importa para o cache. Se você coloca o copy do package.json antes do copy do código-fonte inteiro, o comando npm ci ou yarn install usa a camada cacheada sempre que só o código muda, sem precisar reinstalar dependências. Trocar essa ordem é um erro comum que transforma um build de 2 minutos em um build de 15 minutos porque todas as camadas são refeitas do zero.
Orquestração e o problema que ninguém conta
Kubernetes resolve muitos problemas, mas introduz outros. Liveness probes mal configurados são um deles. Eu configurei um probe de saúde num serviço de worker de fila que processava jobs async. A lógica do probe era fazer uma requisição HTTP para /health a cada 10 segundos com timeout de 3 segundos. O problema é que o serviço, quando estava processando lotes grandes, levava até 8 segundos para responder qualquer requisição. O Kubernetes matava o container a cada ciclo porque o health check falhava. Como o serviço reiniciava com frequência, ele nunca chegava a processar nada de fato. A solução foi substituir o probe HTTP por um startup probe configurado com failureThreshold de 30 e periodSeconds de 5. O startup probe só verifica se o serviço subiu, não monitora continuamente. Para o monitoramento de saúde em si, eu mudei para um TCP socket probe na porta 8080, que apenas verifica se a porta está aberta, sem forçar uma resposta da aplicação.
Esse ajuste reduziu os restarts desnecessários de algo como 15 por dia para zero. O custo foi ter menos visibilidade em tempo real do estado do serviço, mas isso foi compensado com logs estruturados enviados para o Loki e alertas no Prometheus com base na taxa de processamento de jobs, não em probe de health check.
Registry e versionamento de imagens
Nunca use a tag latest em produção. Ela é uma fonte constante de dor porque não há como reverter com precisão. Cada build deve receber uma tag baseada no SHA do commit, no mínimo. Algo como 7a3f2b1. Se precisar de rollbacks rápidos, mantenha também uma tag de ambiente, como prod-stable, que você atualiza manualmente após validar o deploy em staging. Para imagens privadas, a autenticação precisa ser gerenciada via secrets no Kubernetes. Um pull secret configurado corretamente evita que o erro de ImagePullBackOff te impeça de subir o deployment durante um rollout de emergência. Eu já perdi 40 minutos tentando resolver um deploy bloqueado porque o secret estava referenciado no namespace errado. A solução foi simples: verificar se o spec.template.spec.imagePullSecrets estava apontando para o namespace correto do deployment.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Rolling updates e zero-downtime
O padrão do Kubernetes para deployments é o rolling update. Ele substitui pods antigos por novos gradualmente. A configuração padrão usa maxSurge de 25% e maxUnavailable de 25%. Isso significa que, em um deployment com 8 réplicas, até 10 pods podem existir simultaneamente (8 originais + 2 novos) e no máximo 2 pods podem ficar indisponíveis durante o processo. O problema é que nem toda aplicação suporta receber tráfego novo enquanto ainda está inicializando. Bibliotecas de conexão com banco de dados e clientes de filas muitas vezes fazem retry apenas no início, e se o banco não estiver pronto, o pod entra em crashloop. A solução prática é adicionar um initContainer que faz um wait-for-it antes do container principal começar. Um script simples que verifica conectividade com o serviço dependente antes de liberar o pod para receber tráfego.
Uma configuração típica de health check para esse cenário seria:
- readinessProbe com httpGet na porta 3000, path /ready, initialDelaySeconds de 5, periodSeconds de 10, failureThreshold de 3
- livenessProbe idem, mas com failureThreshold de 2 e periodSeconds de 15
- startupProbe com httpGet no mesmo endpoint, failureThreshold de 15, periodSeconds de 5
O readiness probe é o que determina se o pod recebe tráfego. O liveness probe mata o container se falhar repetidamente. O startup probe dá tempo para inicializações longas sem interferir nos outros dois.
Limitações reais que você precisa saber
Kubernetes não é solução para tudo. Para aplicações pequenas, com menos de 5 services e tráfego moderado, um servidor VPS com Docker Compose e Nginx como reverse proxy entrega o mesmo resultado com uma fração da complexidade. O overhead de manter um cluster rodando só para ter mais ferramentas disponíveis é desnecessário na maioria dos casos. Efetivamente, Kubernetes faz sentido a partir de cerca de 10 microsserviços em produção, ou quando você tem requisitos reais de scaling automático, multi-cloud, ou compliance que exige isolamento de ambientes. Fora disso, você está gasto_complexidade que não traz valor proporcional.
O mesmo vale para ferramentas de CI/CD. GitHub Actions, GitLab CI, CircleCI. Todas funcionam. A escolha deve considerar se sua equipe já domina alguma delas. Migrar de uma para outra tem custo de adaptação e de manutenção dos pipelines existentes. Não vale a pena por curiosidade técnica. Outro ponto: monitoring em Kubernetes sem uma estratégia definida vira caos. Configurar Prometheus, Grafana, Loki e Alertmanager do zero pode levar horas em um ambiente simples. Ferramentas como k9s ajudam na debugação mas não resolvem a observabilidade. Se o time não tiver alguém dedicado a manter os dashboards e os alertas, eles viram ruído e ninguém mais os consulta depois de duas semanas.
O que funciona na prática
Para quem está começando agora, o caminho mais eficiente é: Dockerfile multi-stage, registry privado com tags por SHA, Kubernetes só se o problema justificar, readiness probes bem configurados, e logs centralizados desde o início. Não tente configurar auto-scaling horizontal no primeiro deploy. Deixe ele como melhoria posterior, quando o volume de tráfego exigir. O erro mais comum que eu vejo em revisões de código e configurações é a falta de resource requests e limits nos containers. Sem requests definidos, o scheduler do Kubernetes não consegue colocar os pods nos nós de forma eficiente. Sem limits, um container pode consumir memória ilimitada e derrubar o nó inteiro. Configurá-los corretamente no início evita problemas que parecem aleatórios e são extremamente difíceis de diagnosticar depois.
Requests e limits não são opcionais. Eles são parte obrigatória de qualquer configuração de produção séria, independente do tamanho da aplicação.