Docker para Desenvolvedores: Contêineres, Imagens e Compose
· 12 min de leitura
Índice
- Entendendo o Docker: Por Que Importa
- Conceitos Fundamentais Explicados
- Anatomia e Estrutura do Dockerfile
- Melhores Práticas de Dockerfile
- Comandos Essenciais do Docker
- Docker Compose para Aplicações Multi-Contêiner
- Fluxo de Trabalho de Desenvolvimento e Dicas
- Depuração e Solução de Problemas
- Considerações de Segurança
- Otimização de Desempenho
- Perguntas Frequentes
- Artigos Relacionados
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:
- Consistência: Ambientes de desenvolvimento, homologação e produção são idênticos
- Isolamento: Cada aplicação executa em seu próprio contêiner sem conflitos
- Portabilidade: Contêineres executam em qualquer lugar onde o Docker esteja instalado
- Eficiência: Contêineres compartilham o kernel do SO hospedeiro, usando muito menos recursos que máquinas virtuais
- Velocidade: Contêineres iniciam em segundos, não minutos
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:
- Volumes: Gerenciados pelo Docker, armazenados na área de armazenamento do Docker (recomendado)
- Bind mounts: Mapeiam um diretório do host diretamente no contêiner (útil para desenvolvimento)
- tmpfs mounts: Armazenados apenas na memória do host, nunca escritos em disco (para dados sensíveis)
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:
- Nunca execute como root: Use
USER nodeou crie um usuário dedicado - Fixe versões específicas: Use
node:22.1.0-alpinenãonode:latest - Escaneie por vulnerabilidades: Use
docker scan myimagepara verificar CVEs conhecidos - Minimize pacotes instalados: Menos software significa menos vulnerabilidades
- Use imagens base oficiais: Elas são mantidas e atualizadas regularmente
- Não armazene segredos em imagens: Use variáveis de ambiente ou ferramentas de gerenciamento de segredos
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