DevGuideTK
← Trilha Backend

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.

O problema: funciona na minha máquina

A aplicação roda no computador do desenvolvedor com Node 24, uma biblioteca de imagens instalada no sistema e uma variável de ambiente que ninguém lembra de ter configurado. No servidor, o Node é outra versão, a biblioteca falta e a variável não existe. O deploy falha, ou pior, funciona com um comportamento diferente.

Antes dos containers, as soluções eram documentar a instalação passo a passo, manter servidores configurados à mão ou criar uma máquina virtual inteira para cada aplicação. As três são lentas, frágeis ou caras.

Um container resolve empacotando a aplicação junto com o ambiente de que ela precisa: o sistema de arquivos com as bibliotecas, o interpretador, as dependências e a configuração de execução. O pacote roda igual em qualquer máquina que tenha um mecanismo de containers.

Container não é máquina virtual

Máquina virtualContainer
O que isolaUm computador inteiro, com o próprio sistema operacionalUm processo, com sistema de arquivos e rede próprios
Núcleo do sistema (kernel)Um para cada máquina virtualCompartilhado com a máquina hospedeira
Tamanho típicoGigabytesDezenas a centenas de megabytes
Tempo para iniciarDezenas de segundos a minutosFrações de segundo a poucos segundos
IsolamentoForte: um núcleo por máquinaBom, porém menor: todos dividem o mesmo núcleo

Um container é, por dentro, um processo comum da máquina hospedeira, com recursos do núcleo do Linux que o fazem enxergar só o próprio mundo:

  • namespaces dão ao processo a sua própria visão de processos, rede, sistema de arquivos e usuários;
  • cgroups limitam quanta CPU e memória ele pode usar.

Por isso um container inicia tão rápido: não há sistema operacional para ligar, só um processo para iniciar. E por isso o isolamento é menor que o de uma máquina virtual: uma falha no núcleo afeta todos os containers da máquina.

Imagem e container

Dois termos que se confundem:

  • Imagem: o pacote congelado, que não muda. Contém o sistema de arquivos e a instrução de qual processo iniciar. É o que se constrói, versiona e publica.
  • Container: uma imagem em execução. Da mesma imagem saem quantos containers forem necessários, todos iguais.

A analogia mais próxima é a de uma classe e seus objetos, ou de um instalador e o programa instalado.

docker build -t loja/api:1.4.2 .     # constrói a imagem a partir do Dockerfile
docker run -p 3000:3000 loja/api:1.4.2   # inicia um container a partir dela

O Dockerfile e as camadas

A imagem é descrita num Dockerfile, uma receita com uma instrução por linha:

# Parte de uma imagem com Node já instalado
FROM node:24-alpine
WORKDIR /app

# Instala as dependências
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Copia o código
COPY . .

# O processo que o container inicia
CMD ["node", "server.js"]

Cada instrução gera uma camada, e a imagem é a pilha dessas camadas. As camadas são guardadas em cache: se uma instrução e tudo o que vem antes dela não mudaram, a reconstrução reaproveita a camada pronta.

Essa regra tem uma consequência prática enorme. O exemplo acima copia primeiro só os arquivos de dependências e instala, e só depois copia o código. Uma mudança no código invalida apenas as últimas camadas, e as dependências vêm do cache.

Medimos a diferença com uma aplicação Node pequena. Depois de mudar uma linha de código, a reconstrução levou 5,4 segundos com um Dockerfile que copia tudo antes de instalar, e 0,4 segundo com as dependências numa camada separada. Em projetos reais, com centenas de dependências, a diferença passa de minutos.

Por que o tamanho e o usuário importam

No mesmo teste, a imagem montada do jeito mais simples (imagem base completa, dependências de desenvolvimento incluídas) ficou com 1,73 GB. A versão com uma imagem base enxuta e só as dependências de produção ficou com 236 MB. Uma aplicação Go compilada como binário estático, sobre uma imagem mínima, ficou com 14 MB.

O tamanho afetaPor quê
Tempo de deployCada máquina que vai rodar o container baixa a imagem
Escala automáticaUma cópia nova só começa depois que a imagem chega
SegurançaCada pacote a mais é uma possível vulnerabilidade, e um shell dentro da imagem ajuda quem invadir
CustoArmazenamento no registro e tráfego de rede

E, por padrão, o processo dentro do container roda como root. Se alguém explorar uma falha da aplicação, ganha o máximo de poder dentro do container, e fica mais perto de escapar para a máquina hospedeira. A versão simples do teste rodava como root; a cuidadosa, como um usuário sem privilégios.

Por que isso importa quando a IA escreve o código

Pedir "crie um Dockerfile para este projeto" gera um arquivo que funciona. Ele quase sempre tem estes problemas:

  • COPY . . antes da instalação das dependências, refazendo tudo a cada mudança de código.
  • A imagem base completa, com compiladores e ferramentas que a produção não usa.
  • Dependências de desenvolvimento e arquivos de teste dentro da imagem final.
  • Nenhum .dockerignore, levando node_modules, .git e até arquivos .env com segredos para dentro da imagem.
  • O processo rodando como root.
  • CMD npm start na forma de texto, que impede o processo de receber o sinal de encerramento.

O próximo nível compara as escolhas de imagem base, de construção e de execução.

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.

  • Containers em cinco minutos

    Explique em no máximo cinco minutos de leitura o que é um container e o que o Docker faz, para alguém que programa mas sempre instalou as aplicações direto no servidor.
    
    Cubra só o essencial: o problema de "funciona na minha máquina", a diferença entre container e máquina virtual, a diferença entre imagem e container, e o que é um Dockerfile.
    
    Use uma analogia simples. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • O que um container é por dentro

    Quero entender o que um container é de verdade, por dentro do sistema operacional.
    
    Aja como um mentor de infraestrutura. Pergunte primeiro qual é a minha experiência com Linux.
    
    Conduza assim, uma etapa por vez:
    1. Mostre que um container é um processo comum da máquina hospedeira, com um comando que eu possa rodar para ver isso.
    2. Explique os namespaces: o que cada tipo isola (processos, rede, sistema de arquivos, usuários).
    3. Explique os cgroups e como eles limitam CPU e memória.
    4. Explique o sistema de arquivos em camadas de uma imagem.
    5. Compare com uma máquina virtual: o que é compartilhado, o que é isolado e as consequências para segurança.
    
    Faça uma pergunta de verificação em cada etapa e espere a minha resposta antes de continuar.
  • Camadas e cache na construção

    Quero entender como as camadas de uma imagem Docker e o cache de construção funcionam.
    
    Explique:
    1. Como cada instrução do Dockerfile gera uma camada.
    2. A regra que decide quando uma camada vem do cache e quando é reconstruída.
    3. Por que copiar os arquivos de dependências e instalar antes de copiar o código faz tanta diferença.
    4. O papel do .dockerignore no cache e no conteúdo da imagem.
    5. Por que apagar um arquivo numa camada posterior não o remove da imagem, e o que isso significa para segredos.
    
    Depois me mostre quatro Dockerfiles, um por vez, e pergunte o que acontece com o cache quando eu mudo uma linha de código em cada um. Corrija as minhas respostas.
  • O processo principal e os sinais

    Quero entender por que a forma de escrever o CMD no Dockerfile afeta o encerramento do container.
    
    Explique:
    1. O que é o PID 1 dentro de um container e por que ele é especial.
    2. A diferença entre a forma de texto e a forma de lista no CMD e no ENTRYPOINT, e qual processo vira o PID 1 em cada uma.
    3. O que acontece quando o docker stop envia o SIGTERM para um shell que não o repassa.
    4. Como escrever um script de entrada que não quebre isso, usando exec.
    5. Quando um init mínimo, como o tini, é necessário.
    
    Depois me mostre cinco variações de CMD e ENTRYPOINT e pergunte quanto tempo o docker stop vai levar em cada uma. Corrija as minhas respostas.