Docker e Kotlin: conteinerização de aplicações de nível avançado
A conteinerização tornou-se uma parte essencial do desenvolvimento moderno. Se você já domina os princípios básicos do Docker e escreveu algumas imagens simples em Kotlin, é hora de passar para o próximo nível. Neste artigo, abordaremos técnicas avançadas: builds multi-estágio, otimização do tamanho da imagem final, trabalho com frameworks populares (Ktor, Spring Boot) e gerenciamento eficiente de corrotinas em ambiente conteinerizado.
1. Builds multi-estágio (Multi-stage builds)
Um dos principais problemas ao conteinerizar aplicações JVM é o tamanho da imagem. Uma imagem base JDK pode pesar centenas de megabytes. O build multi-estágio permite separar as etapas de compilação e execução, deixando na imagem final apenas o JRE e o JAR compilado.
Vamos considerar um exemplo para uma aplicação Kotlin compilada com Gradle:
# Estágio de buildFROM gradle:7.6-jdk17 AS builderWORKDIR /appCOPY build.gradle.kts settings.gradle.kts ./COPY src ./srcRUN gradle build --no-daemon -x test
# Estágio de execuçãoFROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY --from=builder /app/build/libs/*.jar app.jarEXPOSE 8080CMD ["java", "-jar", "app.jar"]Por que isso é eficiente?
- No estágio de build, usamos o JDK completo e o Gradle, mas eles não entram na imagem final.
- A imagem final contém apenas o JRE (Java Runtime Environment), reduzindo o tamanho em 3 a 5 vezes.
- O Alpine Linux reduz ainda mais o volume para ~150-200 MB, em vez de 600+ MB.
2. Otimização de camadas e cache
O Docker armazena em cache as camadas, mas apenas se elas não forem alteradas. Para acelerar rebuilds, separe as dependências e o código fonte:
FROM gradle:7.6-jdk17 AS builderWORKDIR /app
# Primeiro, copie apenas os arquivos de dependênciasCOPY build.gradle.kts settings.gradle.kts ./RUN gradle dependencies --no-daemon
# Depois, o código fonte (muda com mais frequência)COPY src ./srcRUN gradle build --no-daemon -x testEssa abordagem garante que a camada com dependências seja reconstruída apenas quando o arquivo build.gradle.kts for alterado. Caso contrário, o Docker usa o cache, economizando minutos a cada iteração.
Dica: para projetos Kotlin com muitas dependências (ex: Spring Boot), essa técnica é especialmente relevante — o carregamento do cache do Gradle pode levar de 2 a 3 minutos.
3. Conteinerização de aplicações Ktor
Ktor é um framework assíncrono leve em Kotlin, ideal para microsserviços. A conteinerização de uma aplicação Ktor requer configuração mínima, mas há nuances relacionadas às corrotinas.
Exemplo de Dockerfile para Ktor:
FROM gradle:7.6-jdk17 AS builderWORKDIR /appCOPY build.gradle.kts settings.gradle.kts ./RUN gradle dependencies --no-daemonCOPY src ./srcRUN gradle build --no-daemon -x test
FROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY --from=builder /app/build/libs/ktor-app-*.jar app.jarEXPOSE 8080
# Importante: configuração da JVM para corrotinasCMD ["java", "-XX:+UseZGC", "-Xms256m", "-Xmx512m", "-jar", "app.jar"]Pontos-chave:
- Use ZGC (Z Garbage Collector) — ele minimiza pausas, o que é crítico para aplicações assíncronas com muitas corrotinas.
- Limite a memória com
-Xmse-Xmx. Em contêineres, isso ajuda a evitar erros OOM. - Certifique-se de que o servidor Ktor não bloqueie a thread principal — as corrotinas devem ser configuradas via
Dispatchers.IOou um pool personalizado.
Exemplo de configuração do Ktor para funcionar em contêiner:
fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/") {```