Colocar uma aplicação dentro de um container é fácil. Fazer isso de um jeito que seja rápido, leve e seguro em produção é onde a maioria escorrega. Uma imagem mal construída é lenta para subir, cara para armazenar e, pior, uma porta aberta para problemas de segurança. Veja o que separa o Docker de exemplo de tutorial do Docker de produção.
Imagens enxutas: por que o tamanho importa
Imagem grande é imagem lenta — para baixar, para subir, para escalar. E cada megabyte a mais é superfície de ataque a mais. Alguns princípios:
- Parta de uma base mínima. Imagens
slimoualpinecortam centenas de megabytes de coisas que a sua aplicação não usa. - Instale só o necessário. Ferramentas de build, compiladores e caches não precisam viver na imagem final.
- Limpe no mesmo passo. Baixar e remover em camadas diferentes não reduz o tamanho — a camada anterior fica no histórico. Faça a limpeza no mesmo
RUN.
Há um ganho colateral que aparece na escala: imagem pequena significa provisionamento rápido. Em orquestrador que sobe e derruba réplicas conforme a carga, a diferença entre baixar 80 MB e 800 MB aparece direto no tempo de resposta a um pico de tráfego.
Escolhendo a base sem se arrepender
Cada família de base resolve um problema e cobra um preço:
| Base | Vantagem | Custo a considerar |
|---|---|---|
| Completa (padrão da distribuição) | Tudo funciona, fácil de depurar | Grande, muitos pacotes a manter |
slim | Bom equilíbrio, compatibilidade preservada | Falta ferramenta ocasional no build |
alpine | Muito pequena | Usa musl em vez de glibc; algumas dependências exigem compilação |
| Distroless / mínima sem shell | Superfície de ataque mínima | Sem shell para depurar dentro do container |
O caso do Alpine merece nota, porque é a escolha que mais gera surpresa. A biblioteca C diferente faz com que pacotes binários prontos de algumas linguagens não sirvam, obrigando a compilar na hora do build — e um build que era rápido passa a levar minutos. Para muitas stacks, a variante slim da distribuição padrão entrega quase o mesmo tamanho sem esse atrito.
Multi-stage build: o pulo do gato
O build multi-stage é a técnica que mais reduz imagem sem esforço. A ideia: use um estágio "pesado" para compilar/construir a aplicação, e copie apenas o resultado para um estágio final enxuto. Compiladores, dependências de build e arquivos intermediários ficam para trás.
O ganho é duplo: imagem final pequena e uma separação limpa entre o que é necessário para construir e o que é necessário para rodar. Uma aplicação que resultaria em uma imagem de centenas de MB pode terminar em dezenas.
Um desenho de estágios que funciona bem na prática:
- Estágio de dependências: instala bibliotecas a partir dos arquivos de manifesto, aproveitando cache entre builds.
- Estágio de build: compila, empacota ou gera os artefatos estáticos.
- Estágio de teste (opcional): roda a suíte sobre o artefato construído, falhando o build quando quebra.
- Estágio final: parte de uma base mínima e copia apenas o artefato e os arquivos de runtime.
Como bônus, dá para apontar o build para um estágio intermediário durante o desenvolvimento, tendo ferramentas de depuração à mão sem carregá-las para produção.
O arquivo que quase todo mundo esquece
Sem um .dockerignore bem escrito, o contexto de build inclui o diretório inteiro — histórico do repositório, dependências locais, artefatos de build anteriores, arquivos de configuração com credenciais. Isso deixa o build lento, invalida cache sem motivo e, no pior caso, coloca segredo dentro da imagem sem ninguém perceber.
Comece ignorando o controle de versão, as dependências instaladas localmente, diretórios de build, arquivos de ambiente e qualquer coisa que não seja necessária para construir. É o ajuste de cinco minutos com melhor retorno em todo o Dockerfile.
Camadas eficientes e cache
O Docker constrói em camadas e reaproveita cache. Ordene o Dockerfile do que muda menos para o que muda mais: copie e instale dependências antes de copiar o código-fonte. Assim, mudar uma linha de código não invalida o cache da instalação de dependências — e o build fica muito mais rápido.
No pipeline de CI, garanta que o cache seja realmente aproveitado entre execuções — seja reutilizando camadas do registry, seja com o mecanismo de cache do próprio construtor. Build de dez minutos que poderia levar um é atrito diário que desestimula entregas pequenas.
Como o processo se comporta dentro do container
Aqui está uma classe de problema que só aparece em produção. O processo principal do container roda como PID 1, e isso tem consequências:
- Sinais. Se o comando é executado através de um shell, o sinal de encerramento pode não chegar à aplicação, que então é morta à força depois do tempo de espera. O resultado é conexão cortada no meio e transação interrompida a cada deploy. Use a forma de execução direta, sem shell intermediário.
- Processos zumbis. PID 1 tem a obrigação de recolher processos filhos encerrados. Aplicações que geram subprocessos precisam de um init mínimo para isso — o Docker oferece essa opção nativamente.
- Encerramento gracioso. A aplicação deve tratar o sinal de término: parar de aceitar novas requisições, terminar o que está em andamento e sair. Isso é o que permite deploy sem erro visível ao usuário.
- Logs no stdout. Container não é lugar para escrever arquivo de log. Envie para a saída padrão e deixe a plataforma coletar, rotacionar e centralizar.
- Healthcheck que reflete a realidade. Um endpoint que responde apenas "estou de pé" não ajuda; verifique as dependências essenciais, sem transformar o healthcheck em teste caro que derruba o serviço sob carga.
Segurança: o item que ninguém prioriza até o incidente
Container não é máquina virtual, e tratá-lo como se fosse cria riscos:
- Não rode como root. Defina um usuário sem privilégio na imagem. Container comprometido rodando como root é um problema muito maior.
- Fixe versões.
latesté imprevisível — a imagem que subiu hoje pode não ser a de amanhã. Fixe tags e digests. - Escaneie as imagens. Ferramentas de scan encontram vulnerabilidades conhecidas nas dependências antes de irem para produção.
- Não coloque segredo na imagem. Senha, chave e token vão via variável de ambiente ou gerenciador de segredos — nunca dentro do Dockerfile ou da imagem.
- Minimize a superfície. Menos pacotes na imagem, menos vulnerabilidades para gerenciar.
Na hora de executar, quatro ajustes elevam bastante o patamar e custam pouco:
- Sistema de arquivos somente leitura, com volumes explícitos apenas onde a aplicação precisa escrever.
- Remoção de capacidades que a aplicação não usa, mantendo o mínimo necessário.
- Bloqueio de escalada de privilégio, impedindo que o processo ganhe permissões durante a execução.
- Limites de CPU e memória definidos. Sem limite, um container com vazamento de memória derruba o host inteiro, não apenas a si mesmo.
E uma regra sem exceção: não monte o socket do Docker dentro de um container que não seja de infraestrutura confiável. Acesso ao socket equivale a acesso root no host — é a porta dos fundos mais comum em ambientes que pareciam seguros.
Segredos: onde eles devem viver
Segredo em variável de ambiente é o padrão mínimo aceitável, e ainda vaza com facilidade — aparece em log de inspeção, em despejo de erro, no histórico de shell. O caminho melhor é injetar em tempo de execução a partir de um gerenciador de segredos, com rotação possível e registro de quem acessou. No build, use o mecanismo próprio de segredos do construtor, que não persiste o valor em nenhuma camada da imagem.
Registry e política de tag
Imagem só vale em produção se você souber exatamente o que está rodando. Duas práticas resolvem:
- Tag imutável por versão, gerada no pipeline — um identificador de build ou o hash do commit.
latestserve para conveniência local, nunca para produção. - Referência por digest no manifesto de deploy quando a garantia precisa ser absoluta.
Some a isso a limpeza periódica do registry, ou o custo de armazenamento cresce em silêncio, e a política de retenção passa a ser decidida pelo disco cheio.
Compose para orquestração local
Para desenvolvimento e ambientes simples, o Docker Compose organiza múltiplos containers (aplicação, banco, cache) em um arquivo declarativo. É a ponte natural antes de decidir se a escala justifica um orquestrador completo como Kubernetes.
Mantenha o Compose de desenvolvimento próximo da realidade de produção nas versões de dependência, mas separado nos ajustes de conveniência. Ambiente local que difere muito do destino final é fábrica de "na minha máquina funciona".
Checklist de imagem pronta para produção
.dockerignorecobrindo tudo que não é necessário ao build.- Build multi-stage, com imagem final sem ferramentas de compilação.
- Base fixada por tag específica, revisada periodicamente.
- Usuário sem privilégio definido na imagem.
- Nenhum segredo em camada, argumento de build ou código.
- Encerramento gracioso testado, com sinal chegando à aplicação.
- Healthcheck implementado e verificado.
- Logs na saída padrão.
- Limites de CPU e memória definidos na execução.
- Escaneamento de vulnerabilidade no pipeline, com política clara para o que bloqueia a entrega.
Onde a Solvefy/Cloud entra
Nossa consultoria DevOps trabalha conteinerização do jeito que aguenta produção: builds multi-stage, imagens enxutas, redes, volumes, Compose, integração com CI/CD e segurança de imagens — com documentação e transferência de conhecimento. Cobrada por hora, com escopo definido.
Quer revisar seus containers? Diagnóstico gratuito, sem compromisso, em solvefy.cloud.
Solvefy/Cloud — Infraestrutura que cresce com você.