04 Boas Práticas e Otimizações - KleberVales/Dominando-Docker GitHub Wiki
1. Reducing image size with lightweight JDKs
2. Multi-stage builds for production
3. Container security
4. Image versioning and organization
5.Automating builds with CI/CD (GitHub Actions, GitLab CI)
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Reduzindo o tamanho da imagem com JDKs leves
Um dos maiores desafios ao dockerizar aplicações Java é o tamanho das imagens. Usar imagens pesadas aumenta tempo de build, consumo de rede e espaço em disco. A solução é optar por JDKs/JREs leves e boas práticas de otimização.
Por que reduzir o tamanho da imagem?
-
Performance: imagens menores são baixadas e iniciadas mais rápido.
-
Eficiência: menos consumo de espaço em registries e servidores.
-
Segurança: menos pacotes → menor superfície de ataque.
-
Portabilidade: ideal para ambientes de CI/CD e orquestradores como Kubernetes.
Usando JDKs leves
-
Imagens base slim:
-
openjdk:17-jdk-slim → versão enxuta, sem ferramentas extras.
-
eclipse-temurin:17-jre-alpine → baseada em Alpine Linux, extremamente leve.
-
-
JRE vs JDK:
-
Use JRE (Java Runtime Environment) em produção, já que não precisa compilar código.
-
Reserve JDK apenas para builds.
-
Multi-stage builds
Separar build e execução garante que apenas o necessário vá para a imagem final.
# Etapa 1: Build
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
# Etapa 2: Runtime leve
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/minha-app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Usando ferramentas de otimização
- jlink: cria uma runtime Java personalizada contendo apenas os módulos necessários.
jlink --module-path $JAVA_HOME/jmods --add-modules java.base,java.sql --output /customjre
-
jpackage: empacota aplicações Java com dependências mínimas.
-
Distroless images (Google): imagens sem sistema operacional completo, apenas runtime necessário.
Boas práticas adicionais
-
Criar um .dockerignore para evitar copiar arquivos desnecessários.
-
Usar camadas otimizadas (copiar pom.xml antes do código para aproveitar cache).
-
Evitar instalar pacotes extras no container.
-
Usar tags específicas (17-jre-alpine) em vez de latest para consistência.
Comparação de tamanhos (aproximado)
| Imagem base | Tamanho médio |
|---|---|
| openjdk:17 | ~650 MB |
| openjdk:17-slim | ~300 MB |
| eclipse-temurin:17-jre | ~200 MB |
| eclipse-temurin:17-jre-alpine | ~80 MB |
| Runtime customizado via jlink | ~40–60 MB |
Multi-stage builds para produção
Os multi-stage builds são uma técnica poderosa no Docker para criar imagens otimizadas, especialmente em ambientes de produção. Eles permitem separar o processo de build (compilação, testes, empacotamento) do processo de execução, resultando em imagens menores, mais seguras e rápidas.
O que são multi-stage builds?
É a prática de usar múltiplas etapas dentro de um único Dockerfile.
Cada etapa pode ter uma imagem base diferente (ex.: Maven para build, JRE para execução).
Apenas os artefatos necessários são copiados para a imagem final.
Benefícios em produção
Redução de tamanho: elimina ferramentas de build (Maven, Gradle, etc.) da imagem final.
Segurança: menos pacotes → menor superfície de ataque.
Performance: imagens menores são mais rápidas para distribuir e iniciar.
Manutenção: facilita separar responsabilidades (build vs runtime).
Tipos de multi-stage
Estágio de build
Estágio de execução
Test stage
Lint/Analysis stage
Dev stage
Final/Production stage
Comparação de tamanhos (aproximado)
| Estratégia | Tamanho médio |
|---|---|
| Build + Runtime juntos | ~600 MB |
| Multi-stage com JRE slim | ~200 MB |
| Multi-stage com JRE alpine | ~80 MB |
Segurança em containers
A segurança em containers é um aspecto crítico para ambientes de produção. Embora os containers ofereçam isolamento e portabilidade, eles também podem introduzir riscos se não forem configurados e monitorados corretamente.
Principais riscos em containers
Imagens vulneráveis: uso de imagens com pacotes desatualizados ou inseguros.
Configurações inseguras: execução como usuário root dentro do container.
Exposição de segredos: credenciais e chaves armazenadas diretamente em arquivos ou variáveis mal gerenciadas.
Rede aberta: portas expostas sem controle de acesso.
Persistência de dados: volumes sem criptografia ou controle de permissões.
Boas práticas de segurança
Usar imagens oficiais e verificadas: sempre preferir imagens de registries confiáveis.
Reduzir privilégios: evitar rodar containers como root, criando usuários específicos.
RUN adduser --disabled-password appuser
USER appuser
Gerenciar segredos corretamente: usar ferramentas como Docker Secrets, Vault ou AWS Secrets Manager.
Minimizar superfície de ataque: usar imagens leves (ex.: alpine) e remover pacotes desnecessários.
Atualizar imagens regularmente: aplicar patches e manter dependências atualizadas.
Configurar redes isoladas: separar containers em redes específicas para limitar comunicação.
Healthchecks: garantir que containers estejam funcionando corretamente e detectar falhas.
Ferramentas de segurança
Docker Bench for Security: verifica configurações e boas práticas de segurança.
Clair / Trivy: scanners de vulnerabilidades em imagens.
Falco: monitora comportamento em tempo real para detectar atividades suspeitas.
AppArmor / SELinux: adicionam camadas extras de controle de acesso.
Segurança em produção
Controle de acesso: usar RBAC (Role-Based Access Control) em orquestradores como Kubernetes.
Monitoramento contínuo: integrar logs e métricas em ferramentas como Prometheus e ELK Stack.
Políticas de rede: aplicar firewalls e regras de comunicação entre serviços.
Auditoria: manter trilhas de auditoria para rastrear alterações e acessos.
Versionamento e organização de imagens
Gerenciar imagens Docker de forma organizada é essencial para manter ambientes consistentes, facilitar o deploy e evitar confusões entre versões. O versionamento garante que cada build seja identificado corretamente, enquanto a organização ajuda a estruturar repositórios e fluxos de trabalho.
Por que versionar imagens?
Consistência: garante que ambientes diferentes usem a mesma versão da aplicação.
Reprodutibilidade: facilita reproduzir bugs ou comportamentos em versões específicas.
Controle de mudanças: permite acompanhar evolução da aplicação.
Segurança: evita uso de imagens desatualizadas ou não verificadas.
Estrutura de tags
As tags são usadas para identificar versões de imagens.
Exemplos de boas práticas:
-
Semântica de versão:
- 1.0.0, 1.1.0, 2.0.0
-
Ambientes:
- app:dev, app:staging, app:prod
-
Datas de build:
- app:2026-01-30
-
Commit hash (CI/CD):
- app:abc1234
Evite usar apenas latest em produção, pois pode gerar inconsistências.
Organização em registries
-
Docker Hub
-
Estrutura simples: usuario/app:versao.
-
Ideal para projetos open-source ou pessoais.
-
-
GitHub Container Registry (GHCR)
-
Integrado ao GitHub.
-
Permite versionar imagens junto ao código.
-
Exemplo: ghcr.io/usuario/app:1.0.0.
-
-
AWS Elastic Container Registry (ECR)
-
Integrado ao ecossistema AWS.
-
Suporte a autenticação via IAM.
-
Exemplo: .dkr.ecr.us-east-1.amazonaws.com/app:1.0.0.
-
Fluxo de versionamento com CI/CD
1. Build da imagem:
docker build -t usuario/app:1.0.0 .
2. Tag adicional para ambiente:
docker tag usuario/app:1.0.0 usuario/app:prod
3. Push para registry:
docker push usuario/app:1.0.0
docker push usuario/app:prod
4. Deploy automatizado: orquestradores (Kubernetes, Docker Swarm) usam a tag correta.
Boas práticas de organização
Padronizar nomes e tags → facilita automação e leitura.
Usar múltiplas tags → versão + ambiente (1.0.0-prod).
Automatizar builds → pipelines CI/CD devem gerar e publicar imagens.
Manter histórico → não sobrescrever versões antigas.
Documentar → registrar convenções de versionamento no repositório.
Automatizando builds com CI/CD (GitHub Actions, GitLab CI)
Automatizar builds com CI/CD (Continuous Integration / Continuous Deployment) é essencial para garantir consistência, rapidez e confiabilidade no ciclo de desenvolvimento. Com ferramentas como GitHub Actions e GitLab CI, podemos configurar pipelines que constroem, testam e publicam imagens Docker automaticamente.
Por que automatizar builds?
Consistência: cada commit gera uma imagem padronizada.
Agilidade: elimina etapas manuais de build e push.
Qualidade: integra testes automatizados antes do deploy.
Integração: conecta diretamente com registries (Docker Hub, GHCR, AWS ECR).
Exemplo com GitHub Actions
Crie um arquivo .github/workflows/docker-build.yml:
name: Build and Push Docker Image
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout código
uses: actions/checkout@v3
- name: Login no Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build da imagem
run: docker build -t usuario/minha-app:${{ github.sha }} .
- name: Push da imagem
run: docker push usuario/minha-app:${{ github.sha }}
Destaques:
Usa secrets para credenciais seguras.
Versiona imagens com o hash do commit (${{ github.sha }}).
Automatiza build e push sempre que houver commit na branch main.
Boas práticas em pipelines CI/CD
Usar múltiplas tags: versão semântica + commit hash + ambiente (1.0.0, prod, abc123).
Executar testes antes do build: garantir que apenas código validado vá para produção.
Segredos seguros: nunca expor credenciais em YAML, sempre usar secrets ou variables.
Cache de dependências: otimizar builds repetidos.
Deploy automatizado: integrar com Kubernetes, Docker Swarm ou serviços cloud.
Fluxo típico de CI/CD com Docker
Commit no repositório → dispara pipeline.
Build da imagem → gera versão única.
Testes automatizados → validam aplicação.
Push para registry → imagem disponível para deploy.
Deploy automático → orquestrador aplica nova versão.