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.