Containers e Docker
O que é um container, como ele difere de uma máquina virtual e como se constrói uma imagem pequena, rápida e segura.
As decisões de uma imagem são cinco: qual imagem base usar, como construir sem levar ferramentas para produção, como aproveitar o cache, como rodar com segurança e como versionar.
A imagem base
| Base | Exemplo | Tamanho | Vantagens | Cuidados |
|---|---|---|---|---|
| Completa | node:24, python:3.13 | Centenas de MB a mais de 1 GB | Tem tudo: compiladores, ferramentas, shell | Grande e com muito mais pacotes do que a produção usa |
| Slim (Debian reduzido) | node:24-slim, python:3.13-slim | Dezenas a poucas centenas de MB | Compatível com quase tudo, menor | Pode faltar alguma biblioteca de sistema |
| Alpine | node:24-alpine | A menor entre as que têm shell | Muito pequena | Usa outra biblioteca C (musl), que quebra alguns pacotes com código nativo |
| Distroless | gcr.io/distroless/static-debian12, base-debian12 | Mínima | Só o necessário para rodar; sem shell e sem gerenciador de pacotes | Depurar exige outras técnicas, já que não dá para entrar no container |
| Scratch | scratch | Vazia | Só o seu binário | Só para binários estáticos, como Go sem CGO |
Para a maioria das aplicações, slim é o ponto de partida mais seguro. Alpine quando o tamanho importa e as dependências funcionam com musl. Distroless quando a segurança pesa mais que a conveniência de depurar.
Em Go, Rust e outras linguagens que geram binários estáticos, a imagem final pode ser distroless ou scratch. No teste, uma API Go ficou com 14 MB numa base distroless, sem shell dentro: uma tentativa de executar sh no container falhou porque ele não existe.
Construção em estágios
A imagem final não precisa das ferramentas que construíram a aplicação. A construção em estágios (multi-stage build) usa uma imagem para compilar e outra, enxuta, para rodar:
# Estágio 1: tem compilador e ferramentas
FROM golang:1.24-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /api .
# Estágio 2: só o binário
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /api /api
ENTRYPOINT ["/api"]Só o último estágio vira a imagem publicada. O compilador, o código-fonte e os arquivos intermediários ficam para trás.
| Linguagem | O que fica no estágio de construção | O que vai para a imagem final |
|---|---|---|
| Node/TypeScript | Dependências de desenvolvimento, compilador TypeScript | O JavaScript gerado e as dependências de produção |
| Python | Compiladores para pacotes com código nativo | O ambiente virtual já instalado |
| Go | O compilador Go | Um binário |
| Java | Maven ou Gradle, o JDK | O JAR e um JRE |
| C# | O SDK do .NET | A aplicação publicada e o runtime do ASP.NET |
Aproveitar o cache
| Prática | Efeito |
|---|---|
| Copiar os arquivos de dependências e instalar antes de copiar o código | Mudanças no código não refazem a instalação |
Usar o arquivo de trava (package-lock.json, poetry.lock, go.sum) e o comando de instalação exata (npm ci) | A mesma versão de cada dependência em toda construção |
Um .dockerignore bem feito | Arquivos irrelevantes não invalidam o cache nem entram na imagem |
| Instruções que mudam pouco no topo, as que mudam muito no fim | Mais camadas reaproveitadas |
Cache de construção montado (RUN --mount=type=cache) | O cache do gerenciador de pacotes sobrevive entre construções |
O .dockerignore merece atenção especial. Sem ele, COPY . . leva para dentro da imagem a pasta .git, o node_modules local, arquivos de teste e, no pior caso, um .env com senhas.
.git
node_modules
.env*
coverage
tests
*.log
Dockerfile*Executar com segurança
| Prática | Por quê |
|---|---|
Usuário sem privilégios (USER) | Uma falha explorada não dá poder de root dentro do container |
Forma de lista no CMD | O processo recebe o SIGTERM direto, sem um shell no meio |
| Sistema de arquivos somente leitura, quando possível | Impede que um invasor altere ou acrescente arquivos |
| Limites de CPU e memória | Um container com vazamento de memória não derruba os vizinhos |
| Nenhum segredo na imagem | Qualquer um com acesso à imagem lê o que estiver nas camadas, mesmo se apagado numa camada posterior |
| Varredura de vulnerabilidades | Ferramentas como Trivy e Grype apontam pacotes com falhas conhecidas |
O processo principal e o sinal de encerramento
O primeiro processo do container (PID 1) é quem recebe o SIGTERM quando o container é parado. Se esse processo for um shell, o sinal fica nele e não chega à aplicação.
No teste, com CMD npm start na forma de texto, o PID 1 era /bin/sh -c npm start. O docker stop esperou os 10 segundos do limite padrão e matou o container à força, sem que a aplicação recebesse o sinal. Com CMD ["node", "server.js"], o PID 1 era o próprio Node, que recebeu o sinal, encerrou e o docker stop levou 0,1 segundo.
| Forma | Exemplo | PID 1 |
|---|---|---|
| Texto (shell form) | CMD npm start | Um shell, que não repassa o sinal |
| Lista (exec form) | CMD ["node", "server.js"] | A própria aplicação |
| Script de entrada | ENTRYPOINT ["./entrada.sh"] | O script, a menos que ele termine com exec |
Se um script de entrada for necessário, ele deve terminar com exec node server.js, que troca o shell pela aplicação. Aplicações que criam processos filhos se beneficiam de um init mínimo como tini ou da opção --init do Docker.
Versionar as imagens
| Prática | Por quê |
|---|---|
Não usar latest em produção | Ninguém sabe qual versão está rodando, e um rollback não tem para onde voltar |
Etiquetar com a versão ou o commit: loja/api:1.4.2, loja/api:3f9c2e1 | Cada deploy aponta para uma imagem exata e imutável |
Fixar a versão da imagem base: node:24.11-alpine | Uma nova versão da base não muda a imagem sem ninguém saber |
| Reconstruir periodicamente | Recebe as correções de segurança da imagem base |
Fixar a base e reconstruir periodicamente parecem contraditórios. Não são: fixar garante que cada construção seja reproduzível, e a atualização da base vira uma mudança explícita, revisada e testada.
Resumo da decisão
- Base: slim por padrão; alpine ou distroless quando tamanho e segurança pesam mais.
- Construção em estágios: ferramentas no estágio de construção, só o necessário na imagem final.
- Cache: dependências antes do código, arquivo de trava,
.dockerignore. - Execução: usuário sem privilégios,
CMDem forma de lista, sem segredos, com limites. - Versões: etiquetas imutáveis por versão ou commit, base fixada e atualizada de propósito.
O próximo nível mostra Dockerfiles completos para Node e Go, a aplicação preparada para container nas cinco linguagens e um ambiente local com Docker Compose.
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Escolher a imagem base
Quero escolher a imagem base certa para a minha aplicação. Pergunte qual linguagem e versão eu uso, se há dependências com código nativo e quanta importância eu dou a tamanho, segurança e facilidade de depurar. Compare para o meu caso as imagens completa, slim, alpine, distroless e scratch: tamanho aproximado, compatibilidade, superfície de ataque e o que muda na hora de investigar um problema em produção. Explique em detalhe os riscos da alpine com bibliotecas nativas, por causa da musl, e como depurar um container distroless sem shell. Termine com uma recomendação e com a forma de fixar a versão da base e mantê-la atualizada.
Revisar um Dockerfile
Vou colar abaixo o Dockerfile e o .dockerignore da minha aplicação. Revise com foco em tamanho, cache, segurança e comportamento em execução. Verifique, nesta ordem, e para cada item diga se está correto, incorreto ou ausente, citando a linha: 1. A imagem base é adequada e tem versão fixada? 2. Há construção em estágios, sem ferramentas de construção na imagem final? 3. As dependências são instaladas antes da cópia do código, usando o arquivo de trava? 4. O .dockerignore exclui .git, dependências locais, testes e arquivos .env? 5. Há algum segredo em qualquer camada? 6. O processo roda com usuário sem privilégios? 7. O CMD ou ENTRYPOINT está em forma de lista, ou o script termina com exec? 8. A aplicação escuta em 0.0.0.0? Para cada problema, explique a consequência e mostre a correção. No final, mostre o Dockerfile completo corrigido e estime a diferença de tamanho. Dockerfile e .dockerignore: [cole aqui]
Segurança de containers
Quero entender os riscos de segurança de containers e as práticas que os reduzem. Explique: 1. Por que rodar como root dentro do container é perigoso, mesmo isolado. 2. Como segredos acabam vazando em imagens, e as formas corretas de entregá-los ao container. 3. O que é varredura de vulnerabilidades, quais ferramentas existem e como interpretar o resultado. 4. Sistema de arquivos somente leitura, remoção de capacidades do Linux e limites de recursos. 5. Por que o isolamento de um container é menor que o de uma máquina virtual, e quando isso importa. Pergunte onde os meus containers rodam e termine com uma lista priorizada do que aplicar primeiro.