Docker e Java: nível avançado de conteinerização de aplicações
\n\nA conteinerização de aplicações Java tornou-se o padrão de facto no desenvolvimento moderno. No entanto, o conhecimento básico de Docker (criação de imagem a partir de openjdk:11 e execução de contêiner) já não atende aos requisitos de ambientes de produção. Em nível avançado, é necessário considerar as particularidades da JVM dentro do contêiner, otimizar o tamanho das imagens, gerenciar a memória e configurar o monitoramento. Neste artigo, abordaremos as principais técnicas que o ajudarão a criar imagens Docker eficientes e seguras para aplicações Java.
1. Construção multi-estágio: minimização do tamanho da imagem
\n\nA abordagem clássica "um Dockerfile — uma imagem" frequentemente leva ao inchaço do artefato final. Ferramentas de construção (Maven, Gradle, compilador) que não são necessárias em tempo de execução acabam na imagem. A solução é a construção multi-estágio (multi-stage). Dividimos o processo em etapas: construção (build) e execução (runtime).
\n\nExemplo de Dockerfile para uma aplicação Spring Boot usando Maven:
\n\n# Etapa de construção\nFROM maven:3.8.6-openjdk-17 AS builder\nWORKDIR /app\nCOPY pom.xml .\nRUN mvn dependency:go-offline -B\nCOPY src ./src\nRUN mvn clean package -DskipTests\n\n# Etapa de execução\nFROM eclipse-temurin:17-jre-alpine\nWORKDIR /app\nCOPY --from=builder /app/target/*.jar app.jar\nEXPOSE 8080\nENTRYPOINT ["java", "-jar", "app.jar"]\n\nComo resultado, a imagem final pesa ~150 MB em vez de 600+ MB. Importante: use imagens jre (não jdk) e distribuições mínimas (Alpine, Slim). Para aplicações em Java 17+, considere usar jlink — criação de uma JRE personalizada apenas com os módulos necessários.
2. Configuração da JVM para funcionar dentro do contêiner
\n\nPor muito tempo, a JVM não conseguia identificar corretamente os limites de memória e CPU definidos para o contêiner. A partir do Java 8u191 e Java 10, surgiu o suporte a cgroups v1 (e com Java 15, cgroups v2). No entanto, não se deve confiar apenas na automação.
\n\nParâmetros-chave para produção:
\n- \n
-XX:+UseContainerSupport— ativa o suporte a contêineres (ativo por padrão no Java 10+). \n -XX:MaxRAMPercentage=75.0— limita o uso do heap a 75% da memória disponível do contêiner. Isso reserva memória para o metaspaco, pilha de threads e processos do sistema. \n -XX:ActiveProcessorCount=2— especificação explícita do número de processadores disponíveis, caso a deteção automática falhe. \n
Exemplo de comando ENTRYPOINT completo:
\n\nENTRYPOINT ["java", \\\n "-XX:+UseContainerSupport", \\\n "-XX:MaxRAMPercentage=75.0", \\\n "-XX:+ExitOnOutOfMemoryError", \\\n "-jar", "app.jar"]\n\nImportante: evite valores fixos de -Xmx e -Xms em ambientes Docker. Eles não se adaptam a alterações nos limites do contêiner durante replicação ou escalonamento.
3. Otimização de camadas e cache de construção
\n\nCada instrução no Dockerfile cria uma nova camada. O Docker armazena em cache as camadas que não foram alteradas. Para acelerar reconstruções, organize as instruções das menos mutáveis para as mais mutáveis.
\n\nEstrutura correta do Dockerfile:
\n- \n
- Instalação de dependências do SO (apk add, apt-get). \n
- Cópia de arquivos de configuração do construtor (pom.xml, build.gradle). \n
- Download de dependências (mvn dependency:go-offline). \n
- Cópia do código-fonte. \n
- Construção e cópia do artefato. \n
Adicionalmente, pode-se usar o docker layer caching em CI/CD. Por exemplo, no GitHub Actions ou GitLab CI, salve o cache do Maven entre as construções:
\n\n# Exemplo para GitLab CI\ncache:\n key: ${CI_COMMIT_REF_SLUG}\n paths:\n - .m2/repository/\n - .gradle/caches/\n\nConsidere também o uso de
Blogs
Book Recommendations
Leia também
Testes em Rust: unit, integration, e2e — guia completo
NullPointerException no fragmento Kotlin: como vencer e não enlouquecer
Refatoração de código TypeScript: quando e como transformar o caos em ordem
Trabalhando com JSON em Go: parsing e serialização de dados
Criando um bot do Telegram em Swift: da ideia ao deploy