<?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>DEV Community: DaShaun</title>
    <description>The latest articles on DEV Community by DaShaun (@dashaun).</description>
    <link>https://dev.to/dashaun</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51700%2F045fb338-fc52-4b39-8120-82993aa24647.jpg</url>
      <title>DEV Community: DaShaun</title>
      <link>https://dev.to/dashaun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dashaun"/>
    <language>en</language>
    <item>
      <title>Two DGX Sparks, One Model: DeepSeek-V4-Flash with a 1M-Token Context</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 15 Sep 2026 17:09:15 +0000</pubDate>
      <link>https://dev.to/dashaun/two-dgx-sparks-one-model-deepseek-v4-flash-with-a-1m-token-context-2e0a</link>
      <guid>https://dev.to/dashaun/two-dgx-sparks-one-model-deepseek-v4-flash-with-a-1m-token-context-2e0a</guid>
      <description>&lt;p&gt;I got some new hardware.&lt;/p&gt;

&lt;p&gt;Two NVIDIA DGX Sparks. I connected them to each other with two QSFP cables, and put both of them on my Tailscale network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq15uhs4uxvlgx6w77vx9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq15uhs4uxvlgx6w77vx9.jpg" alt="Both Sparks on the bench, joined by the two QSFP cables" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A single Spark has 128 GB of memory, shared between the CPU and the GPU. That is a lot of memory. It is also not the point. I did not buy two of them to run two copies of the same small model. I wanted to run something that &lt;strong&gt;needed both nodes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;NVIDIA even has a &lt;a href="https://build.nvidia.com/spark/vllm/multi-node" rel="noopener noreferrer"&gt;playbook for running vLLM across two Sparks&lt;/a&gt;. Follow the steps, pick a big model, done.&lt;/p&gt;

&lt;p&gt;Easy, right?&lt;/p&gt;

&lt;p&gt;Well, eventually.&lt;/p&gt;

&lt;p&gt;I got it working. DeepSeek-V4-Flash is running across both Sparks with the full 1,048,576-token context. It was not as easy as I hoped, so I made a repo to keep track of what I did: &lt;a href="https://github.com/dashaun/deepseek-v4-flash-2x-dgx-spark" rel="noopener noreferrer"&gt;&lt;code&gt;dashaun/deepseek-v4-flash-2x-dgx-spark&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is the story behind that repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules I set for myself
&lt;/h2&gt;

&lt;p&gt;Before I started, I decided on a few constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;vLLM and Ray run in Docker containers.&lt;/strong&gt; Nothing gets installed on the Spark hosts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The head node&lt;/strong&gt; is one Spark, and &lt;strong&gt;the worker node&lt;/strong&gt; is the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model has to need both nodes.&lt;/strong&gt; Tensor parallelism across the two GPUs, over the QSFP link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything is driven from my Mac&lt;/strong&gt; over SSH. No logging in to each node and typing commands in two terminals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did this work in a long session with &lt;a href="https://claude.com/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; driving the terminal, while I made the decisions. That turned out to be a good fit for this kind of project: lots of reading docs, checking logs, and trying the next thing. It also meant that when something failed, we stopped and figured out &lt;em&gt;why&lt;/em&gt; before changing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the model
&lt;/h2&gt;

&lt;p&gt;My first instinct was "the latest DeepSeek."&lt;/p&gt;

&lt;p&gt;The latest one, DeepSeek-V4.1-Flash, needs about 614 GB of memory at its released precision. The only full-weight DGX Spark setup I could find uses &lt;strong&gt;four&lt;/strong&gt; Sparks. There is a community 2.9-bit quantization that squeezes it onto two, but that means a different base image and very aggressive quantization.&lt;/p&gt;

&lt;p&gt;So I settled on &lt;strong&gt;DeepSeek-V4-Flash&lt;/strong&gt; (the 0731 release): a 284B-parameter mixture-of-experts model, 13B active, about 156 GB of weights. Too big for one Spark. Just right for two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the container image
&lt;/h2&gt;

&lt;p&gt;The NVIDIA playbook uses the NGC vLLM container. My plan was to use the newest NGC image.&lt;/p&gt;

&lt;p&gt;It did not take long to find out that nobody runs DeepSeek V4 on a DGX Spark with the stock images. The GB10 (compute capability 12.1) needs kernels that just are not in them.&lt;/p&gt;

&lt;p&gt;The community has done a lot of work here. I went with &lt;a href="https://github.com/eugr/spark-vllm-docker" rel="noopener noreferrer"&gt;eugr/spark-vllm-docker&lt;/a&gt; and its &lt;code&gt;spark-vllm-b12x&lt;/code&gt; image, which has a recipe for exactly this model on two Sparks. I pinned the image by digest, so both nodes always run identical bits.&lt;/p&gt;

&lt;p&gt;eugr's tooling defaults to running without Ray. I wanted Ray, and I wanted nothing copied onto the hosts, so I wrote my own &lt;code&gt;start.sh&lt;/code&gt;, &lt;code&gt;stop.sh&lt;/code&gt; and &lt;code&gt;status.sh&lt;/code&gt; that drive both nodes over SSH. They run &lt;code&gt;docker&lt;/code&gt; commands on each Spark and install nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two cables, four links
&lt;/h2&gt;

&lt;p&gt;Here is something I did not expect: two QSFP cables show up as &lt;strong&gt;four&lt;/strong&gt; RoCE devices on each Spark.&lt;/p&gt;

&lt;p&gt;Each physical port appears twice, once for each of the GB10's PCIe connections. So &lt;code&gt;rocep1s0f0&lt;/code&gt; and &lt;code&gt;roceP2p1s0f0&lt;/code&gt; are the same cable.&lt;/p&gt;

&lt;p&gt;That matters, because NCCL has to be told which devices to use. I ended up writing &lt;code&gt;network.sh&lt;/code&gt;, which reads everything from both nodes (ports, devices, IPs, MTU, the RoCE v2 GID index) and checks it. It doesn't change any host settings; it just tells you what you have and whether it's right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Fnetwork-verify.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Fnetwork-verify.svg" alt="Output of ./network.sh verify: four QSFP links at 200G with MTU 9000, RoCE ACTIVE, and every check passing. Hostnames and paths are blurred." width="1159" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all four devices in use, an NCCL all-reduce between the nodes measured &lt;strong&gt;145–185 Gbit/s&lt;/strong&gt;, over RDMA, not TCP. Later load tests showed traffic split evenly across all four devices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Froce-balance.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Froce-balance.svg" alt="RoCE traffic per device during a long-prompt load test: about 166 GB on each of the four devices, two per QSFP port" width="720" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It works! (the first time)
&lt;/h2&gt;

&lt;p&gt;The first full start took about four and a half minutes: load the weights (156 GB total, split across both nodes), compile, capture CUDA graphs. Then the API came up.&lt;/p&gt;

&lt;p&gt;vLLM's &lt;code&gt;--max-model-len auto&lt;/code&gt; picked the full 1M-token context. A single request ran at around 41–52 tokens per second, and eight concurrent requests reached 88 tokens per second in total.&lt;/p&gt;

&lt;p&gt;I thought I was done.&lt;/p&gt;

&lt;h2&gt;
  
  
  It doesn't work (the second time)
&lt;/h2&gt;

&lt;p&gt;Before sharing anything, I wanted to prove the whole thing survives a clean stop and start.&lt;/p&gt;

&lt;p&gt;It did not.&lt;/p&gt;

&lt;p&gt;The second start failed while warming up the model, with an assertion deep in vLLM's DeepSeek-V4 attention code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;active_topk_width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_seq_len&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compress_ratio&lt;/span&gt;
&lt;span class="nb"&gt;AssertionError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turned out this start had about 3 GiB less memory available for the KV cache than the first one. So &lt;code&gt;auto&lt;/code&gt; quietly shrank the context from 1,048,576 to 937,472 tokens, and part of the attention code still assumed the full length.&lt;/p&gt;

&lt;p&gt;Same machines. Same image. Same settings. Different result.&lt;/p&gt;

&lt;p&gt;I pinned the context at 524,288 tokens and it started cleanly. Half the context, but stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OS update surprise
&lt;/h2&gt;

&lt;p&gt;Then I ran &lt;code&gt;sudo apt update&lt;/code&gt; and &lt;code&gt;sudo apt upgrade -y&lt;/code&gt; and rebooted both nodes.&lt;/p&gt;

&lt;p&gt;The upgrade pulled in DGX OS OTA 7.6.0 and a new kernel: 7.0 instead of 6.17. Everything below the kernel stayed the same (driver, ConnectX firmware), and all four links came back healthy.&lt;/p&gt;

&lt;p&gt;A reboot also taught me that nothing restarts on its own. The containers are left "Exited", and &lt;code&gt;./stop.sh &amp;amp;&amp;amp; ./start.sh&lt;/code&gt; brings everything back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chasing the 1M context
&lt;/h2&gt;

&lt;p&gt;I really wanted the full context back, so I pinned &lt;code&gt;--max-model-len 1048576&lt;/code&gt; and tried again.&lt;/p&gt;

&lt;p&gt;This time vLLM refused to start. The KV cache needed 10.06 GiB, and it had 10.03 GiB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30 MiB short.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix I reached for was to give vLLM a bigger share of memory, so I raised &lt;code&gt;--gpu-memory-utilization&lt;/code&gt; from 0.85 to 0.87.&lt;/p&gt;

&lt;p&gt;That froze &lt;strong&gt;both&lt;/strong&gt; Sparks.&lt;/p&gt;

&lt;p&gt;SSH stopped answering. Ping went from under a millisecond to several seconds. After 30 minutes of waiting, they needed a hard power cycle. The kernel logs from that boot showed endless "Under memory pressure" messages right up until they stopped. I am just glad nothing got corrupted.&lt;/p&gt;

&lt;p&gt;That was the most important lesson of the whole project: &lt;strong&gt;on a DGX Spark, the GPU and the operating system share the same memory.&lt;/strong&gt; A memory setting for the GPU is also a memory setting for the OS. At 0.87, vLLM gave the KV cache 16.6 GiB and left the machines nothing to breathe with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: stop letting it guess
&lt;/h2&gt;

&lt;p&gt;After the power cycle, I looked at what else was running on each node. The desktop (GDM, GNOME, Xorg) was using about 0.8 GB, so I turned it off. I will never use a desktop on these machines.&lt;/p&gt;

&lt;p&gt;But freeing host memory was not the real fix. The real problem was that vLLM &lt;strong&gt;measures&lt;/strong&gt; how much memory it can use for the KV cache at startup, and that measurement moved around a lot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Fkv-cache-per-start.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Fkv-cache-per-start.svg" alt="KV cache memory per start: 12.84, 9.66, 14.35 and 10.03 GiB at utilization 0.85, 16.6 GiB at 0.87 which froze both nodes, and a fixed 11 GiB that fits the 10.06 GiB a 1M-token request needs" width="720" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Five starts, same settings, anywhere from 9.66 to 16.6 GiB. The 1M context fit on some of them and not on others.&lt;/p&gt;

&lt;p&gt;vLLM has a flag for this: &lt;code&gt;--kv-cache-memory-bytes&lt;/code&gt;. Instead of measuring, it uses exactly the size you give it. I set it to &lt;code&gt;11G&lt;/code&gt;, enough for one full 1M-token request plus about 9% headroom, and well below the sizes that had run stably.&lt;/p&gt;

&lt;p&gt;Every start now behaves the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did the 1M context actually work?
&lt;/h2&gt;

&lt;p&gt;Configured is not the same as working, so I tested it with real long prompts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;129K-token prompts:&lt;/strong&gt; about 63 seconds to the first token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four 129K-token prompts at once:&lt;/strong&gt; the slowest waited 228 seconds. The prompts are processed one after another, so the waits stack up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 904,911-token prompt:&lt;/strong&gt; 12.2 minutes to the first token, then 42 tokens per second. No errors, and the server stayed healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Fttft.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdashaun.com%2Fposts%2Fdeepseek-v4-flash-2x-dgx-spark%2Fimages%2Fttft.svg" alt="Time to first token for long prompts: 62.6 s for one 129K-token prompt, 114.6 s and 227.7 s for the slowest of 2 and 4 concurrent prompts, and 729 s for one 905K-token prompt" width="720" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Free memory on both hosts stayed above 8 GB the whole time.&lt;/p&gt;

&lt;p&gt;The full 1M-token context works end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I made a repo
&lt;/h2&gt;

&lt;p&gt;By the end I had scripts, a network checker, a load tester, a memory-sizing rule, and a list of things that had bitten me. Next time I rebuild these nodes, or someone else tries the same thing, I don't want to rediscover all of it.&lt;/p&gt;

&lt;p&gt;So I wrote it down: &lt;a href="https://github.com/dashaun/deepseek-v4-flash-2x-dgx-spark" rel="noopener noreferrer"&gt;&lt;code&gt;dashaun/deepseek-v4-flash-2x-dgx-spark&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;start.sh&lt;/code&gt;, &lt;code&gt;stop.sh&lt;/code&gt;, &lt;code&gt;status.sh&lt;/code&gt;:&lt;/strong&gt; bring both nodes up and down from one machine, with nothing installed on the hosts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;network.sh&lt;/code&gt;:&lt;/strong&gt; discover and verify the QSFP/RoCE setup, and write the right settings into the config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;loadtest.py&lt;/code&gt;:&lt;/strong&gt; concurrency and long-prompt tests, with per-cable RoCE traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A README&lt;/strong&gt; with the measured results, the memory-sizing rules, troubleshooting for every failure above, and links to all the sources and community projects I leaned on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is &lt;strong&gt;experimental&lt;/strong&gt;. It was tested on one pair of Sparks, it depends on a community container image with a vLLM development build, and the API has no authentication. The README says all of that up front.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two QSFP cables are four RoCE devices.&lt;/strong&gt; Use all four, and verify them before you blame the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The newest model is not always the right model.&lt;/strong&gt; Start from what fits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stock images are not enough for new hardware.&lt;/strong&gt; The community builds the good stuff first. Use it, credit it, and pin it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test a clean restart before you celebrate.&lt;/strong&gt; My first success hid a problem that only showed up on the second start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On unified memory, GPU memory settings are OS memory settings.&lt;/strong&gt; Be conservative, and don't nudge them "just a little" on a live system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't let the runtime guess when you can tell it.&lt;/strong&gt; A fixed KV cache size turned "sometimes works" into "always works."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write it down.&lt;/strong&gt; That is what the repo is for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a pair of DGX Sparks, give it a try, and let me know what breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on how this post was edited
&lt;/h2&gt;

&lt;p&gt;I finished editing this blog post using &lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; and DeepSeek V4 Flash, running on the exact setup this post describes.&lt;/p&gt;

&lt;p&gt;DeepSeek V4 Flash and OpenCode work so well together that I dropped my two $200/month subscriptions (Claude and Codex) and switched to a $200/year subscription and a $20/month subscription instead. The savings are real, and I'm getting better results.&lt;/p&gt;

&lt;p&gt;In about two years, this will have paid for itself. Until then, I'm going to be token-maxing to get the most bang for the buck.&lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dashaun/deepseek-v4-flash-2x-dgx-spark" rel="noopener noreferrer"&gt;dashaun/deepseek-v4-flash-2x-dgx-spark&lt;/a&gt; — the repo with the scripts, network checker, load tester, and notes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>deepseek</category>
      <category>ai</category>
      <category>llm</category>
      <category>nvidia</category>
    </item>
    <item>
      <title>Spring AI + AGENTS.md Repository Steward</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 01 Sep 2026 21:56:22 +0000</pubDate>
      <link>https://dev.to/dashaun/spring-ai-agentsmd-repository-steward-2ol0</link>
      <guid>https://dev.to/dashaun/spring-ai-agentsmd-repository-steward-2ol0</guid>
      <description>&lt;p&gt;I have been having a lot of fun building &lt;a href="https://github.com/dashaun/spring-ai-agents-md" rel="noopener noreferrer"&gt;&lt;code&gt;spring-ai-agents-md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let me back up.&lt;/p&gt;

&lt;p&gt;I had two motivations for creating this starter.&lt;/p&gt;

&lt;p&gt;First, I am an &lt;a href="https://aaif.io/community/ambassadors/" rel="noopener noreferrer"&gt;AAIF Ambassador&lt;/a&gt;. I needed to do some Ambassador'ing. I wanted to make a public contribution that tied directly to an AAIF project and helped developers understand it, try it, and build something useful with it. &lt;code&gt;AGENTS.md&lt;/code&gt; was the obvious choice for me.&lt;/p&gt;

&lt;p&gt;Second, I am building agents with Spring AI. I want to be able to build my own "Claude" or "Codex" with Spring AI, and I am getting closer all the time.&lt;/p&gt;

&lt;p&gt;I work with the popular coding agents. I am also building my own agents and agent harnesses. I do not want a completely different instruction format for each one. &lt;code&gt;AGENTS.md&lt;/code&gt; is a simple, open specification that works across those worlds. The same repository instructions can help the agents people already use and the agents I am creating with Spring AI.&lt;/p&gt;

&lt;p&gt;I really like that.&lt;/p&gt;

&lt;p&gt;The idea seemed simple enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agents.md" rel="noopener noreferrer"&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt; gives coding agents a predictable place to find the instructions for a repository. &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring AI&lt;/a&gt; gives Java developers a consistent way to build AI applications. I wanted to bring the two together.&lt;/p&gt;

&lt;p&gt;Put an &lt;code&gt;AGENTS.md&lt;/code&gt; file in a Spring project. Add a starter. Build a normal Spring AI &lt;code&gt;ChatClient&lt;/code&gt;. Let the agent get the right repository instructions automatically.&lt;/p&gt;

&lt;p&gt;Easy, right?&lt;/p&gt;

&lt;p&gt;Well, mostly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Make the simple thing simple
&lt;/h2&gt;

&lt;p&gt;The first goal was a Spring Boot starter that loads an &lt;code&gt;AGENTS.md&lt;/code&gt; document and adds it to Spring AI requests through a &lt;code&gt;CallAdvisor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I did not want to invent a schema. An &lt;code&gt;AGENTS.md&lt;/code&gt; file is Markdown. People should be able to organize it in whatever way works for their project. The library preserves the complete document, including headings, lists, tables, code blocks, whitespace, and line endings.&lt;/p&gt;

&lt;p&gt;The starter handles the wiring. This is the dependency:&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;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.spring-ai-community&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;spring-ai-starter-agents-md&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.0.1-SNAPSHOT&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you build the &lt;code&gt;ChatClient&lt;/code&gt; like you normally would.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auto-configuration attaches the advisor to the auto-configured builder. There is no special advisor registration required in the application.&lt;/p&gt;

&lt;p&gt;That part felt really good. It was the Spring experience I wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precedence was the hard part
&lt;/h2&gt;

&lt;p&gt;Loading one file was not the biggest struggle. Precedence was.&lt;/p&gt;

&lt;p&gt;What happens when a repository has more than one &lt;code&gt;AGENTS.md&lt;/code&gt; file?&lt;/p&gt;

&lt;p&gt;Which instructions apply to a file deep inside a project? Does the closest document replace the root document, or do the instructions accumulate? What happens when two documents disagree? What happens after a filesystem tool moves the agent into another part of the repository?&lt;/p&gt;

&lt;p&gt;I did not want the answers to be whatever felt right to me that day.&lt;/p&gt;

&lt;p&gt;Luckily, the proposed &lt;a href="https://github.com/agentsmd/agents.md/issues/135" rel="noopener noreferrer"&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; v1.1 clarification&lt;/a&gt; arrived at the right time. It put names around the behavior I needed: jurisdiction, accumulation, precedence, and implicit inheritance.&lt;/p&gt;

&lt;p&gt;The rules are intuitive once they are written down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;AGENTS.md&lt;/code&gt; file applies to its directory and everything below it.&lt;/li&gt;
&lt;li&gt;Applicable guidance accumulates from ancestor directories.&lt;/li&gt;
&lt;li&gt;The closest instructions take precedence when guidance conflicts.&lt;/li&gt;
&lt;li&gt;Explicit user instructions override file-based instructions.&lt;/li&gt;
&lt;li&gt;Instructions from sibling directories do not apply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The proposal is still a proposal, and the library says that clearly. But it gave me a solid target to implement and test instead of leaving the most important behavior implicit.&lt;/p&gt;

&lt;p&gt;For a target file, &lt;code&gt;spring-ai-agents-md&lt;/code&gt; walks toward the repository root, collects the applicable documents, and supplies them broadest first. Each complete document is kept intact inside a small framework-owned context envelope that explains the precedence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repository/AGENTS.md
        |
        v
repository/examples/AGENTS.md
        |
        v
repository/examples/steward/AGENTS.md
        |
        v
repository/examples/steward/src/RepositoryTools.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That final file gets the accumulated guidance, with the closest applicable instructions winning when there is a conflict.&lt;/p&gt;

&lt;p&gt;When an application already knows the target, it can be explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;advisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentsMdAdvisorParams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/main/java/com/example/Example.java"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filesystem tools can also propagate the path they accessed. The advisor resolves the instructions again on the next pass through the tool loop. That is the part that made the final example possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  I wanted to be able to see what was happening
&lt;/h2&gt;

&lt;p&gt;I really wanted observability in this project.&lt;/p&gt;

&lt;p&gt;When an agent gives a surprising answer, I need to be able to debug it. Did the advisor find an &lt;code&gt;AGENTS.md&lt;/code&gt; file? Did it find one document or several? Did discovery stop at a safety limit? How much context did I add to the prompt?&lt;/p&gt;

&lt;p&gt;Without that feedback, every problem starts to look like a model problem.&lt;/p&gt;

&lt;p&gt;At the same time, an &lt;code&gt;AGENTS.md&lt;/code&gt; file can contain repository details that do not belong in logs, metric tags, or exception messages. I wanted useful visibility without copying the instructions into another system.&lt;/p&gt;

&lt;p&gt;The starter uses Spring Boot's normal logging configuration. At DEBUG, it reports the selected resource location and character count. It never writes the contents of &lt;code&gt;AGENTS.md&lt;/code&gt; to the logs.&lt;/p&gt;

&lt;p&gt;When an &lt;code&gt;ObservationRegistry&lt;/code&gt; is available, the advisor emits a focused Micrometer observation around the local prompt augmentation step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring.ai.agents.md.advisor

document.state      = present | empty
document.count      = zero | one | multiple
resolution.outcome  = complete | depth-limit | document-limit | size-limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The observation does not wrap the model call or duplicate the observations that Spring AI already provides. A separate &lt;code&gt;spring.ai.agents.md.context.size&lt;/code&gt; distribution summary records how many characters were added to the system prompt. The tags stay low-cardinality. Document contents and resource paths are never used as tags.&lt;/p&gt;

&lt;p&gt;I also wanted sane defaults. A repository should not be able to add an unlimited number of files, or an unlimited amount of text, to every model request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Directories inspected&lt;/td&gt;
&lt;td&gt;&lt;code&gt;32&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents composed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size of one document&lt;/td&gt;
&lt;td&gt;&lt;code&gt;64KB&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total composed context&lt;/td&gt;
&lt;td&gt;&lt;code&gt;256KB&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those limits are configurable. Invalid values fail during configuration instead of surprising you later. An oversized document is skipped whole, never silently truncated. When the total-size or document-count limit is reached, composition stops while keeping the accepted documents in broadest-to-closest order.&lt;/p&gt;

&lt;p&gt;Most importantly, the application gets feedback when that happens. The advisor publishes an &lt;code&gt;AgentsMdLimitReachedEvent&lt;/code&gt; with the normalized target, the limit that was reached, the number of documents accepted, the context size, and the configured limit. It does not contain the document contents.&lt;/p&gt;

&lt;p&gt;An interactive application can turn that event into immediate, useful feedback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EventListener&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;warnAboutAgentsMdLimit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AgentsMdLimitReachedEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"AGENTS.md %s reached for %s; %d documents were applied.%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;acceptedDocumentCount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction matters to me. A safety limit should not quietly change what the model knows. If a limit changes the context, I want to know about it. I want enough information to fix the configuration or the repository without exposing the instructions I was trying to protect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Prove it with a coding agent
&lt;/h2&gt;

&lt;p&gt;For Phase 2, I wanted something more convincing than a unit test and a chat endpoint.&lt;/p&gt;

&lt;p&gt;I built the &lt;a href="https://github.com/dashaun/spring-ai-agents-md/tree/main/examples/spring-ai-agents-md-coding-agent" rel="noopener noreferrer"&gt;&lt;code&gt;Repository Steward&lt;/code&gt;&lt;/a&gt;, an interactive coding agent built with Spring AI and Spring Shell 4.0.3.&lt;/p&gt;

&lt;p&gt;The steward can list, search, and read files inside a bounded repository workspace. As it moves through the repository, its filesystem tools propagate the active path. The &lt;code&gt;AgentsMdSystemAdvisor&lt;/code&gt; refreshes the applicable instructions for that path.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Shell `steward` command
            |
            v
Spring AI ChatClient + tool loop
            |
            +--&amp;gt; AgentsMdSystemAdvisor
            |       refreshes instructions for the active path
            |
            +--&amp;gt; listFiles / searchFiles / readFile
            |
            +--&amp;gt; proposePatch --&amp;gt; pending ChangeProposal
                                      |
                         show-change / apply-change
                                      |
                                      v
                              atomic filesystem write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can explore the repository and propose a patch. It cannot approve its own change. &lt;code&gt;apply-change&lt;/code&gt; is a Spring Shell command, not a model tool. A person has to review the proposal and run the command.&lt;/p&gt;

&lt;p&gt;The safety boundaries are implemented in Java, not trusted to the prompt.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paths must remain inside the configured workspace.&lt;/li&gt;
&lt;li&gt;Absolute paths and &lt;code&gt;..&lt;/code&gt; escapes are rejected.&lt;/li&gt;
&lt;li&gt;Symbolic links and protected directories such as &lt;code&gt;.git&lt;/code&gt; and &lt;code&gt;target&lt;/code&gt; are rejected.&lt;/li&gt;
&lt;li&gt;Reads, searches, file sizes, depth, and result counts are bounded.&lt;/li&gt;
&lt;li&gt;A patch must replace one exact, unique piece of text.&lt;/li&gt;
&lt;li&gt;Each proposal stores a digest of the file that was reviewed.&lt;/li&gt;
&lt;li&gt;The digest is checked again immediately before the atomic write.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the file changed after the proposal was reviewed, the proposal is stale and the write is refused.&lt;/p&gt;

&lt;p&gt;That is one of my favorite parts of the demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was the final example necessary?
&lt;/h2&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Was it too much?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;The Repository Steward turned a focused Spring Boot starter into a real CLI coding agent, with filesystem containment, proposal storage, diffs, atomic writes, stale-change protection, Spring Shell commands, JLine, and a lot more tests.&lt;/p&gt;

&lt;p&gt;It also forced the model to do something that sounds simple but is not. The model has to inspect a file, preserve exact text across multiple calls, invoke a structured tool with the right arguments, process the result, and return to the conversation.&lt;/p&gt;

&lt;p&gt;I tried a lot of models.&lt;/p&gt;

&lt;p&gt;Some stopped after reading the file. Some printed JSON that looked like a tool call instead of calling the tool. Some invented a proposal ID. One changed the registered &lt;code&gt;readFile&lt;/code&gt; tool name to &lt;code&gt;read&lt;/code&gt;, which Spring AI correctly rejected. A model advertising tool support does not mean it is good at multi-step structured tool calling.&lt;/p&gt;

&lt;p&gt;At first, I wondered if my laptop was the problem. So I ran the same workflow against larger hardware and more models. The hardware helped with speed. It did not turn every model into a reliable coding agent.&lt;/p&gt;

&lt;p&gt;That was humbling, but useful.&lt;/p&gt;

&lt;p&gt;The example might be more than the starter needed. It is also the best proof that the starter works. It shows AGENTS.md instructions changing as an agent moves through a repository. It shows Spring AI driving a recursive tool loop. It shows the difference between instructions for a model and safety boundaries enforced by an application.&lt;/p&gt;

&lt;p&gt;It also made one requirement very clear: use a model that is good at multi-step structured tool calling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the Repository Steward
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://youtu.be/bIlYmp_iiBM" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ivs8u68c1qk7tb2l6b5.jpg" alt="Repository Steward demo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The video is less than one minute. It shows the steward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirming its bounded workspace.&lt;/li&gt;
&lt;li&gt;Discovering the &lt;code&gt;AGENTS.md&lt;/code&gt; files that apply to a nested Java file.&lt;/li&gt;
&lt;li&gt;Inspecting the implementation before answering a question.&lt;/li&gt;
&lt;li&gt;Creating a pending patch without changing the file.&lt;/li&gt;
&lt;li&gt;Showing the proposed diff.&lt;/li&gt;
&lt;li&gt;Applying it only after explicit approval.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the story I wanted the example to tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think this matters
&lt;/h2&gt;

&lt;p&gt;I am building my own agents and agent harnesses with Spring AI. I am not sure how many other people are doing the same thing yet.&lt;/p&gt;

&lt;p&gt;I do know that I want the instructions for those agents to live with the code. I want the format to be open. I want nested projects to provide focused context without copying everything from the repository root. I want the behavior to be easy to explain. I want the Spring integration to feel like Spring.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;spring-ai-agents-md&lt;/code&gt; is small, but I think it is really valuable.&lt;/p&gt;

&lt;p&gt;This has been really fun. I am really excited about this project. The tests are green, the repository is public, the example is documented, and the demo is recorded.&lt;/p&gt;

&lt;p&gt;Now that the blog is done, let's see if I can get this pulled into &lt;a href="https://github.com/spring-ai-community" rel="noopener noreferrer"&gt;&lt;code&gt;spring-ai-community&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have put a lot of work into it. I am ready for feedback, but I am still nervous.&lt;/p&gt;

&lt;p&gt;That probably means it is time to share it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/spring-ai-agents-md" rel="noopener noreferrer"&gt;&lt;code&gt;spring-ai-agents-md&lt;/code&gt; repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/spring-ai-agents-md/tree/main/examples/spring-ai-agents-md-coding-agent" rel="noopener noreferrer"&gt;Repository Steward example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/bIlYmp_iiBM" rel="noopener noreferrer"&gt;Repository Steward video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://agents.md" rel="noopener noreferrer"&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/agentsmd/agents.md/issues/135" rel="noopener noreferrer"&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; v1.1 clarification proposal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aaif.io/" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springai</category>
      <category>java</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Multi-Architecture Spring OCI from anywhere with Paketo</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 17 Dec 2024 17:33:00 +0000</pubDate>
      <link>https://dev.to/dashaun/multi-architecture-spring-oci-from-anywhere-with-paketo-1ic2</link>
      <guid>https://dev.to/dashaun/multi-architecture-spring-oci-from-anywhere-with-paketo-1ic2</guid>
      <description>&lt;h2&gt;
  
  
  Multi-Architecture Spring OCI from anywhere with Paketo
&lt;/h2&gt;

&lt;p&gt;My favorite feature of Spring Boot 3.4.0 is the adoption of multi-architecture buildpacks.&lt;br&gt;
This feature empowers you to construct native images and traditional JVM-based applications for both AMD64 and ARM64, from a unified build process.&lt;br&gt;
The fact that OCI images will also be smaller because of the new default buildpack is a bonus.&lt;/p&gt;
&lt;h3&gt;
  
  
  Emotionally Invested
&lt;/h3&gt;

&lt;p&gt;I've been building native images for ARM64 since 2021, because I'm a Raspberry Pi enthusiast and passionate about Spring Boot.&lt;br&gt;
Since that time, I've created dozens of solutions, to put production-ready, enterprise-grade, Spring Boot on Raspberry Pi ARM64 devices with only 512MB of RAM.&lt;br&gt;
I no longer have to support my own solution.&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;At least Java 17&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Docker Desktop Configuration
&lt;/h3&gt;

&lt;p&gt;Docker Desktop comes with built-in QEMU support for multi-architecture builds. However, if you need to install QEMU manually, you have two options:&lt;/p&gt;
&lt;h4&gt;
  
  
  Option 1: Using tonistiigi/binfmt
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install QEMU support for all architectures&lt;/span&gt;
docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; tonistiigi/binfmt &lt;span class="nt"&gt;--install&lt;/span&gt; all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;This command was found here: &lt;a href="https://docs.docker.com/build/building/multi-platform/" rel="noopener noreferrer"&gt;https://docs.docker.com/build/building/multi-platform/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Option 2: Using multiarch/qemu-user-static
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install and configure QEMU&lt;/span&gt;
docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; multiarch/qemu-user-static &lt;span class="nt"&gt;--reset&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;More information can be found here: &lt;a href="https://github.com/multiarch/qemu-user-static" rel="noopener noreferrer"&gt;https://github.com/multiarch/qemu-user-static&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Spring Boot 3.4.0 application
&lt;/h3&gt;

&lt;p&gt;First, let's create a new Spring Boot application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;mydemo
&lt;span class="nb"&gt;cd &lt;/span&gt;mydemo
curl https://start.spring.io/starter.tgz &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web,actuator &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;maven-project | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvzf&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You could also use &lt;a href="https://start.spring.io/#!type=maven-project&amp;amp;language=java&amp;amp;platformVersion=3.4.0&amp;amp;packaging=jar&amp;amp;jvmVersion=23&amp;amp;groupId=com.example&amp;amp;artifactId=demo&amp;amp;name=demo&amp;amp;description=Demo%20project%20for%20Spring%20Boot&amp;amp;packageName=com.example.demo&amp;amp;dependencies=web,actuator" rel="noopener noreferrer"&gt;https://start.spring.io&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create a multi-architecture OCI Image
&lt;/h3&gt;

&lt;p&gt;Here's the step-by-step process to build and publish multi-architecture images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build ARM64 image&lt;/span&gt;
./mvnw spring-boot:build-image &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imagePlatform&lt;span class="o"&gt;=&lt;/span&gt;linux/arm64 &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imageName&lt;span class="o"&gt;=&lt;/span&gt;dashaun/blog-3-4-0:arm64
docker push dashaun/blog-3-4-0:arm64

&lt;span class="c"&gt;# Build AMD64 image&lt;/span&gt;
./mvnw spring-boot:build-image &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imagePlatform&lt;span class="o"&gt;=&lt;/span&gt;linux/amd64 &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imageName&lt;span class="o"&gt;=&lt;/span&gt;dashaun/blog-3-4-0:amd64
docker push dashaun/blog-3-4-0:amd64

&lt;span class="c"&gt;# Create and push multi-architecture manifest&lt;/span&gt;
docker manifest create dashaun/blog-3-4-0:multiarch &lt;span class="nt"&gt;--amend&lt;/span&gt; dashaun/blog-3-4-0:arm64 &lt;span class="nt"&gt;--amend&lt;/span&gt; dashaun/blog-3-4-0:amd64
docker manifest push dashaun/blog-3-4-0:multiarch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inspect
&lt;/h3&gt;

&lt;p&gt;Inspect the multi-architecture manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker manifest inspect dashaun/blog-3-4-0:multiarch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Inspect the manifest, results below.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.list.v2+json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.v2+json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:96d95eed9308f86a8055156913ed8710c25917bdf0389bd6d5cf2ab0a79c77fc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amd64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.v2+json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:4c42eebe412f56a03d840d2660b9defde3820ce1c5e510ffba0067be0ae11c8e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arm64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The manifest shows both platforms &lt;code&gt;linux/amd64&lt;/code&gt; and &lt;code&gt;linux\arm64&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Looking at the &lt;a href="https://hub.docker.com/r/dashaun/blog-3-4-0/tags" rel="noopener noreferrer"&gt;Docker Hub tags&lt;/a&gt; &lt;code&gt;Digest&lt;/code&gt; sections, notice that the &lt;code&gt;sha&lt;/code&gt; values for the &lt;code&gt;arm64&lt;/code&gt; and &lt;code&gt;amd64&lt;/code&gt; tags match the &lt;code&gt;multiarch&lt;/code&gt; tag's values.&lt;/p&gt;

&lt;p&gt;The new default buildpack, &lt;a href="https://github.com/paketo-buildpacks/builder-jammy-java-tiny" rel="noopener noreferrer"&gt;builder-jammy-java-tiny&lt;/a&gt; also results in much smaller images.  Both of the images we created here are under 275mb.&lt;br&gt;
Switching to Spring Boot 3.3.6 and the previous default buildpack &lt;a href="https://github.com/paketo-buildpacks/builder-jammy-base" rel="noopener noreferrer"&gt;paketobuildpacks/builder-jammy-base&lt;/a&gt; results in a ~350mb image.&lt;/p&gt;
&lt;h3&gt;
  
  
  Building Native Images
&lt;/h3&gt;

&lt;p&gt;The process for building native images is similar, with the addition of GraalVM support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-native-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;my-native-demo
curl https://start.spring.io/starter.tgz &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web,actuator,native &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;maven-project | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvzf&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You could also use &lt;a href="https://start.spring.io/#!type=maven-project&amp;amp;language=java&amp;amp;platformVersion=3.4.0&amp;amp;packaging=jar&amp;amp;jvmVersion=23&amp;amp;groupId=com.example&amp;amp;artifactId=demo&amp;amp;name=demo&amp;amp;description=Demo%20project%20for%20Spring%20Boot&amp;amp;packageName=com.example.demo&amp;amp;dependencies=web,actuator,native" rel="noopener noreferrer"&gt;https://start.spring.io&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Building Multi-Architecture Native Images
&lt;/h3&gt;

&lt;p&gt;When the buildpack see's the &lt;code&gt;native-maven-plugin&lt;/code&gt; in the project, it creates native images with GraalVM. Our steps won't change, we will just use different tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build ARM64 native image&lt;/span&gt;
./mvnw spring-boot:build-image &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imagePlatform&lt;span class="o"&gt;=&lt;/span&gt;linux/arm64 &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imageName&lt;span class="o"&gt;=&lt;/span&gt;dashaun/blog-3-4-0:native-arm64
docker push dashaun/blog-3-4-0:native-arm64

&lt;span class="c"&gt;#Build AMD64 native image&lt;/span&gt;
./mvnw spring-boot:build-image &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imagePlatform&lt;span class="o"&gt;=&lt;/span&gt;linux/amd64 &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imageName&lt;span class="o"&gt;=&lt;/span&gt;dashaun/blog-3-4-0:native-amd64
docker push dashaun/blog-3-4-0:native-amd64

&lt;span class="c"&gt;# Create and push multi-architecture manifest&lt;/span&gt;
docker manifest create dashaun/blog-3-4-0:native-multiarch &lt;span class="nt"&gt;--amend&lt;/span&gt; dashaun/blog-3-4-0:native-arm64 &lt;span class="nt"&gt;--amend&lt;/span&gt; dashaun/blog-3-4-0:native-amd64
docker manifest push dashaun/blog-3-4-0:native-multiarch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inspect Native
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker manifest inspect dashaun/blog-3-4-0:native-multiarch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Inspect the manifest, results below.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.list.v2+json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"manifests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.v2+json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:70311015a28ff9ebbaa0ef0552e91bd85d2220716b69a8444ecf18ae542fa3f1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amd64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.v2+json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:4b9243d946a61f5fd8c9762f9e8a5ef107ba5a51a3679faa54b5a3cd96e4147c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arm64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The manifest shows both platforms &lt;code&gt;linux/amd64&lt;/code&gt; and &lt;code&gt;linux/arm64&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a look at these image sizes!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dashaun/blog-3-3-6                         latest           0e61fed5cf4a   44 years ago   348MB
dashaun/blog-3-4-0                         amd64            6ce61c588eba   44 years ago   268MB
dashaun/blog-3-4-0                         native-amd64     9f63e381e26c   44 years ago   117MB
dashaun/blog-3-4-0                         native-arm64     7a9b08bf964a   44 years ago   111MB
dashaun/blog-3-4-0                         arm64            0d07d6f15c86   44 years ago   259MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did this experiment on MacOS with ARM64 (M3 chip), but these exact same steps can be used on either ARM64 or AMD64 machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Spring Boot 3.4.0's multi-architecture support with Paketo buildpacks revolutionizes how we build and deploy applications across different architectures. Whether you're targeting cloud platforms or edge devices like Raspberry Pi, you can now build and deploy with confidence.&lt;/p&gt;

&lt;p&gt;The combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-architecture support&lt;/li&gt;
&lt;li&gt;Smaller image sizes&lt;/li&gt;
&lt;li&gt;Native image capabilities&lt;/li&gt;
&lt;li&gt;Simplified build process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Makes Spring Boot 3.4.0 a compelling choice for modern cloud-native and edge-native applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Learning
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.4-Release-Notes" rel="noopener noreferrer"&gt;Spring Boot 3.4 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/fdyj_b-Mj4c?si=MA9azUhPg1tw09Kv" rel="noopener noreferrer"&gt;YouTube with Karanbir Singh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neuw.medium.com/multi-architecture-oci-images-spring-boot-3-4-x-d8f2a0f7d6e6" rel="noopener noreferrer"&gt;Karanbir Singh's Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/paketo-buildpacks/builder-jammy-java-tiny" rel="noopener noreferrer"&gt;paketobuildpacks/builder-jammy-java-tiny&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springboot</category>
      <category>paketo</category>
      <category>buildpacks</category>
      <category>multiarch</category>
    </item>
    <item>
      <title>Spring Health Assessment Report</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Sat, 17 Feb 2024 03:11:50 +0000</pubDate>
      <link>https://dev.to/dashaun/spring-health-assessment-report-14oj</link>
      <guid>https://dev.to/dashaun/spring-health-assessment-report-14oj</guid>
      <description>&lt;p&gt;Spring Boot 3.0.0 was released 450 days ago. Helping people upgrade, and enjoy all the amazing benefits, has been one of my passions for about 450 days.  I'm a Spring Developer Advocate and I love my job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/tschuehly/status/1723106405756846117" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk94td1q501hr7dsy4q8s.png" alt="Thomas Schuehly has thoughts" width="600" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's a passion really. Thomas Schuehly gets it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At SpringOne 2023 in Las Vegas, my friend, Joachim Pasquali, over at Fiserv, shared a story that I'll not soon forget.  The process of upgrading and patching, doesn't have to be a painful process.  On that day, his flagship product, was on the latest and greatest version of Spring Boot.  &lt;a href="https://youtube.com/clip/UgkxKNJxAWtsIWDzsFJM8tDB8VvG1E66EMVk?si=0e7-mUIHfjIHl9Qp" rel="noopener noreferrer"&gt;Watch the clip that gave me all the feels here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want you to know about some tools and techniques that can help you upgrade your Spring Boot projects with more joy, frequently, and safely.&lt;/p&gt;

&lt;p&gt;First things first, let's talk about the challenges. Outdated Spring Boot versions can expose your projects to security risks, leave you without access to the latest features and performance improvements, and can be costing you more money. Have you ever been working, on an outdated JVM perhaps, or an older Spring Boot version, and secretly wished you were using the latest release? I get it, I've been there too, and I'm here to help.&lt;/p&gt;

&lt;p&gt;I want you to check out the &lt;a href="https://tanzu.vmware.com/spring-health-assessment" rel="noopener noreferrer"&gt;Spring Health Assessment Report&lt;/a&gt;.  This tool is very similar to something you probably have running already, it tells you about known vulnerabilities. However, this tool is also unique in a few very special ways.  First of all, it's free for you to use.  It also has the support timeline information built in. It provides a measure of how hard it will be to upgrade. Finally, it only reports on the Spring and Micrometer dependencies that you are using. It's very focused and actionable.&lt;/p&gt;

&lt;p&gt;I like to call &lt;a href="https://docs.openrewrite.org" rel="noopener noreferrer"&gt;OpenRewrite&lt;/a&gt; my favorite &lt;code&gt;cheat code&lt;/code&gt;. When I say that I demonstrate &lt;code&gt;OpenRewrite&lt;/code&gt; twenty times per week, I'm not exaggerating.  It's a powerful tool, and I'm still surprised by how many developers still don't know about it.  &lt;a href="https://github.com/dashaun/openrewrite-spring-boot-upgrade-example" rel="noopener noreferrer"&gt;This repository includes one of those demos, that I deliver multiple times every week&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Software is soft for a reason.  It's meant to be changed.  It's meant to be upgraded.  It's meant to be improved.  Java comes out with a new JVM, every 6-months.  The Spring team releases a new Spring Boot version every 6-months.  It's time to start upgrading more frequently.  In 2023, there were only 4-weeks, of the entire year, that the Spring team didn't release something new.  You can see for yourself over at &lt;a href="https://calendar.spring.io" rel="noopener noreferrer"&gt;calendar-dot-spring-dot-io&lt;/a&gt;.  It's time to start upgrading more frequently.&lt;/p&gt;

&lt;p&gt;I believe that the way software is going to be measured, going forward, is by how easy it is to maintain. I'm convinced that continuous upgrading, and continuous patching, is going to be a key differentiator for software projects.  I want you to get started, today.&lt;/p&gt;

&lt;p&gt;By upgrading your Spring Boot projects, you can benefit from improved security, performance, and access to the latest features. You'll also be able to take advantage of the ongoing support and updates provided by the Spring team and the Java team!&lt;/p&gt;

&lt;p&gt;So why wait? &lt;a href="https://youtu.be/XI_7mpEiPjI" rel="noopener noreferrer"&gt;Watch this video I made last week&lt;/a&gt;. Check out the Spring Health Assessment Report. You will see a few reasons why you should upgrade.  You will also see how easy it can be. Spring Boot 3.2.3 comes out next Thursday, how long will it take you to upgrade?&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/XI_7mpEiPjI" rel="noopener noreferrer"&gt;Spring into Action: Boost Your Java Projects with Essential Upgrade Tools!&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanzu.vmware.com/spring-health-assessment" rel="noopener noreferrer"&gt;Spring Health Assessment Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/tschuehly/status/1723106405756846117" rel="noopener noreferrer"&gt;Thomas Schuehly gets it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/openrewrite-spring-boot-upgrade-example" rel="noopener noreferrer"&gt;OpenRewrite Spring Boot Upgrade Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtube.com/clip/UgkxKNJxAWtsIWDzsFJM8tDB8VvG1E66EMVk?si=bF6GiJRZgWbH78JX" rel="noopener noreferrer"&gt;Joachim Pasquali at SpringOne 2023&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springboot</category>
    </item>
    <item>
      <title>Five Months in the making, multi-architecture builder</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Mon, 18 Sep 2023 00:09:00 +0000</pubDate>
      <link>https://dev.to/dashaun/five-months-in-the-making-multi-architecture-builder-27oi</link>
      <guid>https://dev.to/dashaun/five-months-in-the-making-multi-architecture-builder-27oi</guid>
      <description>&lt;h2&gt;
  
  
  Long-overdue version of multi-architecture buildpack with Spring Boot 3 AOT processing support for M1, Raspberry Pi, ARM64
&lt;/h2&gt;

&lt;p&gt;If you have been following along, you are probably asking "What took so long?" and I totally understand.&lt;br&gt;
I had no idea what was wrong before today.&lt;br&gt;
My scripts had been working for over a year, then just stopped.&lt;br&gt;
I had removed a bunch of the debug info from the logs, which didn't help the situation at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upstream progress
&lt;/h2&gt;

&lt;p&gt;The Paketo team continues to put a high priority on ARM64 and multi-architecture tools/buildpacks.&lt;br&gt;
I had created several ARM64 workarounds over the years.&lt;br&gt;
The Paketo team continued improving their tools to support ARM64 as well.&lt;br&gt;
I was not doing a good job of pulling the tooling changes from upstream.&lt;br&gt;
So my workarounds were breaking the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Several failed attempts
&lt;/h2&gt;

&lt;p&gt;I had made several solid attempts at resolving the issues.&lt;br&gt;
I just kept getting distracted with other priorities.&lt;br&gt;
I have had this process working on multiple environments.&lt;br&gt;
Every version of my pipeline seemed to be broken in a slightly different way.&lt;br&gt;
They were, because of my workarounds.&lt;br&gt;
I had different workarounds for different environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;This year, I've been traveling more than ever.&lt;br&gt;
In fact, I'm on my way to Oracle Cloud World 2023, right now.&lt;br&gt;
The laptop that I was using for work, wasn't keeping up with what I was needing to deliver, to the community.&lt;br&gt;
My conference sessions were having issues, my customer sessions were also having issues.&lt;br&gt;
Watching for over 4-minutes, for a native compile, isn't great for anyone.&lt;br&gt;
So I requested a new M1 laptop.&lt;br&gt;
When I got home on Friday, my new laptop had arrived.&lt;br&gt;
Since I'm going to be working on an M1 from now on, I needed to have all the fast builds.&lt;br&gt;
I needed to make sure all of my demos work on the new hardware.&lt;br&gt;
I also needed to be ready to use Java 21 when it comes out in 2 days.&lt;br&gt;
In addition to my new laptop from work, my Ampere Dev Kit server also arrived.&lt;br&gt;
The server had been stuck in US Customs for a couple of months, after arriving from China.&lt;br&gt;
It was my first time working with US Customs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;M1&lt;/li&gt;
&lt;li&gt;Ampere Dev Kit&lt;/li&gt;
&lt;li&gt;Java 21&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The work
&lt;/h2&gt;

&lt;p&gt;On my new M1, I simply started adding in debug info to the scripts.&lt;br&gt;
It felt good to get back into the process and remind myself how it all worked.&lt;br&gt;
When things work, you don't need to keep the details in your mental cache.&lt;br&gt;
The upstream builder added a few buildpacks.&lt;br&gt;
Packaging the newer buildpacks is where the problems were.&lt;br&gt;
Moments ago, I was getting ready to board when my tests passed.&lt;br&gt;
The KC Chiefs were playing Jacksonville at the same time.&lt;br&gt;
When I shouted with excitement, the people around me assumed that I was watching the game.&lt;br&gt;
I love my job.&lt;br&gt;
I love what I do.&lt;br&gt;
I could hear the Chiefs game, but getting this build to work was even more exciting that watching the Chiefs win the Super Bowl, twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  A huge surprise
&lt;/h2&gt;

&lt;p&gt;I don't remember what the number was, from the last time I looked at the stats on Docker Hub.&lt;br&gt;
When I went to look today, I was hoping for it to be over 2000.&lt;br&gt;
Maybe 3000?  Is 5000 possible?&lt;br&gt;
My goosebumps hit when I saw that it was over 8500!&lt;br&gt;
I am so excited that people are using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;I am going to try to move away from my scripts, towards what the Paketo team is doing.&lt;br&gt;
I want to get my version moved over to the &lt;a href="https://github.com/paketo-community" rel="noopener noreferrer"&gt;paketo-community&lt;/a&gt; repository.&lt;br&gt;
Hopefully, from there, it will be even closer to being merged upstream.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback welcome
&lt;/h2&gt;

&lt;p&gt;Please let me know how it is working for you, all of &lt;a href="https://dashaun.com" rel="noopener noreferrer"&gt;my socials/contact info can be found here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>arm64</category>
      <category>multiarch</category>
      <category>buildpacks</category>
      <category>spring</category>
    </item>
    <item>
      <title>Teamwork makes the dream work for this multi-architecture builder.</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 13 Dec 2022 04:28:48 +0000</pubDate>
      <link>https://dev.to/dashaun/teamwork-makes-the-dream-work-for-this-multi-architecture-builder-1co8</link>
      <guid>https://dev.to/dashaun/teamwork-makes-the-dream-work-for-this-multi-architecture-builder-1co8</guid>
      <description>&lt;h1&gt;
  
  
  Teamwork makes the dream work for this multi-architecture builder
&lt;/h1&gt;

&lt;p&gt;There has never been a better time to start delivering Spring Boot applications to ARM64 architectures. I've been working on ARM64 buildpacks for over a year now and I want you to see how far things have come.  These last few days have been leveled-up with help from Daniel Mikusa and Salman Malik, and the results are amazing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So close
&lt;/h2&gt;

&lt;p&gt;I'm excited because the builder for ARM64 is so close to the AMD64 builder supported by Paketo.  To see just how close things are, we can do a simple &lt;code&gt;diff&lt;/code&gt; of the builders, from different architectures.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Optionally, clear cached images first&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Inspect with ARM64
&lt;/h2&gt;

&lt;p&gt;On an ARM64 device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pack builder inspect dashaun/builder:tiny &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/dashaun.builder.tiny.arm64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inspecting builder: dashaun/builder:tiny

REMOTE:

Description: Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java, Java Native Image and Go

Created By:
Name: Pack CLI
Version: 0.28.0+git-b10e41f.build-3729

Trusted: No

Stack:
ID: io.paketo.stacks.tiny

Lifecycle:
Version: 0.15.2
Buildpack APIs:
Deprecated: (none)
Supported: 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9
Platform APIs:
Deprecated: (none)
Supported: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.10

Run Images:
index.docker.io/paketobuildpacks/run:tiny-cnb
gcr.io/paketo-buildpacks/run:tiny-cnb

Buildpacks:
ID                                                  NAME                                                   VERSION        HOMEPAGE
paketo-buildpacks/apache-tomcat                     Paketo Buildpack for Apache Tomcat                     7.9.1          https://github.com/paketo-buildpacks/apache-tomcat
paketo-buildpacks/apache-tomee                      Paketo Buildpack for Apache Tomee                      1.4.0          https://github.com/paketo-buildpacks/apache-tomee
paketo-buildpacks/azure-application-insights        Paketo Buildpack for Azure Application Insights        5.9.3          https://github.com/paketo-buildpacks/azure-application-insights
paketo-buildpacks/bellsoft-liberica                 Paketo Buildpack for BellSoft Liberica                 9.10.1         https://github.com/paketo-buildpacks/bellsoft-liberica
paketo-buildpacks/ca-certificates                   Paketo Buildpack for CA Certificates                   3.5.1          https://github.com/paketo-buildpacks/ca-certificates
paketo-buildpacks/clojure-tools                     Paketo Buildpack for Clojure Tools                     2.6.1          https://github.com/paketo-buildpacks/clojure-tools
paketo-buildpacks/datadog                           Paketo Buildpack for Datadog                           3.1.0          https://github.com/paketo-buildpacks/datadog
paketo-buildpacks/dist-zip                          Paketo Buildpack for DistZip                           5.4.0          https://github.com/paketo-buildpacks/dist-zip
paketo-buildpacks/encrypt-at-rest                   Paketo Buildpack for Encrypt-at-Rest                   4.3.2          https://github.com/paketo-buildpacks/encrypt-at-rest
paketo-buildpacks/environment-variables             Paketo Buildpack for Environment Variables             4.4.0          https://github.com/paketo-buildpacks/environment-variables
paketo-buildpacks/executable-jar                    Paketo Buildpack for Executable JAR                    6.5.0          https://github.com/paketo-buildpacks/executable-jar
paketo-buildpacks/git                               Paketo Buildpack for Git                               1.0.0          https://github.com/paketo-buildpacks/git
paketo-buildpacks/go                                Paketo Buildpack for Go                                4.0.0          https://github.com/paketo-buildpacks/go
paketo-buildpacks/go-build                          Paketo Buildpack for Go Build                          2.0.7          https://github.com/paketo-buildpacks/go-build
paketo-buildpacks/go-dist                           Paketo Buildpack for Go Distribution                   2.2.2          https://github.com/paketo-buildpacks/go-dist
paketo-buildpacks/go-mod-vendor                     Paketo Buildpack for Go Mod Vendor                     1.0.6          https://github.com/paketo-buildpacks/go-mod-vendor
paketo-buildpacks/google-stackdriver                Paketo Buildpack for Google Stackdriver                7.0.0          https://github.com/paketo-buildpacks/google-stackdriver
paketo-buildpacks/gradle                            Paketo Buildpack for Gradle                            6.10.0         https://github.com/paketo-buildpacks/gradle
paketo-buildpacks/image-labels                      Paketo Buildpack for Image Labels                      4.3.0          https://github.com/paketo-buildpacks/image-labels
paketo-buildpacks/jattach                           Paketo Buildpack for JAttach                           1.2.0          https://github.com/paketo-buildpacks/jattach
paketo-buildpacks/java                              Paketo Buildpack for Java                              8.1.0          https://paketo.io/docs/howto/java
paketo-buildpacks/java-memory-assistant             Paketo Buildpack for Java Memory Assistant             1.2.0          https://github.com/paketo-buildpacks/java-memory-assistant
paketo-buildpacks/java-native-image                 Paketo Buildpack for Java Native Image                 7.42.0         https://paketo.io/docs/howto/java/#build-an-app-as-a-graalvm-native-image-application
paketo-buildpacks/leiningen                         Paketo Buildpack for Leiningen                         4.4.0          https://github.com/paketo-buildpacks/leiningen
paketo-buildpacks/liberty                           Paketo Buildpack for Liberty                           3.1.0          https://github.com/paketo-buildpacks/liberty
paketo-buildpacks/maven                             Paketo Buildpack for Maven                             6.11.0         https://github.com/paketo-buildpacks/maven
paketo-buildpacks/native-image                      Paketo Buildpack for Native Image                      5.6.0          https://github.com/paketo-buildpacks/native-image
paketo-buildpacks/procfile                          Paketo Buildpack for Procfile                          5.5.0          https://github.com/paketo-buildpacks/procfile
paketo-buildpacks/sbt                               Paketo Buildpack for SBT                               6.9.0          https://github.com/paketo-buildpacks/sbt
paketo-buildpacks/spring-boot                       Paketo Buildpack for Spring Boot                       5.20.0         https://github.com/paketo-buildpacks/spring-boot
paketo-buildpacks/syft                              Paketo Buildpack for Syft                              1.23.0         https://github.com/paketo-buildpacks/syft
paketo-buildpacks/upx                               Paketo Buildpack for UPX                               3.3.0          https://github.com/paketo-buildpacks/upx
paketo-buildpacks/watchexec                         Paketo Buildpack for Watchexec                         2.7.0          https://github.com/paketo-buildpacks/watchexec

Detection Order:
├ Group #1:
│  └ paketo-buildpacks/java-native-image@7.42.0
│     └ Group #1:
│        ├ paketo-buildpacks/ca-certificates@3.5.1          (optional)
│        ├ paketo-buildpacks/upx@3.3.0                      (optional)
│        ├ paketo-buildpacks/bellsoft-liberica@9.10.1
│        ├ paketo-buildpacks/syft@1.23.0                    (optional)
│        ├ paketo-buildpacks/leiningen@4.4.0                (optional)
│        ├ paketo-buildpacks/gradle@6.10.0                  (optional)
│        ├ paketo-buildpacks/maven@6.11.0                   (optional)
│        ├ paketo-buildpacks/sbt@6.9.0                      (optional)
│        ├ paketo-buildpacks/executable-jar@6.5.0           (optional)
│        ├ paketo-buildpacks/spring-boot@5.20.0             (optional)
│        ├ paketo-buildpacks/native-image@5.6.0
│        ├ paketo-buildpacks/procfile@5.5.0                 (optional)
│        ├ paketo-buildpacks/environment-variables@4.4.0    (optional)
│        └ paketo-buildpacks/image-labels@4.3.0             (optional)
├ Group #2:
│  └ paketo-buildpacks/java@8.1.0
│     └ Group #1:
│        ├ paketo-buildpacks/ca-certificates@3.5.1               (optional)
│        ├ paketo-buildpacks/bellsoft-liberica@9.10.1
│        ├ paketo-buildpacks/syft@1.23.0                         (optional)
│        ├ paketo-buildpacks/leiningen@4.4.0                     (optional)
│        ├ paketo-buildpacks/clojure-tools@2.6.1                 (optional)
│        ├ paketo-buildpacks/gradle@6.10.0                       (optional)
│        ├ paketo-buildpacks/maven@6.11.0                        (optional)
│        ├ paketo-buildpacks/sbt@6.9.0                           (optional)
│        ├ paketo-buildpacks/watchexec@2.7.0                     (optional)
│        ├ paketo-buildpacks/executable-jar@6.5.0                (optional)
│        ├ paketo-buildpacks/apache-tomcat@7.9.1                 (optional)
│        ├ paketo-buildpacks/apache-tomee@1.4.0                  (optional)
│        ├ paketo-buildpacks/liberty@3.1.0                       (optional)
│        ├ paketo-buildpacks/dist-zip@5.4.0                      (optional)
│        ├ paketo-buildpacks/spring-boot@5.20.0                  (optional)
│        ├ paketo-buildpacks/procfile@5.5.0                      (optional)
│        ├ paketo-buildpacks/jattach@1.2.0                       (optional)
│        ├ paketo-buildpacks/azure-application-insights@5.9.3    (optional)
│        ├ paketo-buildpacks/google-stackdriver@7.0.0            (optional)
│        ├ paketo-buildpacks/datadog@3.1.0                       (optional)
│        ├ paketo-buildpacks/java-memory-assistant@1.2.0         (optional)
│        ├ paketo-buildpacks/encrypt-at-rest@4.3.2               (optional)
│        ├ paketo-buildpacks/environment-variables@4.4.0         (optional)
│        └ paketo-buildpacks/image-labels@4.3.0                  (optional)
├ Group #3:
│  ├ paketo-buildpacks/go@4.0.0
│  │  └ Group #1:
│  │     ├ paketo-buildpacks/ca-certificates@3.5.1          (optional)
│  │     ├ paketo-buildpacks/watchexec@2.7.0                (optional)
│  │     ├ paketo-buildpacks/go-dist@2.2.2
│  │     ├ paketo-buildpacks/git@1.0.0                      (optional)
│  │     ├ paketo-buildpacks/go-mod-vendor@1.0.6
│  │     ├ paketo-buildpacks/go-build@2.0.7
│  │     ├ paketo-buildpacks/procfile@5.5.0                 (optional)
│  │     ├ paketo-buildpacks/environment-variables@4.4.0    (optional)
│  │     └ paketo-buildpacks/image-labels@4.3.0             (optional)
│  └ paketo-buildpacks/go@4.0.0
│     └ Group #2:
│        ├ paketo-buildpacks/ca-certificates@3.5.1          (optional)
│        ├ paketo-buildpacks/watchexec@2.7.0                (optional)
│        ├ paketo-buildpacks/go-dist@2.2.2
│        ├ paketo-buildpacks/git@1.0.0                      (optional)
│        ├ paketo-buildpacks/go-build@2.0.7
│        ├ paketo-buildpacks/procfile@5.5.0                 (optional)
│        ├ paketo-buildpacks/environment-variables@4.4.0    (optional)
│        └ paketo-buildpacks/image-labels@4.3.0             (optional)
└ Group #4:
└ paketo-buildpacks/procfile@5.5.0

LOCAL:
(not present)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Inspect with AMD64
&lt;/h2&gt;

&lt;p&gt;On an AMD64 device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pack builder inspect dashaun/builder:tiny &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/dashaun.builder.tiny.amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file will look very similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inspecting builder: dashaun/builder:tiny

REMOTE:

Description: Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java, Java Native Image and Go

Created By:
  Name: Pack CLI
  Version: 0.28.0+git-b10e41f.build-3729

Trusted: No

Stack:
  ID: io.paketo.stacks.tiny

Lifecycle:
  Version: 0.15.2
  Buildpack APIs:
    Deprecated: (none)
    Supported: 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9
  Platform APIs:
    Deprecated: (none)
    Supported: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.10

Run Images:
  index.docker.io/paketobuildpacks/run:tiny-cnb
  gcr.io/paketo-buildpacks/run:tiny-cnb

Buildpacks:
  ID                                                  NAME                                                   VERSION        HOMEPAGE
  paketo-buildpacks/apache-tomcat                     Paketo Buildpack for Apache Tomcat                     7.9.1          https://github.com/paketo-buildpacks/apache-tomcat
  paketo-buildpacks/apache-tomee                      Paketo Buildpack for Apache Tomee                      1.4.0          https://github.com/paketo-buildpacks/apache-tomee
  paketo-buildpacks/azure-application-insights        Paketo Buildpack for Azure Application Insights        5.9.3          https://github.com/paketo-buildpacks/azure-application-insights
  paketo-buildpacks/bellsoft-liberica                 Paketo Buildpack for BellSoft Liberica                 9.10.1         https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/ca-certificates                   Paketo Buildpack for CA Certificates                   3.5.1          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/clojure-tools                     Paketo Buildpack for Clojure Tools                     2.6.1          https://github.com/paketo-buildpacks/clojure-tools
  paketo-buildpacks/datadog                           Paketo Buildpack for Datadog                           3.1.0          https://github.com/paketo-buildpacks/datadog
  paketo-buildpacks/dist-zip                          Paketo Buildpack for DistZip                           5.4.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/encrypt-at-rest                   Paketo Buildpack for Encrypt-at-Rest                   4.3.2          https://github.com/paketo-buildpacks/encrypt-at-rest
  paketo-buildpacks/environment-variables             Paketo Buildpack for Environment Variables             4.4.0          https://github.com/paketo-buildpacks/environment-variables
  paketo-buildpacks/executable-jar                    Paketo Buildpack for Executable JAR                    6.5.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/git                               Paketo Buildpack for Git                               1.0.0          https://github.com/paketo-buildpacks/git
  paketo-buildpacks/go                                Paketo Buildpack for Go                                4.0.0          https://github.com/paketo-buildpacks/go
  paketo-buildpacks/go-build                          Paketo Buildpack for Go Build                          2.0.7          https://github.com/paketo-buildpacks/go-build
  paketo-buildpacks/go-dist                           Paketo Buildpack for Go Distribution                   2.2.2          https://github.com/paketo-buildpacks/go-dist
  paketo-buildpacks/go-mod-vendor                     Paketo Buildpack for Go Mod Vendor                     1.0.6          https://github.com/paketo-buildpacks/go-mod-vendor
  paketo-buildpacks/google-stackdriver                Paketo Buildpack for Google Stackdriver                7.0.0          https://github.com/paketo-buildpacks/google-stackdriver
  paketo-buildpacks/gradle                            Paketo Buildpack for Gradle                            6.10.0         https://github.com/paketo-buildpacks/gradle
  paketo-buildpacks/image-labels                      Paketo Buildpack for Image Labels                      4.3.0          https://github.com/paketo-buildpacks/image-labels
  paketo-buildpacks/jattach                           Paketo Buildpack for JAttach                           1.2.0          https://github.com/paketo-buildpacks/jattach
  paketo-buildpacks/java                              Paketo Buildpack for Java                              8.1.0          https://paketo.io/docs/howto/java
  paketo-buildpacks/java-memory-assistant             Paketo Buildpack for Java Memory Assistant             1.2.0          https://github.com/paketo-buildpacks/java-memory-assistant
  paketo-buildpacks/java-native-image                 Paketo Buildpack for Java Native Image                 7.42.0         https://paketo.io/docs/howto/java/#build-an-app-as-a-graalvm-native-image-application
  paketo-buildpacks/leiningen                         Paketo Buildpack for Leiningen                         4.4.0          https://github.com/paketo-buildpacks/leiningen
  paketo-buildpacks/liberty                           Paketo Buildpack for Liberty                           3.1.0          https://github.com/paketo-buildpacks/liberty
  paketo-buildpacks/maven                             Paketo Buildpack for Maven                             6.11.0         https://github.com/paketo-buildpacks/maven
  paketo-buildpacks/native-image                      Paketo Buildpack for Native Image                      5.6.0          https://github.com/paketo-buildpacks/native-image
  paketo-buildpacks/procfile                          Paketo Buildpack for Procfile                          5.5.0          https://github.com/paketo-buildpacks/procfile
  paketo-buildpacks/sbt                               Paketo Buildpack for SBT                               6.9.0          https://github.com/paketo-buildpacks/sbt
  paketo-buildpacks/spring-boot                       Paketo Buildpack for Spring Boot                       5.20.0         https://github.com/paketo-buildpacks/spring-boot
  paketo-buildpacks/syft                              Paketo Buildpack for Syft                              1.23.0         https://github.com/paketo-buildpacks/syft
  paketo-buildpacks/upx                               Paketo Buildpack for UPX                               3.3.0          https://github.com/paketo-buildpacks/upx
  paketo-buildpacks/watchexec                         Paketo Buildpack for Watchexec                         2.7.0          https://github.com/paketo-buildpacks/watchexec

Detection Order:
 ├ Group #1:
 │  └ paketo-buildpacks/java-native-image@7.42.0
 │     └ Group #1:
 │        ├ paketo-buildpacks/ca-certificates@3.5.1          (optional)
 │        ├ paketo-buildpacks/upx@3.3.0                      (optional)
 │        ├ paketo-buildpacks/bellsoft-liberica@9.10.1
 │        ├ paketo-buildpacks/syft@1.23.0                    (optional)
 │        ├ paketo-buildpacks/leiningen@4.4.0                (optional)
 │        ├ paketo-buildpacks/gradle@6.10.0                  (optional)
 │        ├ paketo-buildpacks/maven@6.11.0                   (optional)
 │        ├ paketo-buildpacks/sbt@6.9.0                      (optional)
 │        ├ paketo-buildpacks/executable-jar@6.5.0           (optional)
 │        ├ paketo-buildpacks/spring-boot@5.20.0             (optional)
 │        ├ paketo-buildpacks/native-image@5.6.0
 │        ├ paketo-buildpacks/procfile@5.5.0                 (optional)
 │        ├ paketo-buildpacks/environment-variables@4.4.0    (optional)
 │        └ paketo-buildpacks/image-labels@4.3.0             (optional)
 ├ Group #2:
 │  └ paketo-buildpacks/java@8.1.0
 │     └ Group #1:
 │        ├ paketo-buildpacks/ca-certificates@3.5.1               (optional)
 │        ├ paketo-buildpacks/bellsoft-liberica@9.10.1
 │        ├ paketo-buildpacks/syft@1.23.0                         (optional)
 │        ├ paketo-buildpacks/leiningen@4.4.0                     (optional)
 │        ├ paketo-buildpacks/clojure-tools@2.6.1                 (optional)
 │        ├ paketo-buildpacks/gradle@6.10.0                       (optional)
 │        ├ paketo-buildpacks/maven@6.11.0                        (optional)
 │        ├ paketo-buildpacks/sbt@6.9.0                           (optional)
 │        ├ paketo-buildpacks/watchexec@2.7.0                     (optional)
 │        ├ paketo-buildpacks/executable-jar@6.5.0                (optional)
 │        ├ paketo-buildpacks/apache-tomcat@7.9.1                 (optional)
 │        ├ paketo-buildpacks/apache-tomee@1.4.0                  (optional)
 │        ├ paketo-buildpacks/liberty@3.1.0                       (optional)
 │        ├ paketo-buildpacks/dist-zip@5.4.0                      (optional)
 │        ├ paketo-buildpacks/spring-boot@5.20.0                  (optional)
 │        ├ paketo-buildpacks/procfile@5.5.0                      (optional)
 │        ├ paketo-buildpacks/jattach@1.2.0                       (optional)
 │        ├ paketo-buildpacks/azure-application-insights@5.9.3    (optional)
 │        ├ paketo-buildpacks/google-stackdriver@7.0.0            (optional)
 │        ├ paketo-buildpacks/datadog@3.1.0                       (optional)
 │        ├ paketo-buildpacks/java-memory-assistant@1.2.0         (optional)
 │        ├ paketo-buildpacks/encrypt-at-rest@4.3.2               (optional)
 │        ├ paketo-buildpacks/environment-variables@4.4.0         (optional)
 │        └ paketo-buildpacks/image-labels@4.3.0                  (optional)
 ├ Group #3:
 │  ├ paketo-buildpacks/go@4.0.0
 │  │  └ Group #1:
 │  │     ├ paketo-buildpacks/ca-certificates@3.5.1          (optional)
 │  │     ├ paketo-buildpacks/watchexec@2.7.0                (optional)
 │  │     ├ paketo-buildpacks/go-dist@2.2.2
 │  │     ├ paketo-buildpacks/git@1.0.0                      (optional)
 │  │     ├ paketo-buildpacks/go-mod-vendor@1.0.6
 │  │     ├ paketo-buildpacks/go-build@2.0.7
 │  │     ├ paketo-buildpacks/procfile@5.5.0                 (optional)
 │  │     ├ paketo-buildpacks/environment-variables@4.4.0    (optional)
 │  │     └ paketo-buildpacks/image-labels@4.3.0             (optional)
 │  └ paketo-buildpacks/go@4.0.0
 │     └ Group #2:
 │        ├ paketo-buildpacks/ca-certificates@3.5.1          (optional)
 │        ├ paketo-buildpacks/watchexec@2.7.0                (optional)
 │        ├ paketo-buildpacks/go-dist@2.2.2
 │        ├ paketo-buildpacks/git@1.0.0                      (optional)
 │        ├ paketo-buildpacks/go-build@2.0.7
 │        ├ paketo-buildpacks/procfile@5.5.0                 (optional)
 │        ├ paketo-buildpacks/environment-variables@4.4.0    (optional)
 │        └ paketo-buildpacks/image-labels@4.3.0             (optional)
 └ Group #4:
    └ paketo-buildpacks/procfile@5.5.0

LOCAL:
(not present)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, take a look at the &lt;code&gt;diff&lt;/code&gt; between those two files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;diff dashaun.builder.tiny.arm64 dashaun.builder.tiny.amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I'm writing this, the &lt;code&gt;diff&lt;/code&gt; looks like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
text
&amp;lt; Inspecting builder: 'dashaun/builder:tiny'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>mobile</category>
      <category>softwaredevelopment</category>
      <category>devops</category>
    </item>
    <item>
      <title>Spring Cloud Gateway 4.0.0-RC2 native example with Testcontainers</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Fri, 09 Dec 2022 01:24:19 +0000</pubDate>
      <link>https://dev.to/dashaun/spring-cloud-gateway-400-rc2-native-example-with-testcontainers-1jpg</link>
      <guid>https://dev.to/dashaun/spring-cloud-gateway-400-rc2-native-example-with-testcontainers-1jpg</guid>
      <description>&lt;h1&gt;
  
  
  Spring Cloud Gateway 4.0.0-RC2 native example with Testcontainers
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/dashaun/dev.dashaun.service.gateway" rel="noopener noreferrer"&gt;This example repository is on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are 3 goals I have for this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deliver Spring Cloud Gateway as a graalvm-native-image with Spring Boot 3&lt;/li&gt;
&lt;li&gt;Get feedback on the multi-architecture buildpack that includes ARM64 support&lt;/li&gt;
&lt;li&gt;Demonstrate a pattern for building and testing buildpack images using Testcontainers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://spring.io/projects/spring-cloud-gateway" rel="noopener noreferrer"&gt;Spring Cloud Gateway&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://spring.io/projects/spring-cloud-gateway" rel="noopener noreferrer"&gt;Spring.io&lt;/a&gt; website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Cloud Gateway aims to provide a simple,
yet effective way to route to APIs and provide
cross cutting concerns to them such as:
security, monitoring/metrics, and resiliency.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is very popular, and even more valuable than it is popular!&lt;/p&gt;

&lt;p&gt;Spring Framework 6 and Spring Boot 3.0.0 have recently gone GA. My favorite feature of these new releases is hands down the AOT processing. The AOT processing, provided by &lt;a href="https://graalvm.org" rel="noopener noreferrer"&gt;GraalVM&lt;/a&gt;, creates statically linked binary images. These images are optimized, ahead of time, for the operating system and architecture. For many modern workloads they can provide smaller image sizes, smaller memory footprint, and a much faster startup time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dashaun.com/posts/k3s-knative-ubuntu-raspberry-pi/" rel="noopener noreferrer"&gt;In a recent experiment&lt;/a&gt;, I was able to expose Spring Cloud Gateway to the public, and have it route to other Spring Boot 3 native image workloads. &lt;/p&gt;

&lt;p&gt;In that experiment Spring Cloud Gateway provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single service for SSL and ingress integration&lt;/li&gt;
&lt;li&gt;YAML configured routing&lt;/li&gt;
&lt;li&gt;Circuit Breaker capabilities with Resilience4j&lt;/li&gt;
&lt;li&gt;Request Rate Limiting with Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the experiment, I left the Spring Cloud Gateway running, by setting the &lt;code&gt;min-scale&lt;/code&gt; value to 1.&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;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spring-cloud-gateway&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;autoscaling.knative.dev/initial-scale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
        &lt;span class="na"&gt;autoscaling.knative.dev/min-scale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
        &lt;span class="na"&gt;autoscaling.knative.dev/max-scale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dashaun/dev.dashaun.service.gateway:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other Spring Boot applications, being routed to by the gateway, were allowed to scale-to-zero when they weren't used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dashaun.com/posts/k3s-knative-ubuntu-raspberry-pi/" rel="noopener noreferrer"&gt;The experiment was on a cluster of Raspberry Pi&lt;/a&gt; devices which are ARM64 cpus.&lt;/p&gt;

&lt;p&gt;The Spring Cloud Gateway, native image, typically starts up between 0.600s and 0.650s, on a Raspberry Pi.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dashaun.com/posts/spring-cloud-gateway-4-0-0-rc2-native-example-with-testcontainers/images/scg-startup-rpi.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspdl5fw2vxqmdsxihjdg.png" alt="SCG native image Raspberry Pi startup" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, the buildpack delivered by &lt;a href="https://start.spring.io" rel="noopener noreferrer"&gt;Spring Initializr at start.spring.io&lt;/a&gt;, is unable to create native images on ARM64.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-architecture buildpack
&lt;/h2&gt;

&lt;p&gt;Earlier this year, I started working on a buildpack to use with the experimental project, &lt;code&gt;Spring Native&lt;/code&gt;, and ARM64, for my Raspberry Pi devices. Later the M1/M2 Apple Silicon community, also ARM64, began showing interest in using this buildpack. &lt;a href="https://hub.docker.com/r/dashaun/java-native-builder-arm64" rel="noopener noreferrer"&gt;I have delivered a few versions to date&lt;/a&gt;. I've also created some automation to improve my process for creating these buildpacks.&lt;/p&gt;

&lt;p&gt;Recently, I created a &lt;a href="https://hub.docker.com/r/dashaun/java-native-builder-multiarch" rel="noopener noreferrer"&gt;multi-architecture buildpack&lt;/a&gt; that can be used with Spring Boot 3, to create native images on both Intel/AMD64 and ARM64 architectures. This example repository is configured with this multi-architecture buildpack.&lt;/p&gt;

&lt;p&gt;I'm still working to improve this builder, in an effort to get ARM64 support upstream to &lt;a href="https://paketo.io" rel="noopener noreferrer"&gt;Paketo&lt;/a&gt;. I have a long way to go, but with your help, I can discover gaps and continue this effort. One of the known, and frustrating gaps today, is that UPX compression does not work on ARM64, it does work with AMD64.  I really want to get this figured out, and it is a high priority for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building and testing native images
&lt;/h2&gt;

&lt;p&gt;One of the most common concerns I have heard from the community this year has been around validating that the native image artifacts are delivering the same results as the JIT/JVM based artifacts. This is a very valid concern.  This is also why I feel, that organizations with mature testing capabilities today, will be able to move forward with native images faster.&lt;/p&gt;

&lt;p&gt;This repository provides a &lt;code&gt;BuildImageTest&lt;/code&gt; that uses the buildpack to create a native image.  It then tests the native image, using &lt;a href="https://testcontainers.org" rel="noopener noreferrer"&gt;Testcontainers&lt;/a&gt; and &lt;a href="https://junit.org" rel="noopener noreferrer"&gt;JUnit&lt;/a&gt;. Building the native image with AOT processing, as part of a test, takes minutes not seconds, and should not be part of normal "inner loop" development.  So the &lt;code&gt;BuildImageTest&lt;/code&gt; is in a separate &lt;code&gt;sourceSet&lt;/code&gt; and can be executed independently. This is a very powerful pattern, that I'm just getting started with. I would love to hear your thoughts on this pattern or other alternatives to it.&lt;/p&gt;

&lt;p&gt;To build and validate the native image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew integrationTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can also execute the &lt;code&gt;BuildImageTest&lt;/code&gt; directly in your IDE.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;I am having so much fun with all of these new capabilities and challenges. I want to thank the Spring team, for all of their amazing work bring Spring Framework 6 and Spring Boot 3 to GA. I also want to thank Daniel Mikusa and the Paketo community for being amazing to work with. I want to thank Sergei Egorov for &lt;a href="https://bsideup.github.io/posts/spring_boot_in_container/" rel="noopener noreferrer"&gt;sharing the testing pattern&lt;/a&gt; and supporting my Testcontainers journey. Finally, I would like to thank &lt;a href="https://danvega.dev" rel="noopener noreferrer"&gt;Dan Vega&lt;/a&gt;, my &lt;a href="http://bit.ly/spring-office-hours" rel="noopener noreferrer"&gt;Spring Office Hours&lt;/a&gt; cohost, for being an amazing teammate, and having the JUICE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Learning
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spring.io/projects/spring-cloud-gateway" rel="noopener noreferrer"&gt;Spring Boot Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/projects/spring-cloud" rel="noopener noreferrer"&gt;Spring Cloud 2022.0.0-RC2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/projects/spring-boot" rel="noopener noreferrer"&gt;Spring Boot 3.0.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://testcontainers.org" rel="noopener noreferrer"&gt;Testcontainers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dashaun.com/posts/spring-boot-3-buildpacks-with-testcontainers-cloud/" rel="noopener noreferrer"&gt;Spring Boot 3 buildpacks with Testcontainers Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dashaun.com/posts/java-native-builder-multiarch-7-41-0/" rel="noopener noreferrer"&gt;Cloud Native Buildpack for ARM64 and AMD64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dashaun.com/posts/multiarch-builder-poc/" rel="noopener noreferrer"&gt;First attempt at a multi-architecture buildpack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dashaun.com/posts/k3s-knative-ubuntu-raspberry-pi/" rel="noopener noreferrer"&gt;K3s Knative Ubuntu Raspberry Pi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Cloud Native Buildpack for ARM64 and AMD64</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 06 Dec 2022 22:31:40 +0000</pubDate>
      <link>https://dev.to/dashaun/cloud-native-buildpack-for-arm64-and-amd64-39g2</link>
      <guid>https://dev.to/dashaun/cloud-native-buildpack-for-arm64-and-amd64-39g2</guid>
      <description>&lt;h1&gt;
  
  
  Version 7.41.0
&lt;/h1&gt;

&lt;p&gt;This builder works with Intel/AMD architectures and ARM64 architectures like Apple M1 and Raspberry Pi.&lt;/p&gt;

&lt;p&gt;With this release I was able to use a little more automation on my side, to get my &lt;code&gt;unsupported&lt;/code&gt; builder even closer to the &lt;a href="https://paketo.io" rel="noopener noreferrer"&gt;upstream at Paketo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/dashaun/java-native-builder-multiarch" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dmikusa/paketo-arm64" rel="noopener noreferrer"&gt;Daniel Mikusa GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;I'm trying to make each of the buildpacks as similar as possible to each other and upstream.&lt;br&gt;
I'm also working on delivering newer versions of the stack, which is currently Ubuntu Focal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback welcome
&lt;/h2&gt;

&lt;p&gt;Please let me know how it is working for you, all of &lt;a href="https://dashaun.com" rel="noopener noreferrer"&gt;my socials/contact info can be found here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>github</category>
    </item>
    <item>
      <title>K3s Knative Ubuntu Raspberry Pi</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 06 Dec 2022 07:58:45 +0000</pubDate>
      <link>https://dev.to/dashaun/k3s-knative-ubuntu-raspberry-pi-45i0</link>
      <guid>https://dev.to/dashaun/k3s-knative-ubuntu-raspberry-pi-45i0</guid>
      <description>&lt;h1&gt;
  
  
  K3s Knative Ubuntu Raspberry Pi
&lt;/h1&gt;

&lt;p&gt;Knative is a super exciting solution for serverless and event driven applications.&lt;br&gt;
Personally, I have been using it as a part of my &lt;code&gt;inner loop&lt;/code&gt; development.&lt;br&gt;
I can deploy dozens of workloads.&lt;br&gt;
All scaled down to zero when they aren't being used.&lt;br&gt;
All responding and starting up quickly when they are needed.&lt;/p&gt;

&lt;p&gt;I ran into a couple of problems with Envoy on Raspberry Pi OS.&lt;br&gt;
After investigating, it is a known issue in the Raspberry Pi OS kernel.&lt;br&gt;
In order to keep going forward with my project I had to choose between fixing the kernel or trying Ubuntu.&lt;/p&gt;

&lt;p&gt;This is an overview of how to get Knative Serving working on a Raspberry Pi device with K3s and Ubuntu.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install Ubuntu Server
&lt;/h2&gt;

&lt;p&gt;Use RPI Imager.&lt;br&gt;
Select &lt;code&gt;Ubuntu Server 22.10 64-bit&lt;/code&gt; from the list of other distributions.&lt;br&gt;
Boot the new image.&lt;/p&gt;

&lt;p&gt;I was happy to see that SSH and the default user were configured as expected by using the advanced settings options.&lt;/p&gt;
&lt;h2&gt;
  
  
  Upgrade the stuff
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Grab the latest repository info&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update 
&lt;span class="c"&gt;# Install one prerequisite package that isn't installed by default&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;linux-modules-extra-raspi &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="c"&gt;# Upgrade everything that can be upgraded&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt dist-upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Fix cgroup
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add this to the beginning of /boot/firmware/cmdline.txt&lt;/span&gt;
&lt;span class="c"&gt;# cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano &lt;span class="nt"&gt;-w&lt;/span&gt; /boot/firmware/cmdline.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Reboot after making this change.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;shutdown &lt;span class="nt"&gt;-r&lt;/span&gt; now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install k3s via k3sup
&lt;/h2&gt;

&lt;p&gt;From my laptop, not one of the Raspberry Pi nodes, run &lt;a href="https://github.com/alexellis/k3sup" rel="noopener noreferrer"&gt;k3sup&lt;/a&gt;, it's super easy.&lt;br&gt;
If you haven't figured it out by now, I'm a big fan of &lt;a href="https://github.com/alexellis/k3sup" rel="noopener noreferrer"&gt;k3sup&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CONTROL_NODE_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.0.10
&lt;span class="c"&gt;# Disable Traefik because we will use kourier for knative&lt;/span&gt;
k3sup &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--ip&lt;/span&gt; &lt;span class="nv"&gt;$CONTROL_NODE_IP&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; pi &lt;span class="nt"&gt;--k3s-extra-args&lt;/span&gt; &lt;span class="s1"&gt;'--disable traefik'&lt;/span&gt; &lt;span class="nt"&gt;--merge&lt;/span&gt; &lt;span class="nt"&gt;--local-path&lt;/span&gt; ~/.kube/config &lt;span class="nt"&gt;--context&lt;/span&gt; cluster00
&lt;span class="c"&gt;# Knative-Serving with Kourier works on a single Raspberry Pi 4 with 8GB&lt;/span&gt;
&lt;span class="c"&gt;# Optionally add other nodes&lt;/span&gt;
k3sup &lt;span class="nb"&gt;join&lt;/span&gt; &lt;span class="nt"&gt;--ip&lt;/span&gt; 10.0.0.11 &lt;span class="nt"&gt;--server-ip&lt;/span&gt; &lt;span class="nv"&gt;$CONTROL_NODE_IP&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; pi
k3sup &lt;span class="nb"&gt;join&lt;/span&gt; &lt;span class="nt"&gt;--ip&lt;/span&gt; 10.0.0.12 &lt;span class="nt"&gt;--server-ip&lt;/span&gt; &lt;span class="nv"&gt;$CONTROL_NODE_IP&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; pi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deep breath
&lt;/h2&gt;

&lt;p&gt;This is where I struggled with how to deliver this example.&lt;br&gt;
The &lt;a href="https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/" rel="noopener noreferrer"&gt;Knative install documentation&lt;/a&gt; is awesome.&lt;br&gt;
I've been through it dozens of times!&lt;/p&gt;

&lt;p&gt;I tried to simplify this for you.&lt;br&gt;
I want to give you the 'easy button' experience.&lt;br&gt;
I created a &lt;a href="https://github.com/dashaun/knative-serving-raspberry-pi" rel="noopener noreferrer"&gt;repository&lt;/a&gt; with all the YAML you need to deploy Knative Serving to this K3s cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knative Serving with Kourier
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dashaun/knative-serving-raspberry-pi
&lt;span class="nb"&gt;cd &lt;/span&gt;knative-serving-raspberry-pi/operator-serving-kourier
&lt;span class="c"&gt;# Use the `default` namespace in the Kubernetes cluster&lt;/span&gt;
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default
&lt;span class="c"&gt;# Deploy the Knative operator&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; operator.yaml
&lt;span class="c"&gt;# Check the deployment status&lt;/span&gt;
kubectl get deployment knative-operator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Continue when &lt;code&gt;Ready&lt;/code&gt; status is &lt;code&gt;1/1&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME               READY   UP-TO-DATE   AVAILABLE   AGE
knative-operator   1/1     1            1           19h
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Knative Serving custom resource and Kourier&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; knative-serving.yaml
&lt;span class="c"&gt;# Check the custom resource status&lt;/span&gt;
kubectl get KnativeServing knative-serving &lt;span class="nt"&gt;-n&lt;/span&gt; knative-serving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Continue when &lt;code&gt;READY&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME              VERSION   READY   REASON
knative-serving   1.8.0     True
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the deployment status&lt;/span&gt;
kubectl get deployment &lt;span class="nt"&gt;-n&lt;/span&gt; knative-serving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Continue when &lt;code&gt;READY&lt;/code&gt; are all &lt;code&gt;1/1&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
activator                1/1     1            1           23m
autoscaler               1/1     1            1           23m
controller               1/1     1            1           23m
domain-mapping           1/1     1            1           23m
domainmapping-webhook    1/1     1            1           23m
autoscaler-hpa           1/1     1            1           23m
3scale-kourier-gateway   1/1     1            1           23m
webhook                  1/1     1            1           23m
net-kourier-controller   1/1     1            1           23m
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  SSLIP.IO and a Spring Boot 3 &lt;code&gt;native&lt;/code&gt; application
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install default domain&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; serving-default-domain.yaml
&lt;span class="c"&gt;# Deploy a Spring Boot 3 `native` application&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; spring-boot-native-pi-service.yaml
&lt;span class="c"&gt;# Get the address&lt;/span&gt;
kubectl get ksvc spring-boot-native-pi &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;custom-columns&lt;span class="o"&gt;=&lt;/span&gt;NAME:.metadata.name,URL:.status.url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                    URL
spring-boot-native-pi   http://spring-boot-native-pi.default.10.0.0.10.sslip.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Curl the address provided by knative with `/actuator/health` at the end&lt;/span&gt;
curl http://spring-boot-native-pi.default.default.10.0.0.10.sslip.io/actuator/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Expected output:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"status":"UP"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;With the recent release of Spring Boot 3.0.0, my Raspberry Pi collection has become significantly more interesting.&lt;br&gt;
Knative Serving allows me to deploy dozens of Spring Boot / Spring Cloud workloads to a tiny, low power device, with very exciting capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Learning
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://knative.dev" rel="noopener noreferrer"&gt;Knative&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/" rel="noopener noreferrer"&gt;Spring Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/knative-serving-raspberry-pi" rel="noopener noreferrer"&gt;Knative Serving Raspberry Pi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>community</category>
    </item>
    <item>
      <title>Spring Boot 3 Buildpacks with Testcontainers Cloud</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Tue, 22 Nov 2022 05:55:59 +0000</pubDate>
      <link>https://dev.to/dashaun/spring-boot-3-buildpacks-with-testcontainers-cloud-5fd2</link>
      <guid>https://dev.to/dashaun/spring-boot-3-buildpacks-with-testcontainers-cloud-5fd2</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I have a modest laptop. It checks the boxes, for just about everything I need to do for work. Just about. AOT processing, with GraalVM, is not a great experience on this machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The origin story
&lt;/h2&gt;

&lt;p&gt;Back in June 2022, I was presenting at SpringOne Tour NYC. As part of the demo, I built a &lt;code&gt;native&lt;/code&gt; image using Spring Boot 2.7 with the &lt;code&gt;spring-native&lt;/code&gt; experimental dependency. The session went well, except for the building the native image part of it. During my 50-minute session, my laptop was busy building the native image for over 8-minutes, using the buildpack. It was a humbling experience. After the session, I hung out with Sergei Egorov, and we worked on something very special. With help from &lt;a href="https://bsideup.github.io/posts/spring_boot_in_container/" rel="noopener noreferrer"&gt;one of his previous blog posts&lt;/a&gt;, we created a proof of concept. We used &lt;a href="https://www.testcontainers.cloud/" rel="noopener noreferrer"&gt;Testcontainers Cloud&lt;/a&gt; to build the native OCI image, in the cloud, in a little over 3-minutes. Pairing with Sergei is amazing, if you get the chance, I highly recommend it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/dashaun/status/1541812027286495235" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F98atmk5wrklz3kvkt2lf.png" alt="Origin Story" width="800" height="1095"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It sat on the back-burner and fire is hot
&lt;/h2&gt;

&lt;p&gt;I had this code sitting in my repository, waiting to be used. When it was time for SpringOne Tour Tel Aviv, Nov 2022, I was ready to show it off. Unfortunately for me, my session was shortened, so I pulled it out of the presentation. I shouldn’t have pulled it out, I made the exact same mistake that I made in NYC. When I built the native image using buildpacks, it took way too long on my laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Momentum
&lt;/h2&gt;

&lt;p&gt;My adventures with buildpacks have been top of mind for a couple of weeks now. Oleg Šelajev and Cora Iberklied also presented about testcontainers in Tel Aviv. The stage was set for me to take this use case further.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build Spring Boot 3, native OCI images, with buildpacks, during demos, faster, with Testcontainers Cloud
&lt;/h3&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need to have an account at &lt;a href="https://testcontainers.cloud" rel="noopener noreferrer"&gt;https://testcontainers.cloud&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Login to your account
&lt;/h2&gt;

&lt;p&gt;I’m logging in with &lt;code&gt;v1.3.11&lt;/code&gt; of the cloud desktop app for Mac, this is still in private beta at the time of this writing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context list

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You should see at least one context named ’tcc'&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use tcc

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create an application example
&lt;/h2&gt;

&lt;p&gt;Create a Spring Boot 3 application with &lt;code&gt;web&lt;/code&gt;, &lt;code&gt;actuator&lt;/code&gt;, and &lt;code&gt;testcontainers&lt;/code&gt; for a simple test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://start.spring.io/starter.tgz -d dependencies=web,actuator,testcontainers -d javaVersion=17 -d bootVersion=3.0.0-RC2 -d type=maven-project | tar -xzf -

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Spring Boot 3 goes GA later this week, but I can’t wait that long&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the pom.xml add this dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.shared&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;maven-invoker&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.2.0&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;testcontainers.version&amp;gt;1.17.6&amp;lt;/testcontainers.version&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this dependency management section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependencyManagement&amp;gt;
    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.testcontainers&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;testcontainers-bom&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${testcontainers.version}&amp;lt;/version&amp;gt;
            &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
            &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;
&amp;lt;/dependencyManagement&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those pieces will allow you to write a test, that creates an OCI image, using buildpacks, with Testcontainers Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the test class
&lt;/h2&gt;

&lt;p&gt;Here is the test class that I’ve been reusing since June.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.example.demo;

import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.jupiter.api.Test;
import org.springframework.util.DigestUtils;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.LazyFuture;

import java.io.File;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Future;

public class TccTest {
    private static final Future&amp;lt;String&amp;gt; IMAGE_FUTURE = new LazyFuture&amp;lt;&amp;gt;() {
        @Override
        protected String resolve() {
            // Find project's root dir
            File cwd;
            for (
                    cwd = new File(".");
                    !new File(cwd, "mvnw").isFile();
                    cwd = cwd.getParentFile()
            );

            // Make it unique per folder (for caching)
            var imageName = String.format(
                    "local/app-%s:%s",
                    DigestUtils.md5DigestAsHex(cwd.getAbsolutePath().getBytes()),
                    System.currentTimeMillis()
            );

            var properties = new Properties();
            properties.put("spring-boot.build-image.imageName", imageName);
            properties.put("skipTests", "true");

            var request = new DefaultInvocationRequest()
                    .addShellEnvironment("DOCKER_HOST", DockerClientFactory.instance().getTransportConfig().getDockerHost().toString())
                    .setPomFile(new File(cwd, "pom.xml"))
                    .setGoals(List.of("spring-boot:build-image"))
                    .setMavenExecutable(new File(cwd, "mvnw"))
                    .setProfiles(List.of("native"))
                    .setProperties(properties);

            InvocationResult invocationResult = null;
            try {
                invocationResult = new DefaultInvoker().execute(request);
            } catch (MavenInvocationException e) {
                throw new RuntimeException(e);
            }

            if (invocationResult.getExitCode() != 0) {
                throw new RuntimeException(invocationResult.getExecutionException());
            }

            return imageName;
        }
    };

    static final GenericContainer&amp;lt;?&amp;gt; APP = new GenericContainer&amp;lt;&amp;gt;(IMAGE_FUTURE)
            .withExposedPorts(8080);

    @Test
    void letsGo() throws Exception {
        APP.start();
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;a href="https://github.com/dashaun/spring-boot-3-buildpacks-with-testcontainers-cloud" rel="noopener noreferrer"&gt;my example repository&lt;/a&gt;, I actually create test classes for a regular OCI image and a &lt;code&gt;native&lt;/code&gt; OCI image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./mvnw clean test

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! You get an OCI image, or two if you use &lt;a href="https://github.com/dashaun/spring-boot-3-buildpacks-with-testcontainers-cloud" rel="noopener noreferrer"&gt;my example repository&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker images | grep "local"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This is slick. It brings me joy. When I am presenting this topic, I no longer need to have &lt;code&gt;Docker Desktop&lt;/code&gt; running on my laptop. Therefore, my laptop battery will last longer. My builds will be consistently faster, using Testcontainers Cloud, and my demos will be smoother.&lt;/p&gt;

&lt;p&gt;I am completely rethinking a few of my own use cases. I will definitely create more Testcontainers Cloud content in the future.&lt;/p&gt;

&lt;p&gt;Please let me know what you think!&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://testcontainers.cloud" rel="noopener noreferrer"&gt;Testcontainers Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/spring-boot-3-buildpacks-with-testcontainers-cloud" rel="noopener noreferrer"&gt;Example Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bsideup.github.io/posts/spring_boot_in_container/" rel="noopener noreferrer"&gt;Sergei Egorov Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6fqtqkdwr1evh6ox4gg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6fqtqkdwr1evh6ox4gg1.png" alt="Pairing with Sergei" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>First attempt at a multi-arch buildpack for Spring Boot 3</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Sat, 12 Nov 2022 13:58:51 +0000</pubDate>
      <link>https://dev.to/dashaun/first-attempt-at-a-multi-arch-buildpack-for-spring-boot-3-4p2d</link>
      <guid>https://dev.to/dashaun/first-attempt-at-a-multi-arch-buildpack-for-spring-boot-3-4p2d</guid>
      <description>&lt;p&gt;My &lt;a href="https://dashaun.com/paketo-aarch64-builder-spring-boot-3.rc1" rel="noopener noreferrer"&gt;previous builder&lt;/a&gt; generated some fabulous feedback, including &lt;a href="https://github.com/jabrena/101-docker/issues/57" rel="noopener noreferrer"&gt;an issue in GitHub&lt;/a&gt;. It also opened the door for some wonderful collaborations while I have been traveling to Barcelona and Tel Aviv.&lt;/p&gt;

&lt;p&gt;On this journey of trying to get ARM64 support into the upstream &lt;a href="https://paketo.io" rel="noopener noreferrer"&gt;Paketo buildpacks&lt;/a&gt; I have learned so much. The next step is to prove that we can create the pipelines that can deliver a multi-architecture image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Skip to the &lt;a href="https://github.com/dashaun/java-native-builder-multiarch" rel="noopener noreferrer"&gt;Quick Start here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Docker manifest
&lt;/h2&gt;

&lt;p&gt;In order to see the architecture of an image, you can use the &lt;code&gt;docker manifest&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Inspect shows the image architecture
docker manifest inspect --verbose dashaun/java-native-builder-arm64:7.37.0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Inspecting the ARM64 builder that was created in earlier article&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Create a new manifest list
&lt;/h2&gt;

&lt;p&gt;The process for slapping docker images together is pretty simple. Ideally you should be using the “same” image, but we are just proving a point here.&lt;/p&gt;

&lt;p&gt;My theory is that if I provide an &lt;code&gt;arm64&lt;/code&gt; architecture and an &lt;code&gt;amd64&lt;/code&gt; architecture to the same manifest list, that it will work as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create a new manifest with a name and tag
docker manifest create dashaun/java-native-builder-multiarch:7.37.0 \
# add ARM64 builder
--amend dashaun/java-native-builder-arm64:7.37.0 \
# add Paketo tiny builder for AMD64
--amend paketobuildpacks/builder:tiny

# push the manifest to the repository
docker manifest push dashaun/java-native-builder-multiarch:7.37.0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  inspect the manifest list - the builder
&lt;/h2&gt;

&lt;p&gt;Now you can inspect the manifest and see that it has packaged ARM64 and AMD64 architectures. This capability has been available for years. It is also surprisingly simple to accomplish as you can see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker manifest inspect --verbose dashaun/java-native-builder-multiarch:7.37.0


[
    {
        "Ref": "docker.io/dashaun/java-native-builder-arm64:7.37.0",
        "Descriptor": {
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "digest": "sha256:80771c9c2efee25020ba60e320c41cc385a333434ad2982ed7326a0a413b9ae7",
            "size": 5150,
            "platform": {
                "architecture": "arm64",
                "os": "linux"
            }
        },
        "SchemaV2Manifest": {
            "schemaVersion": 2,
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "config": {
                "mediaType": "application/vnd.docker.container.image.v1+json",
                "size": 12587,
                "digest": "sha256:8cd1ead83689b853055ac2de0cc07c5f3c9d8d087b2ac97e838551fe8e962e00"
            },
            "layers": [
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 27195998,
                    "digest": "sha256:4e7e0215f4adc2c48ad9cb3b3781e21d474b477587f85682c2e2975ae91dce9d"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 4348,
                    "digest": "sha256:fc2e670f062f6edbb1b3be6aae88cbc0978a5273314c365bc4e99d861dfb5ff1"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 103281689,
                    "digest": "sha256:cba3892e6fcca4b47983803d53b777bc05476639fcfc1a2d4f384eac40363677"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 179,
                    "digest": "sha256:f5230b5f167cfc41fb9f96d53de0d9c5832e3b03e3a705052cf42ceada13aea9"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 197,
                    "digest": "sha256:357fefdf9bc907107a38600cf8d79c713346dc97370273d1aa79635d97a2f6f9"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 8870380,
                    "digest": "sha256:0ab7ccc1e1a42589b53d1397672009a979b296933efc276d79d3a2cdc336656c"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2498256,
                    "digest": "sha256:fa161ac7a0015773c3f6890d6f1824957a6e21cee5a9bbf8ddc60e4e6d30a3ed"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1806073,
                    "digest": "sha256:bdfdd20caee6a7044607d11d9a4f758a007ad2d61399ea5e3db22a7037ab4975"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3273385,
                    "digest": "sha256:bc251569386a8e5fd513c85d9c51e76a258f0e59d0f945e185e101930012c9db"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1509592,
                    "digest": "sha256:caeca36964d2da6c2b88abd998281e4fd5b708cfdd1440486f6acf7bc569292e"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3191207,
                    "digest": "sha256:f840d9ee444653d04a2181d1d99e4f579d01b56349cce9325c6dade9af410998"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3273637,
                    "digest": "sha256:b4f56cede28073295dc750e93678876a55b9fdafd97c940b97dc9a01ae345663"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2387292,
                    "digest": "sha256:cebaf5f7c6e3350f5973f2c77f9ba90138d5a6bb2f1eb9e86084c8cf9f6117c6"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 4208510,
                    "digest": "sha256:47be0bf349071cb594970ef2d18f383b91231645ba49f27b5b39b47d9090aedc"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1818619,
                    "digest": "sha256:5844db3d59f2bdc13aa19a6fd09789cf4a12aa87088759840a79c10eb01f93c5"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1569855,
                    "digest": "sha256:89975d6b7daf9b8cf8b1a8350461e9e009b0ffc2a482becb9e1073adc3475153"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2413551,
                    "digest": "sha256:aca3b8db4220fa7d69bf9cbd0bebc048cfa381174929828affb820c5b6b0f505"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2480839,
                    "digest": "sha256:71bd4e702bfc97f1db60e6a5ac364897b2818dc2f92f1bf9d78c2935a1ae125d"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2521983,
                    "digest": "sha256:3e1f506f69017dbcedb2a2b022ff0d6e63d4c0cd44e32fdcf6cc9e39b2416ade"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2475996,
                    "digest": "sha256:f617bf8af0912672e16ed61332d6e7ad1f0ffc4fffe049da45ea670a1c441afd"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 362,
                    "digest": "sha256:0e3fabdd36281c4c1c946d09854510dc82ffd61476d62aaa474b3c64d8969ae3"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 151,
                    "digest": "sha256:9602e7831cd64261f173d4789e26f9cada67107ba1688409f7781bd737792b58"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 32,
                    "digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
                }
            ]
        }
    },
    {
        "Ref": "docker.io/paketobuildpacks/builder:tiny",
        "Descriptor": {
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "digest": "sha256:e5b4e4e720598e905baa793a94259ee75827949a6ba22a3d3196eb332e475dd6",
            "size": 9773,
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            }
        },
        "SchemaV2Manifest": {
            "schemaVersion": 2,
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "config": {
                "mediaType": "application/vnd.docker.container.image.v1+json",
                "size": 64488,
                "digest": "sha256:94a3f804e111904682577130709e0cac6e0541e6b1931c1f9aa4f27326c974fc"
            },
            "layers": [
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 29589398,
                    "digest": "sha256:38496a9cab3e2bb43fa8ae1d45c3ddc1d2f89328e1f3973f029949b8cb2dd5fe"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 247,
                    "digest": "sha256:2e7826560210e2f0d4249f9ea20651fecd45306c2482767274c8c37289c23788"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 316,
                    "digest": "sha256:6013ff26a0374342ff4848b3171ddbe396413d80faa2b1ee67ca9afbfa64cafc"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 106179950,
                    "digest": "sha256:87196f2fd40c0ee5587ff771878aad41b42c1d29628c75fff9206b5c0b77f016"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 188,
                    "digest": "sha256:d61afba38b8e093dfc46b8e81c141b2d7eddc0903b0fc530b3af36a46a144af7"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2744757,
                    "digest": "sha256:856e5ee7330534a520c3fd1d90260e9a5e99bfc182e0c2c3b18f7fea06f0bb0e"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 807,
                    "digest": "sha256:7eededf5887404e40dfc0d1d533fb9db3fe6b725213e4f3edda1ca1f5b1492c4"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 197,
                    "digest": "sha256:357fefdf9bc907107a38600cf8d79c713346dc97370273d1aa79635d97a2f6f9"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 9753557,
                    "digest": "sha256:d0d113e4e2bd21dbaa2f66e687ec4a23e4e83331b7845b85d0c10de1da05b08d"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2574003,
                    "digest": "sha256:41869a3aefd1a5bf8db003eb401d1af5ef2568c92262c3038f553d38145f36a1"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2573999,
                    "digest": "sha256:053ee41df41d6484ffec64fda64937a6418a1404f602487019fccddbad2958de"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 4641532,
                    "digest": "sha256:0fec499a25de5a35aa814bed46943a93c94d0e6785eedc679874ec3da04bc748"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2525673,
                    "digest": "sha256:c7b3308e13d3f30bde70d2293590aefdb95e52784b8bb840e13c587d354604e6"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 8714351,
                    "digest": "sha256:51918c870f73c8ceffc527e79041e08d3101beeabd0c0bc44555b6faf1188cdd"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3197061,
                    "digest": "sha256:fbb9179cbbb1aa191e9ec19e77b7e6226b97ba8ac9d382822970ddcc61ce05e2"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2661004,
                    "digest": "sha256:38876f988da32a7ef093d5245f6923bb9b748362834345a3e8995f72ef49e168"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2547018,
                    "digest": "sha256:b847bb2fe3fdc6bd69cd7d690bcef5a1399310ce17bea49bbd1d5fd3641d0140"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3291941,
                    "digest": "sha256:78b11fcb7ca30ba09defc8dcfc1298ea20b970430d9836b4bc7c404840b49a03"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3255182,
                    "digest": "sha256:18ee4cd834fc95727859c15eaedc1f5670c4e9f3f74e3f45a108d5b9167344ab"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1576728,
                    "digest": "sha256:ad443f24b1ed77880e534a0c1aca0ca5a722e866b7611a85e9fd7ef3463dea08"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1511595,
                    "digest": "sha256:a6cd17527f03923a723839bdbe57ec200a008de6c8b35c0c4d900a675adac014"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2689335,
                    "digest": "sha256:cd146e648d04aaf6161526eb8be96d3bf9c8fc22e433c4e6834dc972b4f09830"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3478686,
                    "digest": "sha256:bf32d3cbc5fb2d1c54c54f964b11e10d70c58f340dc17857d45db246807bcca6"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2639800,
                    "digest": "sha256:c505716a4281aec3fc577e417a29d23a19d1ff720f498aef49b9ed362ab53d88"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3382413,
                    "digest": "sha256:17c8e16edff8afd8ef3b3b1d1d061351245287c59c6ab8846c1af94b414e1865"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 555,
                    "digest": "sha256:57bf259ba83920a4e2575582423c084670209a37fdb503fbbc7e10d75a5419d0"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2798929,
                    "digest": "sha256:e71af6bec187b3f6627f52f6f2808c6b946b5cc09b7f4a6ee006cc5557254ebc"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1678930,
                    "digest": "sha256:fcf29a1baad8cb3d657d685b47ab630602a5c47b333f7ed9d7cbcae68a300801"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1826608,
                    "digest": "sha256:fad10dc586613d133e71639507d157251d8d533d9c94c3897ed5ae6c557bcdf7"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2637303,
                    "digest": "sha256:bc22ffea56cce18044465269f6958bfb8ef37e182c82c6ab9f2821525c034717"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1623998,
                    "digest": "sha256:63fd16f5b2cc4af356f18b61281870994919fcb17f3a51f490fc09b6cd5e0cfe"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3091878,
                    "digest": "sha256:71dea8ea27aab08e92be2cb1c61ba12693f8dc622575cd9087e689fdabd048fd"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 8496432,
                    "digest": "sha256:547d0874d2b5a13d766fc98a54725cce8b5ec50af624d5c21b971ca0e668d7e0"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2644647,
                    "digest": "sha256:27bd211db33f03f1ad558807c8312a3bf537e154b591f7067a7eab8d9364f10b"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 5318,
                    "digest": "sha256:3455451f8d3fd909057c5f94d9c34b5618fa6f62db51ed540cd7931c146a5fd6"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3472177,
                    "digest": "sha256:a77b05080df7a983ee586813839673e2dfdb852c87589ddf0900517bc928b779"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 8659224,
                    "digest": "sha256:6a58fb7cc75d73d08a6d85d995ad767e5693c4afd8b4f94f89f61018a3925c67"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 5136,
                    "digest": "sha256:437d9ac90cf3a6df605fbc9b691455242c3f4d7a3ba89b683daa5885314f1909"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 4385434,
                    "digest": "sha256:52814f0ca0c9d85074d817b558ccb3583cdb394d1ec02cf3e45e860b24ef3f7b"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 1838119,
                    "digest": "sha256:7e8facf10ccafea1b9688a4a4ba424d36f92e3e4cea1e913577bb45eb5bdc5f9"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 2464280,
                    "digest": "sha256:b8531cb5032999225be8589ab8bec5ea275fffb47353863c29d1afcdf7966104"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 3468583,
                    "digest": "sha256:55e985d488da8faeaa9772b7cfb3073d458fa626c89a390b292ab879be30f157"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 214,
                    "digest": "sha256:7d37e76963e86dbc76a9cbd3a6afa21c127bb790f77415b608da57daa9019f10"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 187,
                    "digest": "sha256:3aaaa9c943efae7533b9a28a28a676354866cad58208bcd7988b055c754653f0"
                },
                {
                    "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                    "size": 32,
                    "digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
                }
            ]
        }
    }
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  validation
&lt;/h2&gt;

&lt;p&gt;Create a Spring Boot 3 application with &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;actuator&lt;/code&gt; which is enough for a simple test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a Spring Boot 3 application
curl https://start.spring.io/starter.tgz -d dependencies=web,actuator -d javaVersion=17 -d bootVersion=3.0.0-SNAPSHOT -d type=maven-project | tar -xzf -

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I’m using SNAPSHOT, because YOLO, but RC2 is already available!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add a profile to the pom.xml, that will use the &lt;code&gt;multi-arch&lt;/code&gt; builder that we created above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Remove the last line of the pom.xml file (on Mac OS X)
sed -i '' -e '$ d' pom.xml
# Add the new profile to the end of the pom.xml
echo "
  &amp;lt;profiles&amp;gt;
    &amp;lt;profile&amp;gt;
      &amp;lt;id&amp;gt;dashaun&amp;lt;/id&amp;gt;
      &amp;lt;build&amp;gt;
        &amp;lt;plugins&amp;gt;
          &amp;lt;plugin&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
            &amp;lt;configuration&amp;gt;
              &amp;lt;image&amp;gt;
                &amp;lt;builder&amp;gt;dashaun/java-native-builder-multiarch:7.37.0&amp;lt;/builder&amp;gt;
              &amp;lt;/image&amp;gt;
            &amp;lt;/configuration&amp;gt;
          &amp;lt;/plugin&amp;gt;
        &amp;lt;/plugins&amp;gt;
      &amp;lt;/build&amp;gt;
    &amp;lt;/profile&amp;gt;
  &amp;lt;/profiles&amp;gt;
&amp;lt;/project&amp;gt;" &amp;gt;&amp;gt; pom.xml 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your project will use the multi-architecture builder that created.&lt;/p&gt;

&lt;h2&gt;
  
  
  optional
&lt;/h2&gt;

&lt;p&gt;To watch, and be amazed, by the correct images being pulled down to your machine, run these commands to remove any unused images from your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker kill $(docker ps -q)
docker system prune -a --volumes

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  build on AMD64 or ARM64
&lt;/h2&gt;

&lt;p&gt;The magical moment! You can do this from AMD64 or ARM64.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./mvnw -Pnative,dashaun spring-boot:build-image

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should have an image created. One layer of that image, is a statically linked binary for your architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  inspect the new image.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# find the local image
docker images | grep demo
# Look at the architecture for the image
docker inspect demo:0.0.1-SNAPSHOT | jq '.[0].Architecture'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;| jq.[0].Architecture&lt;/code&gt; can be replaced with &lt;code&gt;| grep Architecture&lt;/code&gt; if you don’t have &lt;code&gt;jq&lt;/code&gt; installed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  run and test the image
&lt;/h2&gt;

&lt;p&gt;Run the OCI image with &lt;code&gt;docker&lt;/code&gt; to start up the server. It should start quickly!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Forward the port, run in the background, but see the startup time
docker run -p 8080:8080 demo:0.0.1-SNAPSHOT 
# Check the endpoint to validate
http :8080/actuator/health

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/java-native-builder-multiarch" rel="noopener noreferrer"&gt;GitHub dashaun/java-native-builder-multiarch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/dashaun/java-native-builder-multiarch" rel="noopener noreferrer"&gt;Docker dashaun/java-native-builder-multiarch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/paketo-arm64" rel="noopener noreferrer"&gt;dashaun/paketo-arm64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dashaun.com/posts/paketo-aarch64-builder-spring-boot-3-rc1/" rel="noopener noreferrer"&gt;Related Post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/dashaun/status/1591432189488803840" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmz8zsy2j8sjccxcqlsh.png" alt="Its working" width="800" height="978"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;I want to hear from you! All of my social links are &lt;a href="https://dashaun.com" rel="noopener noreferrer"&gt;here at dashaun.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Issues and feedback can be left in the &lt;a href="https://github.com/dashaun/java-native-builder-multiarch" rel="noopener noreferrer"&gt;GitHub dashaun/java-native-builder-multiarch&lt;/a&gt; repository.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A new builder for Spring Boot 3 RC1 on ARM64</title>
      <dc:creator>DaShaun</dc:creator>
      <pubDate>Thu, 10 Nov 2022 11:18:05 +0000</pubDate>
      <link>https://dev.to/dashaun/a-new-builder-for-spring-boot-3-rc1-on-arm64-4k31</link>
      <guid>https://dev.to/dashaun/a-new-builder-for-spring-boot-3-rc1-on-arm64-4k31</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I’m a fan of using buildpacks. I’ve been &lt;a href="https://dashaun.com/posts/springboot-arm64-image/" rel="noopener noreferrer"&gt;using&lt;/a&gt; and updating the &lt;a href="https://github.com/dmikusa/paketo-arm64" rel="noopener noreferrer"&gt;paketo-arm64 repository started by Daniel Mikusa&lt;/a&gt;. With Spring Boot 3 going GA, very soon, more people are wanting to use buildpacks with their M1 machines. I feel pressure, in a great way, to get a new version released that includes the latest version of GraalVM.&lt;/p&gt;

&lt;p&gt;Because I’m not doing this regularly, I forget the steps that I take to create a new build. So this post, is really a reminder-to-self, but hopefully it helps someone else also.&lt;/p&gt;

&lt;p&gt;You can follow along with this on &lt;code&gt;M1&lt;/code&gt;,&lt;code&gt;Raspberry Pi&lt;/code&gt;, or a free-tier &lt;code&gt;ARM64&lt;/code&gt; instance from Oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;p&gt;I’ve been setting up a temporary ARM64 machine on Oracle Cloud for ARM64/aarch64 builds. The repository I use is &lt;a href="https://github.com/dashaun/oci-arm64-gha-runner-terraform" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I should probably talk about that in another post, also.&lt;/p&gt;

&lt;p&gt;My first step was to deploy that machine, so it can be used to build the builder! Once I got the machine up and connected to my repository, I was ready to update the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make changes
&lt;/h2&gt;

&lt;p&gt;First I just made changes to the versions in &lt;a href="https://raw.githubusercontent.com/dashaun/paketo-arm64/main/.github/workflows/paketo-arm64.yml" rel="noopener noreferrer"&gt;my GitHub workflow&lt;/a&gt;. That worked and pushed out a new image, but it wasn’t enough.&lt;/p&gt;

&lt;p&gt;I had to re-read the workflow to understand what I was missing.&lt;/p&gt;

&lt;p&gt;For each of the files in the &lt;code&gt;arm64-toml&lt;/code&gt; of &lt;a href="https://github.com/dashaun/paketo-arm64" rel="noopener noreferrer"&gt;the repository&lt;/a&gt; I needed to update to the latest versions. This is a tedious, and currently manual, process that requires copy, paste, merge and then creating sha256 values.&lt;/p&gt;

&lt;p&gt;For these versions, it was extra messy, because naming conventions changed since my last release. In some places &lt;code&gt;arm64&lt;/code&gt; was replaced with &lt;code&gt;aarch64&lt;/code&gt; as one example.&lt;/p&gt;

&lt;p&gt;Additionally, some dependencies had even more releases, and I decided to go with the &lt;code&gt;full&lt;/code&gt; option when it was available.&lt;/p&gt;

&lt;p&gt;The last real time-suck was the &lt;code&gt;sha&lt;/code&gt; values. In the GitHub releases, the sha values are provided as &lt;code&gt;sha1sum&lt;/code&gt; values. In the builder metadata, it requires &lt;code&gt;sha256&lt;/code&gt; values. Since I couldn’t find &lt;code&gt;sha256&lt;/code&gt; values published anywhere, I had to download the dependencies and calculate the values manually. For each dependency update, &lt;code&gt;curl&lt;/code&gt; then &lt;code&gt;shasum&lt;/code&gt;, which can be error prone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://github.com/bell-sw/Liberica/releases/download/8u352+8/bellsoft-jdk8u352+8-linux-aarch64.tar.gz -O
shasum -a 256 bellsoft-jdk8u352+8-linux-aarch64.tar.gz

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully, this post and the small updates I made to the &lt;code&gt;README.md&lt;/code&gt; will remind me next time. I should probably figure out a way to automate some of that. Maybe a CLI?&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an application image
&lt;/h2&gt;

&lt;p&gt;Create a Spring Boot 3 application with &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;actuator&lt;/code&gt; which is enough for a simple test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a Spring Boot 3 application
curl https://start.spring.io/starter.tgz -d dependencies=web,actuator -d javaVersion=17 -d bootVersion=3.0.0-SNAPSHOT -d type=maven-project | tar -xzf -

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the pom.xml replace this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;build&amp;gt;
    &amp;lt;plugins&amp;gt;
        &amp;lt;plugin&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
&amp;lt;/build&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;build&amp;gt;
    &amp;lt;plugins&amp;gt;
        &amp;lt;plugin&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
            &amp;lt;configuration&amp;gt;
                &amp;lt;image&amp;gt;
                    &amp;lt;builder&amp;gt;dashaun/java-native-builder-arm64:7.37.0&amp;lt;/builder&amp;gt;
                &amp;lt;/image&amp;gt;
            &amp;lt;/configuration&amp;gt;
        &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
&amp;lt;/build&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build an image using the image builder that we have created and configured.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./mvnw -Pnative spring-boot:build-image

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run and test the image
&lt;/h2&gt;

&lt;p&gt;Run the OCI image with &lt;code&gt;docker&lt;/code&gt; to start up the server. It should start quickly!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Forward the part, run in the background, but see the startup time
docker run -p 8080:8080 demo:0.0.1-SNAPSHOT &amp;amp;
# Check the endpoint to validate
http http :8080/actuator/health

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;Because I’m done, and moved &lt;a href="https://hub.docker.com/r/dashaun/java-native-builder-arm64" rel="noopener noreferrer"&gt;builder version 7.37.0 to prod&lt;/a&gt;, I also cleaned up my &lt;code&gt;terraform&lt;/code&gt; deployment.&lt;/p&gt;

&lt;p&gt;Another huge “Thanks” to &lt;a href="https://twitter.com/dmikusa" rel="noopener noreferrer"&gt;Daniel Mikusa&lt;/a&gt; for all of his help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/dashaun/java-native-builder-arm64" rel="noopener noreferrer"&gt;dashaun/java-native-builder-arm64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/paketo-arm64" rel="noopener noreferrer"&gt;dashaun/paketo-arm64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dashaun/oci-arm64-gha-runner-terraform" rel="noopener noreferrer"&gt;dashaun/oci-arm64-gha-runner-terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dashaun.com/posts/springboot-arm64-image/" rel="noopener noreferrer"&gt;Related Post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springboot</category>
      <category>buildpack</category>
      <category>arm64</category>
      <category>m1</category>
    </item>
  </channel>
</rss>
