<?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: Albert</title>
    <description>The latest articles on DEV Community by Albert (@aerc18).</description>
    <link>https://dev.to/aerc18</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F11235%2Fccd3b558-43a2-46e9-b2f7-072e72820264.jpg</url>
      <title>DEV Community: Albert</title>
      <link>https://dev.to/aerc18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aerc18"/>
    <language>en</language>
    <item>
      <title>How to Deploy Spring Boot Apps on Fly.io</title>
      <dc:creator>Albert</dc:creator>
      <pubDate>Tue, 14 Apr 2026 23:03:08 +0000</pubDate>
      <link>https://dev.to/aerc18/how-to-deploy-spring-boot-apps-on-flyio-5390</link>
      <guid>https://dev.to/aerc18/how-to-deploy-spring-boot-apps-on-flyio-5390</guid>
      <description>&lt;p&gt;Fly.io is an incredible platform for deploying applications close to your users globally. However, if you are a Java developer looking to launch a Spring Boot application, you might hit a brief moment of hesitation. &lt;/p&gt;

&lt;p&gt;As of the time of writing, Fly.io does not offer native, out-of-the-box support for Spring in the same way it does for the options listed on their &lt;a href="https://fly.io/docs/languages-and-frameworks/" rel="noopener noreferrer"&gt;supported languages and frameworks page&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Don't worry—getting your Spring Boot app running on Fly.io is still incredibly straightforward. Here is how to do it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Standard Route: Using a Dockerfile
&lt;/h2&gt;

&lt;p&gt;Even though Spring isn't formally supported with an automatic builder, Fly.io handles standard Docker containers seamlessly. If you already have a &lt;code&gt;Dockerfile&lt;/code&gt; for your project, you are basically good to go. You can follow Fly.io's official &lt;a href="https://fly.io/docs/languages-and-frameworks/dockerfile/" rel="noopener noreferrer"&gt;Dockerfile deployment instructions&lt;/a&gt;, and your app will be up and running in no time.&lt;/p&gt;

&lt;p&gt;But what if you don't have a &lt;code&gt;Dockerfile&lt;/code&gt; and don't particularly want to write or maintain one? &lt;/p&gt;




&lt;h2&gt;
  
  
  The Easier Route: Cloud Native Buildpacks
&lt;/h2&gt;

&lt;p&gt;When working with modern Spring Boot, there is a highly convenient way to create optimized Docker images without ever touching a &lt;code&gt;Dockerfile&lt;/code&gt;. You can accomplish this using &lt;strong&gt;Cloud Native Buildpacks&lt;/strong&gt;, which are integrated directly into both Maven and Gradle.&lt;/p&gt;

&lt;p&gt;With a single command like &lt;code&gt;./mvnw spring-boot:build-image&lt;/code&gt; or &lt;code&gt;./gradlew bootBuildImage&lt;/code&gt;, Spring Boot will package your application into a production-ready Docker image. &lt;/p&gt;

&lt;p&gt;Taking this route is, in my opinion, the absolute easiest way to get your app onto Fly.io. Here is the step-by-step process.&lt;/p&gt;




&lt;h2&gt;
  
  
  4 Steps to Deploy Spring Boot via Buildpacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Initialize your Fly.io App
&lt;/h3&gt;

&lt;p&gt;First, you need to tell Fly.io about your project. Open your terminal at the root of your Spring Boot project and run the launch command. Follow the interactive prompts to set up your app name and region.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fly launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command provisions your app on Fly.io and generates a &lt;code&gt;fly.toml&lt;/code&gt; configuration file in your project directory. &lt;em&gt;Note: Tell the CLI not to deploy just yet when it asks, as we still need to build our image.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build and Tag the Docker Image
&lt;/h3&gt;

&lt;p&gt;Next, we will use Spring Boot's buildpack integration to create our image and immediately tag it for the Fly.io Docker registry. Make sure your Docker daemon is running locally before executing this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are using Gradle:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew bootBuildImage &lt;span class="nt"&gt;--imageName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.fly.io/&lt;span class="o"&gt;{&lt;/span&gt;YOUR_APP_NAME&lt;span class="o"&gt;}&lt;/span&gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you are using Maven:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./mvnw spring-boot:build-image &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.build-image.imageName&lt;span class="o"&gt;=&lt;/span&gt;registry.fly.io/&lt;span class="o"&gt;{&lt;/span&gt;YOUR_APP_NAME&lt;span class="o"&gt;}&lt;/span&gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Replace {YOUR_APP_NAME} with the name you chose during the fly launch step.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Push to the Fly Registry
&lt;/h3&gt;

&lt;p&gt;With the image successfully built and tagged, we need to push it to Fly.io's internal registry. First, authenticate your local Docker client with Fly, and then push the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl auth docker
docker push registry.fly.io/&lt;span class="o"&gt;{&lt;/span&gt;YOUR_APP_NAME&lt;span class="o"&gt;}&lt;/span&gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Deploy the Application
&lt;/h3&gt;

&lt;p&gt;Your image is now sitting in the registry, waiting to be used. The final step is to tell Fly.io to deploy a new release using that specific image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flyctl deploy &lt;span class="nt"&gt;--image&lt;/span&gt; registry.fly.io/&lt;span class="o"&gt;{&lt;/span&gt;YOUR_APP_NAME&lt;span class="o"&gt;}&lt;/span&gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;There you have it! You have successfully deployed your Spring Boot app to Fly.io. By utilizing Spring Boot's built-in buildpack support and the Fly.io private registry, you can easily bypass the lack of native framework support and completely avoid the hassle of manually maintaining a &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>docker</category>
      <category>java</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Why My "Lightning Fast" Spring Boot Native App Took 9 Seconds to Boot on Fly.io</title>
      <dc:creator>Albert</dc:creator>
      <pubDate>Thu, 02 Apr 2026 18:58:49 +0000</pubDate>
      <link>https://dev.to/aerc18/why-my-lightning-fast-spring-boot-native-app-took-9-seconds-to-boot-on-flyio-db5</link>
      <guid>https://dev.to/aerc18/why-my-lightning-fast-spring-boot-native-app-took-9-seconds-to-boot-on-flyio-db5</guid>
      <description>&lt;h1&gt;
  
  
  Why My "Lightning Fast" Spring Boot Native App Took 9 Seconds to Boot on Fly.io
&lt;/h1&gt;

&lt;p&gt;We’ve all heard the promise of GraalVM and Spring Boot Native: sub-second cold starts! Instant scaling! A fraction of the memory! So, I spent the time configuring my Spring Boot 4 app to compile into a native image. Locally, inside a Docker container, it booted in a highly respectable &lt;strong&gt;1.7 seconds&lt;/strong&gt;. Feeling triumphant, I deployed it to Fly.io, expecting instantaneous "scale-to-zero" magic.&lt;/p&gt;

&lt;p&gt;I checked the logs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Started Application in 9.026 seconds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wait, what? 9 seconds? For a pre-compiled native binary? Thus began my descent into a debugging rabbit hole that fundamentally changed how I view cloud hardware, GraalVM, and the "scale-to-zero" paradigm. &lt;/p&gt;

&lt;p&gt;Here is the story of how I debugged a 9-second cold start, and why I eventually decided to abandon scale-to-zero altogether.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Spring Boot 4 + Hibernate + Flyway&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java Version:&lt;/strong&gt; Java 25&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tool:&lt;/strong&gt; Gradle with the GraalVM Native Build Tools plugin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; Fly.io (shared-cpu-1x, 512MB RAM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL hosted on AWS RDS (us-east-1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most baffling part was that my local Docker container in Colombia was pointing to the same AWS RDS database, and it still started in 1.7 seconds. So, the code was fine, and the database was reachable. &lt;em&gt;What was happening in the cloud?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Down the Debugging Rabbit Hole
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hypothesis 1: CPU Throttling and Memory Thrashing
&lt;/h3&gt;

&lt;p&gt;My first thought was that GraalVM’s Serial Garbage Collector was thrashing within the tiny 512MB memory limit of my Fly.io microVM, or that the shared CPU was just too weak.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Test:&lt;/strong&gt; I scaled the machine up to a dedicated performance CPU and 2 GB of RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Result:&lt;/strong&gt; &lt;code&gt;Started Application in 9.041 seconds.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It didn't shave off a single millisecond. It wasn't a resource starvation issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hypothesis 2: IPv6 and OS Entropy Blocking
&lt;/h3&gt;

&lt;p&gt;Cloud microVMs can sometimes hang during startup if they lack OS-level entropy (needed for secure random number generation by Tomcat/Hikari) or if they timeout trying to resolve IPv6 DNS records before falling back to IPv4.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Test:&lt;/strong&gt; I passed standard Java arguments to bypass both:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[env]&lt;/span&gt;
  &lt;span class="py"&gt;JAVA_TOOL_OPTIONS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"-Djava.net.preferIPv4Stack&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="py"&gt;-Djava.security.egd&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;file:/dev/./urandom&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Result:&lt;/strong&gt; &lt;code&gt;Started Application in 9.037 seconds.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still 9 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hypothesis 3: Spring Boot's Eager Initialization
&lt;/h3&gt;

&lt;p&gt;Maybe Spring was doing too much work on the main thread?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Test:&lt;/strong&gt; I forced global lazy initialization (&lt;code&gt;SPRING_MAIN_LAZY_INITIALIZATION=true&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Result:&lt;/strong&gt; &lt;code&gt;Started Application in 9.237 seconds.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It actually got &lt;em&gt;slower&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This was because I was using the &lt;code&gt;/actuator/health&lt;/code&gt; endpoint for fly.io to recognize that the app was healthy, and when you do that, the actuator actually performs a series of checks that then create all the beans required for the app to run and show as healthy.&lt;/p&gt;

&lt;p&gt;Even though you can change this behavior. I abandoned the idea of lazy initialization because all my requests perform operations in the DB, so I think this was not the solution to my problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Aha!" Moment: The Reality of Cloud MicroVMs
&lt;/h2&gt;

&lt;p&gt;After staring at timestamps, the reality of cloud architecture finally set in. The 9-second boot wasn't a bug; it was the natural hardware limit of running a heavy Spring Boot 4 app on a microVM. &lt;/p&gt;

&lt;p&gt;It came down to two major bottlenecks:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Single-Threaded CPU Limits
&lt;/h3&gt;

&lt;p&gt;GraalVM Native Image initialization is strictly single-threaded. Locally, my developer laptop has a massive single-core burst speed (4.0 GHz+). Cloud microVMs are carved out of massive, stable server chips (like AMD EPYC) with much lower single-core clock speeds (~2.5 GHz). Throwing &lt;code&gt;cpus = 4&lt;/code&gt; at the app did nothing, because startup only uses one core. The laptop chewed through Spring's AOT wiring in milliseconds; the cloud vCPU took seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Network Penalty (Flyway &amp;amp; HikariCP)
&lt;/h3&gt;

&lt;p&gt;My app included Flyway and HikariCP. During startup, it had to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resolve the AWS RDS DNS hostname.&lt;/li&gt;
&lt;li&gt;Perform the SSL handshake.&lt;/li&gt;
&lt;li&gt;Run Flyway schema validations across the public internet.&lt;/li&gt;
&lt;li&gt;Fetch Hibernate metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Locally, the CPU steps were so fast they hid the network delay. On Fly.io, the slower CPU combined with the network hops compounded into a massive 9-second wall.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scale-to-Zero Dilemma
&lt;/h2&gt;

&lt;p&gt;When your goal is to "scale to zero," a 9-second cold start is a death sentence. The first user to hit your API after it spins down has to wait 9 seconds just for the server to wake up. &lt;/p&gt;

&lt;p&gt;I considered my options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Switch to Quarkus?&lt;/strong&gt; It might shave a few seconds off by shifting more reflection to compile time, but the network handshakes (Flyway/RDS) would still block the startup thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite in Go?&lt;/strong&gt; A Go REST API compiles to a tiny binary and could probably cold-start and serve a request in under 100 ms. But rewriting the entire application wasn't feasible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I made the pragmatic choice: I abandoned scale-to-zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pragmatic Solution: Always-On
&lt;/h2&gt;

&lt;p&gt;For a typical REST API, leaving a single small instance running 24/7 on Fly.io costs roughly $3 to $5 a month. By setting a minimum machine count of 1, the first instance stays warm perpetually, guaranteeing instant responses.&lt;/p&gt;

&lt;p&gt;But this led to a new architectural question: &lt;em&gt;If the app is running 24/7, should I stick with the GraalVM Native Image, or go back to the standard JVM?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is the mental model I landed on for deploying Spring Boot 4:&lt;/p&gt;

&lt;h3&gt;
  
  
  Go back to the JVM if:
&lt;/h3&gt;

&lt;p&gt;You can afford to run your container with 1GB+ of RAM.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peak Performance:&lt;/strong&gt; The JVM's Just-In-Time (JIT) compiler will eventually outperform the Native Image's AOT compiler on a long-running server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Experience:&lt;/strong&gt; Your CI/CD builds will take seconds instead of 10+ minutes, and you get your profiling and debugging tools back.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stick with the Native Image if:
&lt;/h3&gt;

&lt;p&gt;You want to keep infrastructure costs as close to $0 as possible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory Survival:&lt;/strong&gt; If you are deploying on a tiny 256MB or 512MB instance, the JVM will feel claustrophobic and might get killed by the Linux OOM killer. The Native Image's incredibly tiny RAM footprint is the only way a heavy Spring + Hibernate application survives comfortably in that small of a box.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;I ended up switching back to the standard JVM. I bumped my Fly.io machine up to 1 GB of RAM to give the JVM enough breathing room, turned off Flyway at startup (&lt;code&gt;spring.flyway.enabled=false&lt;/code&gt;) to speed up future horizontal scaling, and set my configuration to leave one instance running permanently.&lt;/p&gt;

&lt;p&gt;The extra couple of dollars a month for the upgraded RAM was entirely worth the blazing-fast CI/CD builds, easier debugging, and the peace of mind knowing the JVM's JIT compiler was optimizing my hot paths under the hood.&lt;/p&gt;

&lt;p&gt;Scale-to-zero is a cool concept, but sometimes, paying a few bucks a month to let your server sleep with one eye open is the best engineering decision you can make.&lt;/p&gt;

&lt;p&gt;Have you struggled with Native Image cold starts in the cloud? Did you rewrite it in Go/Rust, or just leave the server running? Please let me know in the comments!&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>architecture</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
