<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Denis Arruda Santos</title>
    <description>The latest articles on Forem by Denis Arruda Santos (@denisarruda).</description>
    <link>https://forem.com/denisarruda</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3740095%2Fb112192e-5387-434c-832a-8cc735174404.jpeg</url>
      <title>Forem: Denis Arruda Santos</title>
      <link>https://forem.com/denisarruda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/denisarruda"/>
    <language>en</language>
    <item>
      <title>Observabilidade de agentes de IA com LangChain4j</title>
      <dc:creator>Denis Arruda Santos</dc:creator>
      <pubDate>Thu, 02 Apr 2026 01:26:02 +0000</pubDate>
      <link>https://forem.com/denisarruda/observabilidade-de-agentes-de-ia-com-langchain4j-ncd</link>
      <guid>https://forem.com/denisarruda/observabilidade-de-agentes-de-ia-com-langchain4j-ncd</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Estamos vivendo uma onda no desenvolvimento de software impulsionada pelo uso de &lt;strong&gt;IA generativa&lt;/strong&gt; e, mais recentemente, por &lt;strong&gt;agentes de IA&lt;/strong&gt; capazes de tomar decisões, orquestrar chamadas a modelos e interagir com ferramentas externas.&lt;/p&gt;

&lt;p&gt;Esses agentes vão além de simples integrações com LLMs. Eles executam fluxos dinâmicos, fazem múltiplas chamadas ao modelo, utilizam ferramentas e tomam decisões com base no contexto. Esse comportamento os aproxima muito mais de sistemas distribuídos.&lt;/p&gt;

&lt;p&gt;Com isso, à medida que começamos a levar esses agentes para &lt;strong&gt;ambientes corporativos&lt;/strong&gt;, surge um requisito essencial que não pode ser ignorado: &lt;strong&gt;observabilidade e monitoramento&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;De forma simplificada:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitoramento&lt;/strong&gt; está relacionado a acompanhar métricas do sistema, como latência, taxa de erro, uso de recursos e disponibilidade. Ele responde perguntas como &lt;em&gt;"o sistema está saudável?"&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observabilidade&lt;/strong&gt; está relacionado a  capacidade de entender o comportamento interno do sistema a partir de sinais externos (métricas, logs e traces). Ela permite responder perguntas como &lt;em&gt;"por que o sistema está se comportando dessa forma?"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Precisamos dessa visibilidade para poder responder perguntas como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quantas chamadas ao modelo foram feitas?&lt;/li&gt;
&lt;li&gt;Qual foi o custo em tokens dessa operação?&lt;/li&gt;
&lt;li&gt;Onde está o gargalo de performance?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esse desafio se torna ainda mais difícil em cenários com &lt;strong&gt;multiagentes&lt;/strong&gt;, onde diferentes agentes interagem entre si, ampliando a complexidade do fluxo e tornando a análise ainda mais difícil sem ferramentas adequadas.&lt;/p&gt;

&lt;p&gt;Neste artigo, vamos focar na &lt;strong&gt;observabilidade de agentes de IA&lt;/strong&gt;, explorando como instrumentar e monitorar suas execuções utilizando o &lt;strong&gt;LangChain4j&lt;/strong&gt; em conjunto com sua extensão para o &lt;strong&gt;Quarkus&lt;/strong&gt;, e como visualizar essas informações de forma prática através de dashboards no Grafana.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que observar em agentes de IA
&lt;/h2&gt;

&lt;p&gt;Diferente de aplicações tradicionais, onde observamos requisições HTTP, bancos de dados e filas, agentes de IA introduzem uma nova complexidade.&lt;/p&gt;

&lt;p&gt;Abaixo estão os principais pontos que devem ser observados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Latência de cada chamada ao modelo.&lt;/li&gt;
&lt;li&gt;Consumo de tokens de entrada.&lt;/li&gt;
&lt;li&gt;Consumo de tokens de saída.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As métricas são fundamentais em ambientes corporativos, onde o custo precisa ser previsível e controlado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usando o LangChain4j com Quarkus
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;LangChain4j&lt;/strong&gt; já fornece mecanismos nativos para observar esses aspectos através de eventos do ciclo de vida da execução do agente, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Início da execução&lt;/li&gt;
&lt;li&gt;Requisição ao modelo&lt;/li&gt;
&lt;li&gt;Resposta recebida&lt;/li&gt;
&lt;li&gt;Execução de ferramentas&lt;/li&gt;
&lt;li&gt;Erros&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Na próxima seção, vamos explorar como esses eventos funcionam na prática e como utilizá-los para instrumentar nossos agentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expondo métricas com LangChain4j no Quarkus
&lt;/h2&gt;

&lt;p&gt;Uma das grandes vantagens de utilizar o &lt;strong&gt;Quarkus&lt;/strong&gt; em conjunto com o &lt;strong&gt;LangChain4j&lt;/strong&gt; é a facilidade de integrar observabilidade utilizando padrões já consolidados no ecossistema Java.&lt;/p&gt;

&lt;p&gt;Para expor métricas da aplicação (incluindo aquelas relacionadas à execução de agentes de IA), basta incluir a extensão de Micrometer no projeto para que métricas passem a ser expostas automaticamente.&lt;/p&gt;

&lt;p&gt;Exemplo de trecho do &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.quarkus&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;quarkus-micrometer&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.quarkus&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;quarkus-micrometer-registry-prometheus&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Após incluir essas dependências, o Quarkus automaticamente expõe as métricas da aplicação no endpoint:&lt;/p&gt;

&lt;p&gt;Por padrão, elas ficam disponíveis em:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/q/metrics&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Como vamos utilizar o Prometheus, esse comportamento é exatamente o que precisamos. O endpoint já expõe as métricas no formato nativo do Prometheus, permitindo que ele realize o scraping diretamente, sem necessidade de adaptações ou conversões adicionais.&lt;/p&gt;

&lt;p&gt;Com essa configuração, qualquer métrica registrada via Micrometer, incluindo as geradas pelo LangChain4j, estará disponível para coleta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando Prometheus e Grafana
&lt;/h2&gt;

&lt;p&gt;Para visualizar as métricas coletadas, vamos utilizar duas ferramentas bastante conhecidas de observabilidade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt;: responsável por coletar (scraping) e armazenar as métricas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana&lt;/strong&gt;: utilizado para visualização e criação de dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neste artigo, não vamos nos aprofundar na configuração detalhada dessas ferramentas, pois o foco será a &lt;strong&gt;construção dos dashboards para agentes de IA&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurando o Grafana e Prometheus
&lt;/h3&gt;

&lt;p&gt;Para que o Prometheus consiga coletar as métricas expostas pelo Quarkus, precisamos apenas configurar um &lt;em&gt;job&lt;/em&gt; apontando para o endpoint &lt;code&gt;/q/metrics&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Exemplo de configuração (&lt;code&gt;prometheus.yml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quarkus-app'&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/q/metrics'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:8080'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, o Prometheus já será capaz de coletar todas as métricas expostas pela aplicação.&lt;/p&gt;

&lt;p&gt;Para facilitar, você pode utilizar uma imagem pronta que já inclui Prometheus, Grafana e outras ferramentas integradas. Uma ótima opção é a imagem &lt;br&gt;
&lt;code&gt;grafana/otel-lgtm&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dashboards no Grafana para agentes de IA
&lt;/h2&gt;

&lt;p&gt;Em aplicações corporativas, é comum termos diversos dashboards cobrindo diferentes aspectos do sistema, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Métricas de APIs REST (latência, throughput, erros)&lt;/li&gt;
&lt;li&gt;Banco de dados (conexões, tempo de query)&lt;/li&gt;
&lt;li&gt;Infraestrutura (CPU, memória)&lt;/li&gt;
&lt;li&gt;Métricas da JVM (GC, threads, heap)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esses dashboards continuam sendo fundamentais e fazem parte da base de qualquer estratégia de observabilidade.&lt;/p&gt;

&lt;p&gt;No entanto, ao introduzirmos &lt;strong&gt;agentes de IA&lt;/strong&gt;, surge uma nova complexidade que precisa ser monitorada. Aqui, vamos focar exclusivamente em &lt;strong&gt;como observar agentes de IA através de dashboards no Grafana&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Métricas disponíveis com LangChain4j no Quarkus
&lt;/h2&gt;

&lt;p&gt;Utilizando a extensão do &lt;strong&gt;LangChain4j&lt;/strong&gt; com o &lt;strong&gt;Quarkus&lt;/strong&gt;, as seguintes métricas estão disponíveis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;langchain4j_aiservices_counted_total&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Total de execuções de serviços de IA (quantidade de chamadas realizadas)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;langchain4j_aiservices_timed_seconds_count&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Número de execuções monitoradas para cálculo de tempo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;langchain4j_aiservices_timed_seconds_sum&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Soma total do tempo gasto nas execuções&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;langchain4j_aiservices_timed_seconds_max&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Tempo máximo registrado em uma execução&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Construindo dashboards no Grafana
&lt;/h2&gt;

&lt;p&gt;Com base nas métricas expostas pelo &lt;strong&gt;LangChain4j&lt;/strong&gt;, podemos construir os dashboards no Grafana.&lt;/p&gt;

&lt;p&gt;A seguir estão alguns exemplos de painéis e as métricas utilizadas em cada um deles:&lt;/p&gt;
&lt;h3&gt;
  
  
  AI Services (visão geral)
&lt;/h3&gt;

&lt;p&gt;Um painel do tipo tabela pode ser utilizado para exibir uma visão geral dos serviços de IA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métrica utilizada:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;langchain4j_aiservices_counted_total&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com essa métrica, conseguimos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identificação dos agentes de IA&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Input Token Usage Total
&lt;/h3&gt;

&lt;p&gt;Esse painel exibe o total de &lt;strong&gt;tokens de entrada (input)&lt;/strong&gt; consumidos pelos agentes de IA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métrica utilizada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gen_ai_client_token_usage_total{gen_ai_token_type="input"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output Token Usage Total
&lt;/h3&gt;

&lt;p&gt;Esse painel exibe o total de &lt;strong&gt;tokens de saída (output)&lt;/strong&gt; gerados pelos agentes de IA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métrica utilizada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gen_ai_client_token_usage_total{gen_ai_token_type="output"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI Estimated Cost Total
&lt;/h3&gt;

&lt;p&gt;Esse painel exibe o &lt;strong&gt;custo estimado total&lt;/strong&gt; das interações com os modelos de IA ao longo do tempo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métrica utilizada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gen_ai_client_estimated_cost_total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI Client Operation Duration (latência média)
&lt;/h3&gt;

&lt;p&gt;Esse painel exibe a &lt;strong&gt;latência média das chamadas ao modelo (LLM)&lt;/strong&gt; realizadas pelos agentes de IA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métrica utilizada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sum by (service_name) (
  rate(gen_ai_client_operation_duration_seconds_sum[5m])
)
/
sum by (service_name) (
  rate(gen_ai_client_operation_duration_seconds_count[5m])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI Services Duration (latência média do agente)
&lt;/h3&gt;

&lt;p&gt;Esse painel exibe a &lt;strong&gt;latência média de execução dos serviços de IA&lt;/strong&gt;, ou seja, o tempo total que um agente leva para processar uma requisição completa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Métrica utilizada:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sum by (aiservice) (
  rate(langchain4j_aiservices_timed_seconds_sum[5m])
)
/
sum by (aiservice) (
  rate(langchain4j_aiservices_timed_seconds_count[5m])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você pode importar diretamente o dashboard e adaptar conforme sua necessidade:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/denis-arruda/langchain4j-grafana-dashboard" rel="noopener noreferrer"&gt;https://github.com/denis-arruda/langchain4j-grafana-dashboard&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações finais
&lt;/h2&gt;

&lt;p&gt;Aplicações que utilizam &lt;strong&gt;agentes de IA&lt;/strong&gt;, e principalmente cenários com &lt;strong&gt;multiagentes&lt;/strong&gt;, trazem uma nova complexidade para o desenvolvimento de software.&lt;/p&gt;

&lt;p&gt;Esses sistemas se comportam, na prática, como &lt;strong&gt;aplicações distribuídas&lt;/strong&gt;, com fluxos dinâmicos e  múltiplas interações com modelos de linguagem. Isso torna ainda mais desafiador entender seu comportamento em produção.&lt;/p&gt;

&lt;p&gt;Por esse motivo, &lt;strong&gt;observabilidade deixa de ser um diferencial e passa a ser um requisito essencial&lt;/strong&gt;, especialmente em ambientes corporativos.&lt;/p&gt;

&lt;p&gt;Ao longo deste artigo, vimos é simples dar os primeiros passos.&lt;/p&gt;

&lt;p&gt;A integração entre Prometheus e Grafana é bastante natural e já consolidada no ecossistema Java, o que reduz significativamente o esforço de adoção.&lt;/p&gt;

&lt;p&gt;Além de construir agentes inteligentes, é preciso  entender como eles se comportam em produção.&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>java</category>
      <category>quarkus</category>
    </item>
    <item>
      <title>Java em Containers: Estratégias Modernas para Build</title>
      <dc:creator>Denis Arruda Santos</dc:creator>
      <pubDate>Thu, 05 Feb 2026 12:12:27 +0000</pubDate>
      <link>https://forem.com/denisarruda/java-em-containers-estrategias-modernas-para-build-1bpa</link>
      <guid>https://forem.com/denisarruda/java-em-containers-estrategias-modernas-para-build-1bpa</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Quando pensamos em entregar software com qualidade e em automatizar ao máximo esse processo, a infraestrutura como código e os containers desempenham um papel fundamental. &lt;/p&gt;

&lt;p&gt;Criar containers nem sempre é a parte mais divertida do nosso dia a dia, mas faz parte do nosso trabalho como desenvolvedores: não apenas escrever código, mas também garantir que ele seja executado da melhor forma possível em produção.&lt;/p&gt;

&lt;p&gt;O objetivo deste artigo é avaliar as principais opções disponíveis hoje para a criação de containers para aplicações Java, apresentando uma visão geral de cada abordagem. Nenhuma delas é universalmente melhor que as outras, todas possuem vantagens e desvantagens. A ideia é ajudar você a escolher a alternativa mais adequada ao seu contexto e, com isso, aprimorar o processo de build do seu projeto.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é um Container
&lt;/h2&gt;

&lt;p&gt;Antes de escolher a alternativa mais adequada, é fundamental entender o que realmente é um container. Compreender esse conceito nos ajuda a enxergar o que pode ser melhorado.&lt;/p&gt;

&lt;p&gt;Um container pode ser entendido como um processo autocontido, que reúne todas as dependências necessárias para executar uma aplicação, como bibliotecas, configurações e runtime. Diferente de uma máquina virtual, ele não precisa carregar um sistema operacional completo, o que o torna muito mais leve e rápido.&lt;/p&gt;

&lt;p&gt;Os containers são executados sobre um Docker Daemon, que é o responsável por gerenciar imagens, redes, volumes e a execução dos processos. Além disso, eles compartilham o kernel do sistema operacional do host onde estão rodando.&lt;/p&gt;

&lt;p&gt;Essa arquitetura permite que múltiplos containers coexistam no mesmo ambiente de forma eficiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile simples
&lt;/h2&gt;

&lt;p&gt;O Dockerfile é como se fosse uma receita para a criação de uma imagem de container. É nele que descrevemos, passo a passo, como o ambiente da aplicação deve ser montado, quais dependências devem ser instaladas e como o sistema deve ser inicializado.&lt;/p&gt;

&lt;p&gt;Cada instrução presente no Dockerfile gera uma camada na imagem final. Essas camadas são armazenadas em cache pelo Docker e reaproveitadas sempre que possível, tornando o processo de build mais rápido e eficiente.&lt;/p&gt;

&lt;p&gt;Por esse motivo, a ordem das instruções é extremamente importante. As primeiras camadas devem conter aquilo que muda com menos frequência, como a imagem base e as dependências do projeto. Já as últimas camadas devem conter os elementos que mudam mais frequentemente, como o código-fonte e os arquivos compilados.&lt;/p&gt;

&lt;p&gt;A seguir, temos um exemplo de Dockerfile simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; bellsoft/liberica-openjre-debian:25&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app.jar .&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse modelo é frequentemente utilizado por ser fácil de entender, mas ele também apresenta algumas limitações importantes.&lt;/p&gt;

&lt;p&gt;A primeira linha define a imagem base, que, nesse caso, contém um sistema Linux Debian com a JRE do Java na versão 25. Essa escolha facilita a configuração, pois já inclui o runtime necessário.&lt;/p&gt;

&lt;p&gt;Em seguida, o comando COPY assume que já existe um arquivo app.jar pronto para uso. No entanto, o Dockerfile não deixa claro como esse artefato foi gerado. Isso pode gerar inconsistências entre ambientes e dificultar a reprodução do build.&lt;/p&gt;

&lt;p&gt;Outro ponto importante é que esse Dockerfile não define um usuário para executar a aplicação. Como consequência, o container será iniciado com o usuário root, o que representa um risco de segurança.&lt;/p&gt;

&lt;p&gt;Apesar de funcional, esse tipo de Dockerfile deve ser encarado apenas como um ponto de partida.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile Multi-stage
&lt;/h2&gt;

&lt;p&gt;O Dockerfile pode ser composto por múltiplos estágios, em um modelo conhecido como &lt;strong&gt;multi-stage&lt;/strong&gt;. Nesse modelo, cada estágio é responsável por uma etapa específica e gera uma imagem intermediária, que pode servir de entrada no estágio seguinte.&lt;/p&gt;

&lt;p&gt;Essa separação traz diversos benefícios. Um dos principais é a padronização do processo de build, já que toda a construção do projeto passa a ser definida dentro do próprio Dockerfile. Com isso, não é mais necessário depender de configurações externas para gerar o artefato do projeto.&lt;/p&gt;

&lt;p&gt;A seguir, temos um exemplo de Dockerfile multi-stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3-eclipse-temurin-25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;~/.m2 mvn &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/app/pom.xml package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; bellsoft/liberica-openjre-debian:25&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/app
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /opt/app/target/app.jar /opt/app.jar&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "/opt/app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse Dockerfile é dividido em dois estágios principais: o estágio de build e o estágio de execução.&lt;/p&gt;

&lt;p&gt;No primeiro estágio, identificado pelo alias build, é utilizada a imagem maven:3-eclipse-temurin-25, que já contém o Maven e o JDK necessários para compilar a aplicação.&lt;/p&gt;

&lt;p&gt;Em seguida, os diretórios src e o arquivo pom.xml são copiados para dentro da imagem. Esses arquivos são necessários para que o Maven possa resolver as dependências e compilar o projeto. Ela utiliza um recurso para criar um cache persistente do repositório local do Maven.&lt;/p&gt;

&lt;p&gt;No segundo estágio, é utilizada a imagem bellsoft/liberica-openjre-debian:25. Essa imagem é leve e contém apenas a JRE necessária para executar a aplicação.&lt;/p&gt;

&lt;p&gt;Em seguida, o artefato &lt;code&gt;app.jar&lt;/code&gt; gerado no estágio de build é copiado para essa imagem.&lt;/p&gt;

&lt;p&gt;Por fim, a instrução ENTRYPOINT define o comando responsável por iniciar a aplicação Java quando o container é executado.&lt;/p&gt;

&lt;p&gt;Esse modelo permite um processo de build mais padronizado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando JREs Customizadas com jdeps e jlink
&lt;/h2&gt;

&lt;p&gt;Desde o Java versão 9, a JVM passou a ser organizada em módulos, permitindo um controle sobre quais módulos da JVM são necessárias para uma aplicação. A partir dessa modularização, surgiram ferramentas como o &lt;strong&gt;jdeps&lt;/strong&gt; e o &lt;strong&gt;jlink&lt;/strong&gt;, que possibilitam a criação de uma JRE customizada contendo apenas os módulos necessários para uma aplicação.&lt;/p&gt;

&lt;p&gt;Uma JRE reduzida também contribui para a segurança da aplicação. Quanto menor o número de módulos e bibliotecas presentes na imagem, menor é a superfície de ataque e, consequentemente, o risco de exploração de vulnerabilidades.&lt;/p&gt;

&lt;p&gt;A seguir, temos um exemplo de Dockerfile multi-stage utilizando jdeps e jlink:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3-eclipse-temurin-25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven-build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;~/.m2 mvn &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/app/pom.xml package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:25-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;jre-build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=maven-build /opt/app/target/app.jar .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;jar xf app.jar
&lt;span class="k"&gt;RUN &lt;/span&gt;jdeps &lt;span class="nt"&gt;--ignore-missing-deps&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;  &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--recursive&lt;/span&gt;  &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--multi-release&lt;/span&gt; 25  &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--print-module-deps&lt;/span&gt;  &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--class-path&lt;/span&gt; &lt;span class="s1"&gt;'BOOT-INF/lib/*'&lt;/span&gt;  &lt;span class="se"&gt;\
&lt;/span&gt;    app.jar &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; deps.info
&lt;span class="k"&gt;RUN &lt;/span&gt;jlink &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--add-modules&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;deps.info&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--strip-debug&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--compress&lt;/span&gt; 2 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-header-files&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-man-pages&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--output&lt;/span&gt; /customjre

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.18&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=jre-build /customjre /opt/jre&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_HOME=/opt/jre&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PATH:$JAVA_HOME/bin"&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=maven-build /opt/app/target/app.jar /opt/app.jar&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "/opt/app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse exemplo, combinamos o multi-stage com o uso do jdeps e do jlink para gerar uma JRE customizada, resultando em uma imagem final ainda mais enxuta.&lt;/p&gt;

&lt;p&gt;No segundo estágio, utilizamos uma imagem baseada em Alpine com o JDK, necessária para executar o jdeps e o jlink.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;jdeps&lt;/code&gt; é utilizado para identificar quais módulos da JVM  são utilizados pela aplicação. O resultado é gravado no arquivo &lt;code&gt;deps.info&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Em seguida, o &lt;code&gt;jlink&lt;/code&gt; utiliza a lista gerada pelo jdeps para montar uma runtime personalizada.&lt;/p&gt;

&lt;p&gt;No último estágio, utilizamos uma imagem Alpine mínima. Copiamos apenas a JRE customizada criada anteriormente e configuramos as variáveis de ambiente para que o sistema utilize essa versão do Java.&lt;/p&gt;

&lt;p&gt;Em seguida, copiamos novamente o JAR da aplicação, para a imagem final.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extração de Camadas em Aplicações Spring Boot
&lt;/h2&gt;

&lt;p&gt;Quando utilizamos o Spring Boot para empacotar uma aplicação no formato de &lt;strong&gt;uber JAR&lt;/strong&gt; ou &lt;strong&gt;fat JAR&lt;/strong&gt;, todas as dependências, bibliotecas e o código da aplicação ficam  em um único arquivo. Esse modelo facilita a distribuição, mas não é o mais eficiente para construção de imagens Docker.&lt;/p&gt;

&lt;p&gt;O Spring Boot também oferece um mecanismo de extração de camadas. O objetivo principal é otimizar o processo de build da imagem, aproveitando melhor o cache do Docker.&lt;/p&gt;

&lt;p&gt;Com esse mecanimos, o JAR é dividido em quatro camadas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependências do Spring Framework&lt;/li&gt;
&lt;li&gt;Spring Boot Loader&lt;/li&gt;
&lt;li&gt;Dependências da aplicação&lt;/li&gt;
&lt;li&gt;Código da aplicação&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada uma dessas partes passa a ser tratada como uma camada independente dentro da imagem Docker.&lt;/p&gt;

&lt;p&gt;Isso permite organizar o Dockerfile de forma que os componentes que mudam com menos frequência, como as dependências e o loader do Spring, sejam copiados primeiro. Já o código da aplicação, que sofre alterações com mais frequência, fica nas últimas camadas.&lt;/p&gt;

&lt;p&gt;Além disso, ao separar as camadas, o tempo de inicialização da aplicação Spring Boot também pode ser reduzido em alguns milissegundos.&lt;/p&gt;

&lt;p&gt;A seguir, um exemplo de Dockerfile com extração das camadas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3-eclipse-temurin-25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;~/.m2 mvn &lt;span class="nt"&gt;-f&lt;/span&gt; /build/pom.xml package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;bellsoft/liberica-openjre-debian:25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;extractor&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /extract&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /build/target/app.jar application.jar&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;java &lt;span class="nt"&gt;-Djarmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tools &lt;span class="nt"&gt;-jar&lt;/span&gt; application.jar extract &lt;span class="nt"&gt;--layers&lt;/span&gt; &lt;span class="nt"&gt;--destination&lt;/span&gt; extracted

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; bellsoft/liberica-openjre-debian:25&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /application&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/spring-boot-loader/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/snapshot-dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/application/ ./&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "application.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim como nos exemplos anteriores, o primeiro estágio é responsável por compilar o projeto utilizando Maven.&lt;/p&gt;

&lt;p&gt;No segundo estágio, utilizamos uma imagem que contém apenas a JRE, suficiente para realizar a extração das camadas.&lt;/p&gt;

&lt;p&gt;Em seguida, executamos o comando &lt;code&gt;java -Djarmode=tools -jar application.jar extract --layers&lt;/code&gt; para extrair as camadas do arquivo.&lt;/p&gt;

&lt;p&gt;No último estágio, copiamos cada camada separadamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abordagem Ultimate
&lt;/h2&gt;

&lt;p&gt;A versão que vamos chamar aqui de Ultimate combina as principais técnicas apresentadas ao longo deste artigo para criar imagens Docker para aplicações Java. Nessa abordagem, unimos o uso de multi-stage, a criação de uma JRE customizada com jdeps e jlink, e a extração de camadas do Spring Boot.&lt;/p&gt;

&lt;p&gt;Além disso, essa estratégia também inclui a criação de um usuário específico, com permissões apenas para executar a aplicação. Dessa forma, evitamos que o container seja iniciado com o usuário root, reduzindo o risco em caso de falhas ou vulnerabilidades.&lt;/p&gt;

&lt;p&gt;Ao combinar essas técnicas, conseguimos separar as responsabilidade dentro do processo de build. Um estágio é responsável pela compilação, outro pela análise e geração da JRE runtime, outro pela extração das camadas, e o estágio final reúne apenas o que é necessário para execução.&lt;/p&gt;

&lt;p&gt;Em seguida, o Dockerfile na versão Ultimate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3-eclipse-temurin-25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;~/.m2 mvn &lt;span class="nt"&gt;-f&lt;/span&gt; /build/pom.xml package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:25-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;jre-build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /jrebuild&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /build/target/app.jar .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;jar xf app.jar
&lt;span class="k"&gt;RUN &lt;/span&gt;jdeps &lt;span class="nt"&gt;--ignore-missing-deps&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--multi-release&lt;/span&gt; 25 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--print-module-deps&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--class-path&lt;/span&gt; &lt;span class="s1"&gt;'BOOT-INF/lib/*'&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    app.jar &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; deps.info
&lt;span class="k"&gt;RUN &lt;/span&gt;jlink &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--add-modules&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;deps.info&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--strip-debug&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--compress&lt;/span&gt; 2 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-header-files&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-man-pages&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--output&lt;/span&gt; /customjre

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;bellsoft/liberica-openjre-debian:25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;extractor&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /extract&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /build/target/app.jar application.jar&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;java &lt;span class="nt"&gt;-Djarmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tools &lt;span class="nt"&gt;-jar&lt;/span&gt; application.jar extract &lt;span class="nt"&gt;--layers&lt;/span&gt; &lt;span class="nt"&gt;--destination&lt;/span&gt; extracted

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.18&lt;/span&gt;
&lt;span class="c"&gt;# Create appuser&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; appuser &lt;span class="nt"&gt;-G&lt;/span&gt; appgroup
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /application&lt;/span&gt;
&lt;span class="c"&gt;# Copy custom JRE&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=jre-build /customjre /opt/jre&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_HOME=/opt/jre&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PATH:$JAVA_HOME/bin"&lt;/span&gt;
&lt;span class="c"&gt;# Copy layered jar contents&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/spring-boot-loader/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/snapshot-dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=extractor /extract/extracted/application/ ./&lt;/span&gt;
&lt;span class="c"&gt;# Set permissions&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /application
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "application.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Jib: Construindo Imagens Java Sem Dockerfile
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;Jib&lt;/strong&gt; é um plugin desenvolvido pelo Google que tem como objetivo simplificar a criação de imagens de container para aplicações Java. Ele está disponível tanto para &lt;strong&gt;Maven&lt;/strong&gt; quanto para &lt;strong&gt;Gradle&lt;/strong&gt; e foi projetado especificamente para projetos baseados na JVM.&lt;/p&gt;

&lt;p&gt;Uma das principais características do Jib é permitir a construção da imagem sem a necessidade de escrever um Dockerfile.&lt;/p&gt;

&lt;p&gt;Além disso, o Jib não depende de um Docker Daemon para funcionar. Ele consegue construir e publicar imagens diretamente em registries externos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buildpacks: Containers Inteligentes sem Dockerfile
&lt;/h2&gt;

&lt;p&gt;Os &lt;strong&gt;Buildpacks&lt;/strong&gt; surgiram inicialmente no Heroku e, depois, evoluíram para um projeto mantido pela CNCF (Cloud Native Computing Foundation). O principal objetivo dessa tecnologia é automatizar a criação de imagens de container para diferentes stacks.&lt;/p&gt;

&lt;p&gt;Ao utilizar Buildpacks, não é necessário escrever um Dockerfile. A ferramenta identifica a stack utilizada e seleciona os componentes mais adequados para gerar uma imagem otimizada, seguindo boas práticas de segurança e performance.&lt;/p&gt;

&lt;p&gt;Os Buildpacks dependem de um Docker Daemon para funcionar. &lt;/p&gt;

&lt;p&gt;No ecossistema Spring, essa abordagem é ainda mais simples. Tanto o plugin do Maven quanto o do Gradle oferecem um target específico para construir a imagem do container, que internamente utiliza Buildpacks.&lt;/p&gt;

&lt;p&gt;Por exemplo, no Maven, é possível gerar a imagem com um comando como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn spring-boot:build-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando utiliza Buildpacks por padrão.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações Finais
&lt;/h2&gt;

&lt;p&gt;Neste artigo, exploramos diferentes abordagens para a construção de containers para aplicações Java, desde Dockerfiles simples até estratégias mais avançadas e ferramentas automatizadas como Jib e Buildpacks.&lt;/p&gt;

&lt;p&gt;Cada uma dessas opções possui vantagens e não existe uma solução única que seja ideal para todos os cenários.&lt;/p&gt;

&lt;p&gt;Uma das boas práticas mais importante é configurar a execução da aplicação com um usuário que possua apenas as permissões necessárias, evitando o uso do usuário root.&lt;/p&gt;

&lt;p&gt;Para ver um exemplo prático dessas abordagens aplicadas em um projeto real, você pode consultar o repositório no GitHub: &lt;a href="https://github.com/denis-arruda/taskmanager/tree/spring" rel="noopener noreferrer"&gt;TaskManager Spring Boot&lt;/a&gt;. Nele estão disponíveis diferentes configurações de Dockerfiles seguindo os modos explicados neste artigo.&lt;/p&gt;

&lt;p&gt;Agora, experimente aplicar essas técnicas no seu projeto. Teste a criação de uma JRE customizada com jdeps e jlink e observe o quanto o tamanho da imagem é reduzido. Experimente também a extração de camadas do Spring Boot e verifique se o tempo de inicialização da aplicação se torna mais rápido. Depois, compartilhe seus resultados, comparações e aprendizados nos comentários. Essa troca de experiências é fundamental para evoluirmos juntos como comunidade.&lt;/p&gt;

&lt;p&gt;À medida que o projeto cresce e os requisitos mudam, é importante revisitar essas decisões e buscar constantemente melhorias no processo de build e entrega. Investir tempo nessas práticas é investir diretamente na qualidade, confiabilidade e sustentabilidade do software.&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>java</category>
      <category>docker</category>
      <category>containers</category>
    </item>
  </channel>
</rss>
