Docker explicado para quem nunca usou containers
Imagem vs container, Dockerfile básico e por que Docker resolve o clássico "na minha máquina funciona" — guia direto para devs iniciando.
O dev novo no time acabou de clonar o repositório. Seguiu o README passo a passo. Rodou. Não funcionou. Versão diferente do Node. Biblioteca de sistema faltando. Variável de ambiente que ninguém documentou. Esse ciclo tem nome: "na minha máquina funciona" — e é irritante de resolver sem Docker.
Docker não nasceu pra ser a ferramenta favorita de todo mundo em entrevista técnica. Nasceu pra resolver esse problema específico: fazer o ambiente de execução viajar junto com o código.
O problema real que o Docker resolve
Quando você instala uma aplicação no seu computador, ela depende de dezenas de coisas além do código: versão do runtime, bibliotecas do sistema operacional, variáveis de ambiente, arquivos de configuração. Tudo isso existe na sua máquina mas não necessariamente em produção — ou na máquina do colega de equipe.
Antes do Docker, a solução era documentar tudo e torcer. Ou usar máquinas virtuais completas — o que funciona, mas custa tempo de setup e usa memória de forma ineficiente. Uma VM carrega um sistema operacional inteiro só pra rodar uma aplicação.
O Docker resolve isso de forma diferente: em vez de virtualizar o hardware inteiro, ele isola processos usando recursos do próprio kernel Linux (namespaces e cgroups). O resultado é um container: um processo isolado que enxerga apenas o que você definiu, mas compartilha o kernel da máquina host. Mais leve que uma VM, mais previsível que instalar na máquina nua.
Se você ainda está entendendo o cenário maior — como deploy, automação e CI/CD se encaixam nisso — vale a leitura de O que é DevOps além das ferramentas, que cobre a cultura e os processos por trás das ferramentas.
Imagem vs container: a distinção que todo mundo confunde no começo
Essa é a dúvida que aparece sempre. A analogia mais direta:
- Imagem é a receita. É estática, imutável, descrita em arquivo.
- Container é o que nasce quando você executa a receita. É dinâmico, tem estado, tem processo rodando.
Uma imagem pode gerar dez containers simultâneos. Cada container é uma instância daquela receita, isolada das outras. Você pode rodar a mesma imagem no seu Mac, na máquina do colega com Linux, e no servidor em nuvem — o container vai se comportar igual nos três lugares, porque o ambiente está definido dentro da imagem.
A imagem em si não roda nada. Ela é só um template. O container é onde a vida acontece.
Dockerfile: onde você descreve o ambiente
O Dockerfile é um arquivo de texto com instruções sequenciais. Cada instrução vira uma camada da imagem. Camadas são cacheadas — se você mudar só o código da aplicação, o Docker não precisa reinstalar as dependências, só aplica a diff da última camada.
Um Dockerfile básico para uma aplicação Node:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
Linha por linha:
FROM node:20-alpine— parte de uma imagem base. O Alpine Linux é minimalista, boa escolha para produção.WORKDIR /app— define o diretório de trabalho dentro do container.COPY package*.json ./seguido deRUN npm ci— copia só o manifesto de dependências e instala. Isso aproveita o cache de camadas: se opackage.jsonnão mudou, o Docker pula esse passo no próximo build.COPY . .— copia o restante do código depois das dependências. Intencionalmente depois, pra não invalidar o cache denpm cia cada alteração de código.EXPOSE 3000— documentação de qual porta o processo vai usar. Não faz binding por si só.CMD— o comando que executa quando o container sobe.
Para construir a imagem a partir desse Dockerfile:
docker build -t minha-api:1.0 .
Para rodar um container a partir dela:
docker run -p 3000:3000 minha-api:1.0
O -p 3000:3000 mapeia a porta 3000 do seu host para a porta 3000 do container. Sem esse mapeamento, o processo está rodando mas inacessível de fora.
O que acontece "por baixo" quando você faz docker run
Quando você executa docker run, o Docker:
- Pega a imagem (baixa do registry se não tiver local)
- Cria uma camada de escrita em cima das camadas somente-leitura da imagem
- Configura a rede virtual do container
- Executa o processo definido no
CMD
O container vive enquanto o processo principal estiver ativo. Quando o processo encerra, o container para. Isso é diferente de uma VM, que fica "ligada" independentemente de ter processos ativos.
Containers são descartáveis por design. Se você precisa de estado persistente — banco de dados, uploads, logs — você usa volumes, que são mapeamentos de diretórios entre o host e o container.
Docker Compose: quando você tem mais de um container
Aplicação real geralmente tem pelo menos dois serviços: a aplicação e o banco de dados. O docker-compose.yml descreve todos eles e como se comunicam:
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/myapp
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Com isso, docker compose up sobe tudo junto. A API consegue acessar o banco pelo hostname db — que é o nome do serviço, resolvido automaticamente pela rede interna do Compose. Sem configurar nada de DNS.
O pgdata é um volume nomeado: os dados do Postgres persistem mesmo que você destrua e recrie o container do banco.
Por que isso resolve "na minha máquina funciona"
A resposta é direta: porque o ambiente deixa de ser implícito.
Antes do Docker, o ambiente estava documentado (quando estava) em READMEs, wikis, scripts de bootstrap que podiam estar desatualizados. Com o Dockerfile, o ambiente está no código. É versionado junto com a aplicação. Se funciona no CI, vai funcionar em produção — porque o CI e produção rodam a mesma imagem.
O container não conhece a versão do Node instalada no seu Mac. Ele usa a versão definida no FROM. A lib de sistema que faltava no servidor não falta mais — ela está na imagem. A variável de ambiente que ninguém documentou está no docker-compose.yml ou no guia de variáveis de ambiente do projeto, que agora é obrigatório pra subir localmente.
Isso não elimina todos os bugs de ambiente — diferenças de arquitetura (x86 vs ARM), secrets gerenciados fora do Compose, dependências externas como APIs de terceiros ainda podem causar divergências. Mas elimina a classe mais comum de problemas: versão errada de runtime e dependência de sistema faltando.
Para validar expressões de cron em containers agendados, o Gerador de Expressão Cron mostra as próximas execuções com fuso correto — útil quando o container roda em UTC mas o job foi escrito pensando em BRT.
Perguntas frequentes
Docker substitui máquina virtual?
Para a maioria dos casos de uso em desenvolvimento e deploy de aplicações, sim — e com vantagens. Containers sobem em segundos, consomem menos memória e são descartáveis. VMs ainda fazem sentido quando você precisa de isolamento completo de kernel (segurança de nível mais alto, diferentes SOs), ou quando a aplicação exige o sistema operacional completo por razões específicas. Para serviços web, APIs e bancos de dados, containers são a escolha padrão da indústria hoje.
Docker funciona igual no Windows, Mac e Linux?
No Linux, o Docker roda nativamente — containers compartilham o kernel da máquina. No Mac e Windows, o Docker Desktop roda uma VM Linux leve em segundo plano para prover o kernel necessário. Para desenvolvimento do dia a dia isso é transparente, mas pode haver pequenas diferenças de performance de I/O em alguns casos. Em produção, o ambiente é quase sempre Linux, então a imagem vai rodar sem camada extra.
O que é Docker Hub?
É o registry público oficial de imagens. Quando você escreve FROM node:20-alpine, o Docker baixa essa imagem do Docker Hub. É de lá que vêm as imagens base de linguagens, bancos de dados e ferramentas. Você também pode hospedar imagens privadas lá, ou usar registries alternativos como o GitHub Container Registry ou o Amazon ECR.
Devo usar Docker em desenvolvimento local?
Depende. Para equipes com mais de uma pessoa e projetos que têm backend + banco + outros serviços, Docker Compose economiza horas de onboarding. Para projetos pessoais ou ferramentas simples que você desenvolve sozinho, o overhead de aprender e manter o Dockerfile pode não valer no começo. A lógica inverte quando o projeto cresce ou quando outros devs precisam contribuir.
Dockerfile como documentação de ambiente
O maior benefício do Docker não é velocidade de deploy — é tornar o ambiente explícito e versionado. Um Dockerfile bem escrito responde a perguntas que antes ficavam na cabeça do dev que configurou o servidor original: qual versão do runtime? Quais dependências do sistema? Qual porta? Qual usuário executa o processo?
Isso tem valor direto em onboarding, em CI, em incidentes às 23h quando alguém precisa subir um container de emergência sem saber o histórico da máquina. O ambiente está no repositório, junto com o código que depende dele.
- 01 VPS, VPC e servidor dedicado: diferenças VPS, VPC e servidor dedicado aparecem juntos em toda comparação de hospedagem — mas significam coisas bem diferentes. Entenda onde o dinheiro vai e como decidir.
- 02 Banco relacional vs NoSQL: como escolher Quando usar PostgreSQL e quando NoSQL faz sentido de verdade — tradeoffs reais de consistência, schema e escala, sem papo de marketing.