Docker para Desenvolvedores: Contêineres, Imagens e Compose

· 12 min de leitura

Índice

Entendendo o Docker: Por Que Importa

O Docker mudou fundamentalmente como os desenvolvedores constroem, distribuem e executam aplicações. Antes do Docker, configurar ambientes de desenvolvimento era um pesadelo de conflitos de dependências, incompatibilidades de versão e o infame problema "funciona na minha máquina".

O Docker resolve isso empacotando sua aplicação com tudo o que ela precisa para executar—código, runtime, ferramentas do sistema, bibliotecas e configurações—em uma unidade padronizada chamada contêiner. Este contêiner executa de forma idêntica no seu laptop, na máquina do seu colega e nos servidores de produção.

Os benefícios são imediatos e tangíveis:

Para desenvolvedores, Docker significa que você pode iniciar uma pilha completa de aplicação—servidor web, banco de dados, cache, fila de mensagens—com um único comando. Não mais gastar horas instalando PostgreSQL ou depurando problemas de configuração do Redis.

Conceitos Fundamentais Explicados

Entender os conceitos fundamentais do Docker é essencial antes de mergulhar no uso prático. Estes blocos de construção trabalham juntos para criar o ecossistema Docker.

Conceito O Que É Analogia Características Principais
Imagem Template somente leitura com app + dependências Uma definição de classe em POO Imutável, em camadas, compartilhável
Contêiner Instância em execução de uma imagem Um objeto (instância de classe) Isolado, efêmero, sem estado
Dockerfile Instruções para construir uma imagem Uma receita ou blueprint Arquivo de texto, versionado
Volume Armazenamento persistente fora do contêiner Um disco rígido externo Sobrevive à exclusão do contêiner
Rede Comunicação entre contêineres Uma rede local (LAN) Isolada, configurável, segura
Registro Armazenamento para imagens (Docker Hub) npm/PyPI para contêineres Público ou privado, versionado

Imagens vs Contêineres: A Distinção Crítica

É aqui que muitos iniciantes ficam confusos. Uma imagem é um snapshot estático—pense nela como um template congelado. Um contêiner é o que você obtém quando executa essa imagem—é um processo vivo, em execução.

Você pode criar contêineres ilimitados a partir de uma única imagem, assim como você pode criar múltiplos objetos a partir de uma classe. Cada contêiner é isolado dos outros, mesmo que todos sejam baseados na mesma imagem.

Dica profissional: Imagens são construídas em camadas. Cada instrução no seu Dockerfile cria uma nova camada. O Docker armazena essas camadas em cache, então reconstruir uma imagem apenas reconstrói as camadas que mudaram. Isso torna as construções incrivelmente rápidas.

Volumes: Resolvendo o Problema de Persistência

Contêineres são efêmeros por design—quando você exclui um contêiner, tudo dentro dele desaparece. Isso é ótimo para aplicações sem estado, mas problemático para bancos de dados ou quaisquer dados que você precise manter.

Volumes resolvem isso armazenando dados fora do sistema de arquivos do contêiner. Os dados persistem mesmo quando contêineres são excluídos e recriados. Existem três tipos de montagens:

Anatomia e Estrutura do Dockerfile

Um Dockerfile é um documento de texto contendo instruções para construir uma imagem Docker. Cada instrução cria uma camada na imagem, e o Docker armazena essas camadas em cache para eficiência.

Aqui está um detalhamento das instruções mais comuns do Dockerfile:

Instrução Propósito Exemplo Melhor Prática
FROM Imagem base para construir FROM node:22-alpine Use versões específicas, prefira alpine
WORKDIR Define diretório de trabalho WORKDIR /app Use caminhos absolutos
COPY Copia arquivos do host para a imagem COPY package.json . Copie dependências primeiro para cache
RUN Executa comandos durante a construção RUN npm install Encadeie comandos com && para reduzir camadas
EXPOSE Documenta em qual porta o app escuta EXPOSE 3000 Apenas documentação, não publica porta
ENV Define variáveis de ambiente ENV NODE_ENV=production Use para configuração
USER Define usuário para comandos subsequentes USER node Nunca execute como root em produção
CMD Comando padrão quando o contêiner inicia CMD ["node", "server.js"] Use formato de array JSON

Entendendo o Cache de Camadas

O Docker constrói imagens camada por camada, de cima para baixo. Cada instrução cria uma nova camada. Se uma camada não mudou, o Docker reutiliza a versão em cache em vez de reconstruí-la.

É por isso que você deve estruturar seu Dockerfile para colocar instruções que raramente mudam no topo e as que mudam frequentemente na parte inferior. Por exemplo, suas dependências mudam menos frequentemente que seu código-fonte, então instale dependências antes de copiar arquivos de origem.

# Ruim: Copia tudo primeiro, depois instala
COPY . .
RUN npm install

# Bom: Instala dependências primeiro, aproveita cache
COPY package*.json ./
RUN npm install
COPY . .

Melhores Práticas de Dockerfile

Escrever Dockerfiles eficientes é uma arte. Aqui está um exemplo pronto para produção que demonstra múltiplas melhores práticas:

# Build multi-estágio: estágio builder
FROM node:22-alpine AS builder
WORKDIR /app

# Copie arquivos de dependência primeiro para melhor cache
COPY package*.json ./
RUN npm ci --production

# Copie código-fonte e construa
COPY . .
RUN npm run build

# Build multi-estágio: estágio de produção
FROM node:22-alpine
WORKDIR /app

# Copie apenas o necessário do builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

# Segurança: execute como usuário não-root
USER node

# Documente a porta (não a publica de fato)
EXPOSE 3000

# Health check para orquestração de contêineres
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

# Inicie a aplicação
CMD ["node", "dist/server.js"]

Builds Multi-Estágio: Redução Dramática de Tamanho

Builds multi-estágio permitem que você use múltiplas instruções FROM em um Dockerfile. Cada FROM inicia um novo estágio, e você pode copiar artefatos de estágios anteriores.

A mágica acontece porque apenas o estágio final se torna sua imagem. Ferramentas de build, compiladores e arquivos intermediários ficam em estágios anteriores e nunca chegam à produção. Isso pode reduzir tamanhos de imagem em 10x ou mais.

Dica rápida: Nomeie seus estágios com AS builder para que você possa referenciá-los depois com COPY --from=builder. Isso torna seu Dockerfile mais legível e mantível.

Imagens Alpine: Pequenas mas Poderosas

Alpine Linux é uma distribuição Linux mínima que tem apenas 5MB de tamanho. Compare isso com imagens baseadas em Ubuntu com mais de 900MB. Para a maioria das aplicações, Alpine fornece tudo o que você precisa.

O trade-off é que Alpine usa musl libc em vez de glibc, o que pode ocasionalmente causar problemas de compatibilidade com binários pré-compilados. Para 95% dos casos de uso, Alpine funciona perfeitamente e reduz drasticamente o tamanho da imagem, tempo de download e superfície de ataque.

O Arquivo .dockerignore

Assim como .gitignore, um arquivo .dockerignore diz ao Docker quais arquivos excluir ao construir imagens. Isso acelera builds e reduz o tamanho da imagem.

# exemplo de .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
dist
coverage
.vscode
.idea
*.log
.DS_Store

Melhores Práticas de Segurança

Segurança deve ser incorporada em seus Dockerfiles desde o início:

Precisa de ajuda para gerar Dockerfiles otimizados? Experimente nossa ferramenta Gerador de Dockerfile.

Comandos Essenciais do Docker

Dominar estes comandos cobrirá 90% do seu uso diário do Docker. Cada comando inclui exemplos práticos e flags comuns.

Construindo e Executando

# Construa uma imagem do Dockerfile no diretório atual
docker build -t myapp:1.0 .

# Construa com argumentos de build
docker build --build-arg NODE_ENV=production -t myapp:1.0 .

# Construa sem cache (forçar reconstrução)
docker build --no-cache -t myapp:1.0 .

# Execute um contêiner em modo desanexado
docker run -d -p 3000:3000 --name myapp myapp:1.0

# Execute com variáveis de ambiente
docker run -d -p 3000:3000 -e NODE_ENV=production --name myapp myapp:1.0

# Execute com montagem de volume
docker run -d -p 3000:3000 -v $(pwd)/data:/app/data --name myapp myapp:1.0

# Execute interativamente com acesso ao shell
docker run -it --rm myapp:1.0 sh

Gerenciando Contêineres

# Liste contêineres em execução
docker ps

# Liste todos os contêineres (incluindo parados)
docker ps -a

# Visualize logs do contêiner
docker logs myapp

# Acompanhe logs em tempo real
docker logs -f myapp

# Visualize últimas 100 linhas de logs
docker logs --tail 100 myapp

# Execute comando em contêiner em execução
docker exec -it myapp sh

# Execute um comando único
docker exec myapp npm test

# Pare um contêiner graciosamente
docker stop myapp

# Mate um contêiner imediatamente
docker kill myapp

# Remova um contêiner parado
docker rm myapp

# Pare e remova em um comando
docker stop myapp && docker rm myapp

# Remova todos os contêineres parados
docker container prune

Trabalhando com Imagens

# Liste todas as imagens
docker images

# Liste imagens com formato personalizado
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# Remova uma imagem
docker rmi myapp:1.0

# Remova todas as imagens não utilizadas
docker image prune -a

# Marque uma imagem
docker tag myapp:1.0 myapp:latest

# Envie para registro
docker push myregistry.com/myapp:1.0

# Puxe do registro
docker pull myregistry.com/myapp:1.0

# Salve imagem em arquivo tar
docker save myapp:1.0 > myapp.tar

# Carregue imagem de arquivo tar
docker load < myapp.tar

Gerenciamento do Sistema

# Visualize uso de disco
docker system df

# Remova todos os dados não utilizados (contêineres, redes, imagens, cache)
docker system prune -a

# Visualize estatísticas de contêiner em tempo real
docker stats

# Inspecione detalhes do contêiner (saída JSON)
docker inspect myapp

# Visualize processos do contêiner
docker top myapp

Dica profissional: Adicione a flag --rm ao executar contêineres para testes. Isso remove automaticamente o contêiner quando ele para, mantendo seu sistema limpo: docker run --rm -it myapp:1.0 sh

Docker Compose para Aplicações Multi-Contêiner

Docker Compose é uma ferramenta para definir e executar aplicações multi-contêiner. Em vez de executar múltiplos comandos docker run, você define tudo em um único arquivo YAML.

Isso é essencial para aplicações modernas que tipicamente consistem em múltiplos serviços: um servidor web, banco de dados, cache, fila de mensagens e mais.

Exemplo Completo de Docker Compose

# docker-compose.yml
version: '3.8'

services:
  # W