O contêiner de navio
Antes do contêiner padronizado, cada carga era um problema: sacas, barris, caixotes — cada navio estivava de um jeito. O contêiner inverteu a lógica: a carga se adapta à caixa, e navio, guindaste e caminhão só precisam saber mover a caixa. Docker fez isso com software: o servidor não sabe se dentro roda Node, Python ou Java — só sabe rodar containers.
O problema: o ambiente que não viaja com o código
O código vai para produção, mas o ambiente fica para trás: a versão do runtime, as bibliotecas do sistema, as variáveis, o fuso do servidor. Cada máquina acumula diferenças invisíveis — e o bug que só acontece lá é quase sempre uma dessas diferenças.
VMs resolvem o isolamento, mas carregam um SO inteiro por app: gigabytes, minutos de boot. Container isola com namespaces e cgroups do próprio kernel — megabytes, milissegundos — porque não virtualiza hardware, só separa processos.
A imagem é feita de camadas: cada instrução do Dockerfile gera uma, e camadas são cacheadas e compartilhadas — dez serviços sobre a mesma base de Node compartilham essa base no disco e no registry. É por isso que a ordem do Dockerfile importa: dependências (que mudam pouco) antes do código (que muda sempre), para o cache aproveitar.
Container é descartável por design: o filesystem dele morre com ele. Estado que importa (banco, uploads) vive fora — em volumes ou serviços gerenciados. Essa é a inversão mental que dói no começo e sustenta todo o resto: você para de tratar servidor como lugar e passa a tratar como processo.
Imagem (imutável, no registry)
camada base: SO mínimo
camada: dependências
camada: seu código
Container (processo em execução)
filesystem descartável
env + rede próprias
Da receita ao processo
Camada 1/3Dockerfile
use ← →Camada 1
Dockerfile
a receita, versionada junto do código
Declara base, dependências, build e comando de início. Multi-stage separa o estágio de build (compiladores, devDependencies) da imagem final (só o necessário para rodar) — a diferença entre 1,2 GB e 150 MB.
No código
FROM node:22 AS build
...
FROM node:22-slim # imagem final magraSe violar: imagem única com toolchain de build inteira = imagem gorda, deploy lento e superfície de ataque cheia de ferramentas que produção nunca usa.
Código
// O container não pede um framework — pede que seu app se comporte:
// config por ambiente, log em stdout e morte digna. O resto é Dockerfile.
import http from "node:http";
// 1. Config vem do ambiente — a MESMA imagem roda em dev, staging e prod
const porta = Number(process.env.PORT ?? 3000);
const urlBanco = process.env.DATABASE_URL; // nunca hardcoded na imagem
const servidor = http.createServer((req, res) => {
// 2. Log em stdout: o runtime coleta; o app não gerencia arquivo de log
console.log(JSON.stringify({ metodo: req.method, rota: req.url }));
res.writeHead(200).end("ok");
});
servidor.listen(porta, () => console.log(`ouvindo em :${porta}`));
// 3. Morte digna: o orquestrador manda SIGTERM antes de matar.
// Quem ignora, derruba requests no meio a cada deploy.
process.on("SIGTERM", () => {
console.log("SIGTERM: parando de aceitar conexões...");
servidor.close(() => process.exit(0)); // termina as requests em voo e sai
});
/* O Dockerfile correspondente (multi-stage: build pesado, imagem final magra):
FROM node:22 AS build
COPY . . && RUN npm ci && npm run build
FROM node:22-slim
COPY --from=build /app/dist ./dist
USER node # nunca root
CMD ["node", "dist/main.js"]
*/Casos de uso
Onboarding que era uma semana, virou um comando
Cenário
O sistema tem API, worker, Postgres, Redis e um gateway — e o guia de setup do novo dev tinha 40 passos desatualizados. Cada notebook era um ambiente diferente, com bugs próprios.
Como o padrão entra
Um docker-compose.yml declara os cinco serviços com versões pinadas. O novo dev roda 'docker compose up' e tem o sistema inteiro, idêntico ao dos colegas, em minutos — e o guia de 40 passos vira um arquivo versionado que o CI também usa.
Trade-off: O time precisa de fluência mínima em Docker para depurar (logs, exec, volumes), e em máquinas modestas os cinco containers pesam mais que os serviços nativos.
A imagem testada é a imagem implantada
Cenário
O deploy antigo era 'git pull + npm install' no servidor: o install de produção às vezes resolvia versões diferentes das testadas, e o rollback era um exercício de arqueologia.
Como o padrão entra
O CI builda a imagem uma vez, roda os testes contra ela e a publica com tag imutável. Produção só troca a tag em execução; rollback é apontar para a tag anterior — o artefato antigo continua no registry, intacto.
Trade-off: Surge uma peça nova para operar (o registry) e uma disciplina nova: limpar imagens velhas, escanear vulnerabilidades da base, renovar a base quando o SO ganha CVE.
Armadilhas
Estado dentro do container
SQLite no filesystem do container, uploads em ./uploads, sessão em memória: tudo isso morre no próximo deploy. Container é processo, não lugar — estado que importa vive em volume, banco ou storage externo, decidido antes do primeiro restart doer.
Rodar como root
O padrão das imagens é root, e um escape de container com root dentro é um problema de verdade. USER node (ou equivalente) no final do Dockerfile custa uma linha e corta a classe inteira de risco.
:latest em produção
Deploy com :latest é deploy de conteúdo desconhecido: o que sobe hoje não é o que subiu ontem, e rollback vira adivinhação. Tags imutáveis (versão ou SHA do commit) tornam cada deploy nomeável, auditável e reversível.
Ignorar o SIGTERM
No deploy, o orquestrador manda SIGTERM e espera; quem não trata é morto com as requests em voo — erros 502 a cada release. Fechar o servidor com graça são cinco linhas, e é a diferença entre deploy invisível e deploy que derruba usuários.
Quando usar × quando evitar
Use quando
3- Serviços de rede em produção — o caso padrão hoje.
- Ambiente de desenvolvimento e CI reprodutíveis.
- Sistemas com serviços em linguagens e runtimes diferentes.
Evite quando
3- Script pontual numa máquina só — o empacotamento não se paga.
- Apps que dependem de kernel, driver ou GUI específicos.
- Times sem ninguém para operar registry e ciclo de imagens — um PaaS esconde isso por você.
Testar o que ficou
4 armadilhas de Docker & containers viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
Dockerfile e Docker HubA receita da imagem e o registro de onde ela é baixada: você empacota app e dependências uma vez e roda igual em qualquer máquina.runners de CICada job do GitHub Actions ou GitLab CI roda dentro de um contêiner limpo, garantindo o mesmo ambiente a cada execução do pipeline.docker compose no dev localSubir banco, cache e app juntos com um comando dá a todo o time o mesmo ambiente, sem instalar serviço por serviço na máquina.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Mesma imagem do laptop ao cluster.
// docker build -t api:1 . && docker run api:1O que ele cobra
1 salto a mais entre a chamada e o efeito.
- Uma imagem a construir, versionar e manter, mais um registro de onde ela vive
- A camada de contêiner adiciona um degrau entre o processo e a máquina para depurar
Não vale se: o ambiente é único, estável e já reproduzível sem esforço — empacotar aí adiciona um passo de build sem ganho de portabilidade.
Onde isto aparece
Continue por aqui
- Kubernetes & podsVocê declara o estado desejado — '3 réplicas desta imagem, com este tanto de memória' — e o cluster trabalha continuamente para a realidade convergir. Pod é a menor unidade: um ou mais containers que vivem e morrem juntos.
- VPS — o servidor na sua mãoUma fatia virtual de um servidor físico, com root, IP público e nenhuma opinião: você instala, configura, protege e opera tudo. É o chão que explica os andares de cima — container, orquestrador e PaaS são abstrações disto aqui.