Um terminal que trabalha por você: zsh, Oh My Zsh, Spaceship e Dracula
O terminal é onde o time de engenharia passa o dia. Veja como montar um setup rápido, bonito e cheio de atalhos com zsh, Oh My Zsh, Spaceship e Dracula.
Novo: Eficify One em beta aberto. Crie seu primeiro ambiente sem cartão.Conhecer a plataforma →

Todo mundo consegue escrever um Dockerfile. Mas a diferença entre uma imagem que passa no tutorial e uma que funciona em produção não é sorte: é engenharia deliberada. Depois de ver incontáveis pipelines quebrando por causa de secrets expostos, pods OOMKilled por falta de limites, imagens de 1.2 GB que demoram 4 minutos para fazer pull, percebi que o problema nunca é o conceito de Docker. É a distância entre saber fazer funcionar e saber fazer direito. Este artigo documenta essa distância com profundidade técnica real.
A maioria dos Dockerfiles que eu vejo em produção tem um cheiro familiar: imagem base ubuntu ou alpine genérica, tudo rodando como root, sem healthcheck, sem limites de recurso, COPY do código inteiro incluindo node_modules ou virtualenv. Funciona na máquina do dev. Em produção, vira problema.
As diferenças não são cosméticas. Elas determinam se sua imagem vai vazar secrets, vai travar em um loop de restart, vai demorar 3 minutos para fazer deploy, ou vai ser explorado por um exploit público. Vou destrinchar cada ponto crítico com código real e trade-offs concretos.
Antes de entrar nos detalhes, preciso de um framing. A tabela abaixo sintetiza o que separa uma imagem de dev de uma imagem de produção em cinco dimensões críticas.
| Aspecto | Dockerfile de tutorial | Imagem de produção |
|---|---|---|
| Tamanho da imagem | Base genérica (ubuntu, debian) com tudo instalado | Slim image com apenas o runtime necessário |
| Usuário | root (padrão) | Usuário não-root com uid fixo |
| Healthcheck | Ausente | Configurado e testado |
| Recursos | Sem limites | CPU e memória declarados |
| Segredos | Hardcoded ou via build args | Mountados em runtime via secrets |
| Tags | latest ou v1.0.0 | Digest SHA256 imutável |
Essa tabela é o mapa. O resto do artigo é a navegação.
Builds multi-stage existem há anos, mas ainda vejo projetos ignorando-os. A ideia é simples: você usa uma imagem de build para compilar, e outra imagem, mínima, para empacotar apenas o artefato final.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm run build
FROM node:20-alpine
# Não copie package.json inteiro, não instale devDeps
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]
Resultado concreto: uma imagem Node que pesava 1.1 GB cai para 180 MB. Build time cai pela metade em pipelines com cache frio. Mais importante: você não carrega compiladores, ferramentas de build nem código fonte na imagem final. Isso elimina uma categoria inteira de CVEs potenciais.
O trade-off é a complexidade de setup inicial e a necessidade de garantir que o artefato compilado seja portável (não dependa de libs nativas do estagio de build). Para linguagens interpretadas como Python ou Ruby, o ganho é menor, mas ainda existe se você usar virtualenvs limpos.
Container rodando como root é o padrão, e é também o maior risco de segurança que você provavelmente tem em produção hoje. Se um exploit no seu app der shell no container, esse shell é root no host, dependendo do driver de storage e das capabilities concedidas.
# Crie um usuário com uid fixo para reproducibilidade
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --gid 1001 --shell /bin/false appuser
USER appuser
Uid fixo importa porque facilita reproducibilidade em clusters onde volumes e permissões são compartilhados. Se você usar uid aleatório, qualquer volume mountado pode dar permissão negada em tempo de deploy.
Um container rodando como root é um container que ainda não foi explorado.
Para workloads que genuinamente precisam de capabilities específicas (ex.: NGINX rodando na porta 80), use capabilities drop por padrão e adicione apenas o mínimo necessário:
securityContext:
runAsNonRoot: true
runAsUser: 1001
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
Sem healthcheck, o orchestrator não sabe quando o seu container está doente. Ele vai continuar mandando tráfego para um processo que está em deadlock, em loop de reconexão, ou simplesmente morto. O resultado é 502s em cascata.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
Para imagens sem curl (que é o caso de alpine slim), instale o wget no healthcheck ou use uma abordagem baseada em arquivo:
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
O --start-period é crítico. Ele dá tempo para aplicações com initialization (banco de dados, caches, migrations) subirem antes do healthcheck começar a contar falhas. Sem ele, containers jovens são marcados como unhealthy e killed antes de ficarem prontos.
Em Kubernetes, o healthcheck do Dockerfile vira o readinessProbe. Para livenessProbe, você precisa de um endpoint separado que indique se o processo está vivo, mesmo que não esteja pronto para receber tráfego.
Sem limites, um container faminto vai consumir tudo e fazer o vizinho pagar. Em Kubernetes, isso vira pod eviction, degradação de performance ou, no pior caso, nó inteiro caindo.
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
Estratégia para descobrir os limites certos: faça load test com o P95 de uso real, depois multiplique por 1.5 para cabeça. Memory limits devem ser suficientes para peak + headroom, porque container em OOM é killed sem graceful shutdown.
Trade-off: limits muito apertados causam throttling de CPU e OOMKilled. Limits muito soltos desperdiçam capacidade e aumentam custo. A calibragem correta é um exercício de medição, não de intuição.
Secrets no Dockerfile (via ARG ou ENV) são visíveis em todas as camadas intermediárias. Qualquer pessoa com acesso à imagem pode extrair o histórico de camadas e ver secrets em texto puro.
# ERRADO: secret vaza para a camada de imagem
ARG DB_PASSWORD
ENV DB_PASSWORD=${DB_PASSWORD}
# CERTO: secret montada em runtime, não construída
CMD ["node", "dist/index.js"]
# O orchestrator injeta via secret mount em /run/secrets/db_password
Em Kubernetes, use Kubernetes Secrets com atenção: por padrão, eles são base64 encoded, não criptografados. Para ambientes com compliance real, use um operator como External Secrets com integração a AWS Secrets Manager, HashiCorp Vault ou Azure Key Vault.
Tag latest é um antipattern em produção. Quando você faz docker pull myapp:latest duas vezes em dias diferentes, pode estar pegando imagens diferentes. Deploys não são reproduzíveis.
# Builds com digest SHA256 (imutável)
docker build -t myapp:$(git rev-parse --short HEAD) .
docker build --build-arg IMAGE_DIGEST=$(docker inspect myapp:latest | jq '.[0].RepoDigests[0]')
# Em produção, referencie por digest, não por tag
docker run myapp@sha256:a3f5e2c8d9...
Em Kubernetes, você pode usar ImagePullPolicy Always para forçar revalidate a cada pull, mas o digest é a única forma de garantir que o que você deployou ontem é o que está rodando hoje.
Além do básico, aqui vai o checklist que separa uma imagem razoável de uma imagem auditável:
-slim reduzem superfícieUma imagem de produção não é um Dockerfile com mais linhas. É uma decisão consciente sobre cada superfície de ataque, cada ponto de falha, cada custo de operação. Builds multi-stage cortam tamanho e vulnerabilidades. Usuário não-root limita o blast radius de exploits. Healthcheck e limites transformam o orchestrator em aliado, não em ignorante. Secrets em runtime eliminam uma categoria inteira de leaks. Digest imutável garante que produção é reproduzível.
Cada item tem trade-off em complexidade de setup. Mas o trade-off de não fazer é pago em incidentes, em cleanup pós-breach, em deploys que falham às 2 da manhã. Se a sua imagem de produção não passa em pelo menos metade dos itens acima, você está rodando dívida técnica que eventualmente vence.
Se sua infraestrutura depende de imagens Docker e você não tem certeza de quantas delas passariam em uma auditoria de segurança ou sobreviveriam a um restart às 3 da manhã, vale 30 minutos para mapear onde estão os riscos. A gente faz isso sem compromisso.
CONTINUE LENDO
O terminal é onde o time de engenharia passa o dia. Veja como montar um setup rápido, bonito e cheio de atalhos com zsh, Oh My Zsh, Spaceship e Dracula.
Vulnerabilidades em bibliotecas de terceiros já foram responsáveis por breaches bilionárias. Este guia apresenta os critérios, as ferramentas e os processos concretos para impedir que código não confiável chegue à produção.
Um guia técnico para integrar agentes de IA nos pipelines de QA, com exemplos de código, armadilhas críticas e uma arquitetura que mantém supervisão humana sem matar a produtividade.
VAMOS CONVERSAR
Conte seu cenário e receba um diagnóstico do seu fluxo de deploy e operação, sem compromisso.
Falar com um especialista