<?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: Ian Ochieng</title>
    <description>The latest articles on DEV Community by Ian Ochieng (@iochieng1).</description>
    <link>https://dev.to/iochieng1</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%2F3953641%2Fd17366eb-7766-4bcf-b157-602c3d0e5d44.jpeg</url>
      <title>DEV Community: Ian Ochieng</title>
      <link>https://dev.to/iochieng1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iochieng1"/>
    <language>en</language>
    <item>
      <title># Building SemaKazi Part 3: Adding Tests (and Why I Had to Refactor First)</title>
      <dc:creator>Ian Ochieng</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:02:15 +0000</pubDate>
      <link>https://dev.to/iochieng1/-building-semakazi-part-3-adding-tests-and-why-i-had-to-refactor-first-2pne</link>
      <guid>https://dev.to/iochieng1/-building-semakazi-part-3-adding-tests-and-why-i-had-to-refactor-first-2pne</guid>
      <description>&lt;p&gt;SemaKazi is a verified reputation platform for Kenya's informal-sector workers — electricians, carpenters, mechanics, tailors — solving the trust gap between a fundi's real skill and a client's ability to verify it before hiring. Backend shipped in Phase 1, frontend (auth, search, profiles, dashboard) shipped in Phase 2. Today was Phase 4: tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with testing an Express app that just... runs
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;server.js&lt;/code&gt; did two jobs at once: configure the Express app &lt;em&gt;and&lt;/em&gt; call &lt;code&gt;app.listen()&lt;/code&gt; to start it on a fixed port. That's fine for running the app, but it makes it nearly impossible to test cleanly — you can't import "the app" without also starting a real server bound to a real port, which collides with whatever's already running in dev.&lt;/p&gt;

&lt;p&gt;The fix is a pattern I'd read about but never actually needed until today: split it in two.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.js&lt;/code&gt; — builds and returns a configured Express app. No &lt;code&gt;listen()&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server.js&lt;/code&gt; — imports that app, calls &lt;code&gt;.listen()&lt;/code&gt;, and that's it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now tests can import &lt;code&gt;app.js&lt;/code&gt;, spin up an instance on &lt;code&gt;port: 0&lt;/code&gt; (which tells the OS "just give me any free port"), run requests against it, and tear it down — no collisions, no fixed ports, no interference with a dev server that might already be running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What got tested
&lt;/h2&gt;

&lt;p&gt;Seven tests, using Node's built-in test runner and native &lt;code&gt;fetch&lt;/code&gt; — no new dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Health check responds correctly&lt;/li&gt;
&lt;li&gt;Registration creates a user and returns a token&lt;/li&gt;
&lt;li&gt;Duplicate email registration is rejected&lt;/li&gt;
&lt;li&gt;Login succeeds with correct credentials, fails with wrong ones&lt;/li&gt;
&lt;li&gt;Protected routes reject requests with no token&lt;/li&gt;
&lt;li&gt;Search returns fundis with correct average rating and review count&lt;/li&gt;
&lt;li&gt;A user cannot edit another user's profile (ownership check)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each test run gets its own throwaway SQLite file, cleaned up after — so tests never touch the real dev database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this specific set
&lt;/h2&gt;

&lt;p&gt;Not exhaustive coverage — just the kinds of things that already broke once. The &lt;code&gt;.env&lt;/code&gt;/JWT issue from Phase 1 and the &lt;code&gt;profile.js&lt;/code&gt;/&lt;code&gt;profiles.js&lt;/code&gt; naming mismatch were both only caught by manual &lt;code&gt;curl&lt;/code&gt; testing at the time. A real test suite means the next regression like that gets caught automatically instead of by trial and error in a terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small refactor, real lesson
&lt;/h2&gt;

&lt;p&gt;The actual code that broke in the past week wasn't complicated. What made debugging slow was structure — a server file that couldn't be tested in isolation, a file name that silently worked in one environment and failed in another. Fixing the &lt;em&gt;shape&lt;/em&gt; of the code turned out to matter more than adding more code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left
&lt;/h2&gt;

&lt;p&gt;One small frontend polish item — the API base URL is currently hardcoded to &lt;code&gt;localhost&lt;/code&gt;, which needs to be configurable before deployment — then Phase 5: getting this live.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repo's public if you want to see the test file or the before/after of the refactor.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>refactoring</category>
      <category>testing</category>
    </item>
    <item>
      <title>Building SemaKazi Part 2: Starting the Frontend, No Framework Required</title>
      <dc:creator>Ian Ochieng</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:19:45 +0000</pubDate>
      <link>https://dev.to/iochieng1/-building-semakazi-part-2-starting-the-frontend-no-framework-required-2j02</link>
      <guid>https://dev.to/iochieng1/-building-semakazi-part-2-starting-the-frontend-no-framework-required-2j02</guid>
      <description>&lt;p&gt;Phase 1 of SemaKazi (a verified reputation platform for Kenya's informal-sector workers — electricians, carpenters, mechanics, tailors) shipped a working backend: auth, profiles, proof-of-work, reviews, badges, all tested end-to-end. Today I started Phase 2 — the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it deliberately simple
&lt;/h2&gt;

&lt;p&gt;No React, no build step, no bundler. Just HTML, CSS, and vanilla JavaScript talking to the API with &lt;code&gt;fetch&lt;/code&gt;. For a project this size, reaching for a framework would've added ceremony without adding value — the goal is working software, not a tech showcase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went in today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The skeleton first.&lt;/strong&gt; Before any actual pages, I built the shared pieces every page would need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;api.js&lt;/code&gt; — one object (&lt;code&gt;api.register&lt;/code&gt;, &lt;code&gt;api.login&lt;/code&gt;, &lt;code&gt;api.searchProfiles&lt;/code&gt;, etc.) wrapping every backend call, so no page has to write raw &lt;code&gt;fetch&lt;/code&gt; logic or handle auth headers manually&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nav.js&lt;/code&gt; — a nav bar that swaps its links depending on whether you're logged in&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;style.css&lt;/code&gt; — shared design tokens (cards, buttons, forms) so every page that comes later looks consistent without repeating CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building this first meant every page after it could focus purely on its own logic instead of re-solving "how do I call the API" five separate times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then the actual auth pages&lt;/strong&gt; — registration (with a role picker that shows trade/location fields only if you're signing up as a fundi) and login. Both store the JWT and redirect to a dashboard that... doesn't exist yet. That's fine — it's the next branch, and I'd rather see an honest 404 now than pretend the flow is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually tripped me up
&lt;/h2&gt;

&lt;p&gt;Folder structure. &lt;code&gt;register.html&lt;/code&gt; and &lt;code&gt;login.html&lt;/code&gt; needed to sit in a &lt;code&gt;pages/&lt;/code&gt; subfolder, which meant every relative path inside them (&lt;code&gt;../css/style.css&lt;/code&gt;, &lt;code&gt;../js/api.js&lt;/code&gt;) had to account for being one level deeper than &lt;code&gt;index.html&lt;/code&gt;. Small thing, easy to get backwards, and a good reminder that "confusing" doesn't mean "hard" — sometimes it's just unfamiliar, and the fix is five minutes of careful checking, not a rabbit hole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow, still
&lt;/h2&gt;

&lt;p&gt;Same discipline as Phase 1: one feature branch per unit of work, each file its own commit, PR descriptions with a summary/why/testing/notes section, merged into &lt;code&gt;main&lt;/code&gt; only after local testing against the live backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Search page, then the fundi profile view, then the dashboard those auth pages are currently redirecting to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repo's public if you want to follow the commit history as it grows.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building SemaKazi Part 1: Shipping a Backend the Way Real Teams Do It</title>
      <dc:creator>Ian Ochieng</dc:creator>
      <pubDate>Fri, 31 Jul 2026 13:15:12 +0000</pubDate>
      <link>https://dev.to/iochieng1/building-semakazi-part-1-shipping-a-backend-the-way-real-teams-do-it-32da</link>
      <guid>https://dev.to/iochieng1/building-semakazi-part-1-shipping-a-backend-the-way-real-teams-do-it-32da</guid>
      <description>&lt;p&gt;Kenya has one of the youngest populations in the world, and most of that workforce works in the informal sector — electricians, carpenters, mechanics, tailors, the people we call "fundis." The problem is simple: there's no digital way for a skilled fundi to prove they're actually skilled. Clients hire based on word-of-mouth alone, which caps good workers to their existing network and lets anyone claim expertise they don't have.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;strong&gt;SemaKazi&lt;/strong&gt; is solving — a platform where fundis build a verifiable reputation through proof-of-work uploads, client ratings, and peer-endorsed skill badges.&lt;/p&gt;

&lt;p&gt;Today was Phase 1: the backend foundation. Here's what actually went into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Node.js + Express, SQLite (via &lt;code&gt;better-sqlite3&lt;/code&gt;) for the database, JWT + bcrypt for auth. Nothing exotic — the goal was a clean, working API, not a tech showcase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What got built
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data models&lt;/strong&gt;: users (fundis and clients), proof-of-work entries, reviews, and skill badges — with foreign keys and indexes where the queries actually need them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt;: register/login with hashed passwords, JWT middleware to protect routes, and an ownership check so users can only edit their own data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core endpoints&lt;/strong&gt;: profile search/view, proof-of-work CRUD, review submission, and badge endorsements&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it like a real project, not a script
&lt;/h2&gt;

&lt;p&gt;The part I actually wanted to practice wasn't the code — it was the workflow around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A proper README and PRD &lt;em&gt;before&lt;/em&gt; any code, with an explicit &lt;strong&gt;out-of-scope&lt;/strong&gt; section (no payments, no in-app chat, no admin dashboard in this version — deliberately)&lt;/li&gt;
&lt;li&gt;One feature branch per unit of work, merged via pull requests with real descriptions (summary, why, testing notes, known gaps)&lt;/li&gt;
&lt;li&gt;GitHub Issues tied to a project board, closed out as each feature merged&lt;/li&gt;
&lt;li&gt;A tagged release (&lt;code&gt;v0.1.0-phase1&lt;/code&gt;) marking the milestone on &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bug that actually taught me something
&lt;/h2&gt;

&lt;p&gt;Everything worked in my Codespace. Then I pulled the same repo onto my local machine and got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Cannot find module './routes/profiles'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turned out one route file was named &lt;code&gt;profile.js&lt;/code&gt; (singular) while the code that required it expected &lt;code&gt;profiles.js&lt;/code&gt; (plural). It had worked by accident in one environment and failed correctly in another — a good reminder that "it runs on my machine" isn't the same as "it's actually correct." Renamed the file, committed the fix, moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Phase 2 is the frontend — wiring up the UI to these endpoints and adding real media upload for proof-of-work. Phase 1 is tagged, merged, and done.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Following along? The repo's public — feel free to poke through the commit history to see how the branches came together.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>github</category>
      <category>opensource</category>
      <category>node</category>
    </item>
    <item>
      <title>How I Tamed Legacy Docker (v1.29) for Multi-Stage Microservice Builds</title>
      <dc:creator>Ian Ochieng</dc:creator>
      <pubDate>Thu, 30 Jul 2026 13:55:32 +0000</pubDate>
      <link>https://dev.to/iochieng1/how-i-tamed-legacy-docker-v129-for-multi-stage-microservice-builds-596c</link>
      <guid>https://dev.to/iochieng1/how-i-tamed-legacy-docker-v129-for-multi-stage-microservice-builds-596c</guid>
      <description>&lt;p&gt;Working with modern multi-stage Dockerfiles on legacy server environments can feel like fitting a square peg into a round hole. While updating the container infrastructure for &lt;strong&gt;flexiride-backend&lt;/strong&gt;—a monorepo running 15 microservices—I ran into a wall with a legacy Docker Engine environment (&lt;code&gt;v1.29.2&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Here is how I adapted our multi-stage build pipeline across 9 active microservices without requiring BuildKit, root privileges, or breaking our build architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Modern Multi-Stage vs. Legacy Daemon
&lt;/h2&gt;

&lt;p&gt;Our goal was straightforward: standardize &lt;code&gt;Dockerfile.base&lt;/code&gt; across our active microservices and gateways (&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;driver&lt;/code&gt;, &lt;code&gt;booking&lt;/code&gt;, &lt;code&gt;matching&lt;/code&gt;, &lt;code&gt;payment&lt;/code&gt;, &lt;code&gt;notification&lt;/code&gt;, &lt;code&gt;web-bff&lt;/code&gt;, and &lt;code&gt;mobile-bff&lt;/code&gt;) to produce consistent &lt;code&gt;:jvm&lt;/code&gt; runtime images.&lt;/p&gt;

&lt;p&gt;However, legacy Docker &lt;code&gt;v1.29.2&lt;/code&gt; presented three hard constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No BuildKit Support:&lt;/strong&gt; Modern caching mechanisms like &lt;code&gt;--mount=type=cache&lt;/code&gt; failed instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequential Target Evaluation:&lt;/strong&gt; Without BuildKit's smart DAG evaluation, legacy Docker couldn't skip late-stage GraalVM native image compilation steps, even when we only requested the intermediate JVM runtime target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restricted Environment:&lt;/strong&gt; No root privileges were available to upgrade the underlying local engine or daemon.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Workarounds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Reordering &lt;code&gt;Dockerfile.base&lt;/code&gt; Targets
&lt;/h3&gt;

&lt;p&gt;Because legacy Docker processes build stages sequentially, putting heavy or optional downstream stages first forces the builder to run them regardless of target flags. &lt;/p&gt;

&lt;p&gt;By &lt;strong&gt;reordering &lt;code&gt;Dockerfile.base&lt;/code&gt; to prioritize JVM runtime stages before GraalVM steps&lt;/strong&gt;, the build pipeline completes the JVM image cleanly before hitting unsupported toolchains.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Explicit Stage Targets in Docker Compose
&lt;/h3&gt;

&lt;p&gt;To force legacy Docker to stop cleanly at the JVM stage, I declared explicit build targets across &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.base&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt; &lt;span class="c1"&gt;# Explicitly targets JVM runtime stage on legacy engines&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;flexirides/auth:jvm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stopped legacy Docker from executing past the JVM stage and kept image naming completely standardized across services (&lt;code&gt;flexirides/&amp;lt;service-name&amp;gt;:jvm&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification &amp;amp; Results
&lt;/h2&gt;

&lt;p&gt;After updating the build definitions, all 9 microservices compiled, tagged, and ran successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tagged Image Consistency
&lt;/h3&gt;

&lt;p&gt;We verified that all 9 images compiled completely without errors and followed standardized naming conventions (&lt;code&gt;flexirides/&amp;lt;service-name&amp;gt;:jvm&lt;/code&gt;):&lt;br&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%2F1tbqlndpyxcbuf6770g7.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%2F1tbqlndpyxcbuf6770g7.png" alt="Compiled Docker Images List" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Service Bootstrapping
&lt;/h3&gt;

&lt;p&gt;Spun up the &lt;code&gt;auth&lt;/code&gt; microservice using &lt;code&gt;docker-compose up -d auth&lt;/code&gt; and monitored its transition to an &lt;code&gt;Up (healthy)&lt;/code&gt; state:&lt;br&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%2Fu73y1ee0v0tt8baz61q3.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%2Fu73y1ee0v0tt8baz61q3.png" alt="Docker Container Status" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Spring Boot Actuator Health Check
&lt;/h3&gt;

&lt;p&gt;Finally, we queried the service's health endpoint (&lt;code&gt;GET http://localhost:8086/actuator/health&lt;/code&gt;) to confirm database connectivity and application readiness:&lt;br&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%2Fbb8cmv6w9x96am8ias91.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%2Fbb8cmv6w9x96am8ias91.png" alt="Spring Actuator Health Check" width="800" height="321"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UP"&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;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stage ordering matters&lt;/strong&gt; on older Docker daemons that lack BuildKit's dynamic target skipping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit &lt;code&gt;target&lt;/code&gt; declarations&lt;/strong&gt; in &lt;code&gt;docker-compose.yml&lt;/code&gt; prevent legacy engines from running unnecessary build stages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always validate microservice readiness&lt;/strong&gt; with runtime health checks (like Spring Actuator) rather than assuming a successful &lt;code&gt;docker build&lt;/code&gt; means a working container.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have you had to support legacy Docker engines in your production environments? How did you handle multi-stage builds? Let's discuss below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>java</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
