<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Serge Matveenko</title>
    <description>The latest articles on Forem by Serge Matveenko (@justlig).</description>
    <link>https://forem.com/justlig</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%2F1059058%2F711b05e4-504d-415a-98c1-6d44441d1abd.png</url>
      <title>Forem: Serge Matveenko</title>
      <link>https://forem.com/justlig</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/justlig"/>
    <language>en</language>
    <item>
      <title>PHP Punishes You for Writing Good Code</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Thu, 27 Nov 2025 21:04:30 +0000</pubDate>
      <link>https://forem.com/justlig/php-punishes-you-for-writing-good-code-2j6g</link>
      <guid>https://forem.com/justlig/php-punishes-you-for-writing-good-code-2j6g</guid>
      <description>&lt;p&gt;Most programming languages guide you toward good code.&lt;/p&gt;

&lt;p&gt;PHP… doesn’t.&lt;/p&gt;

&lt;p&gt;In fact, PHP often feels like it actively &lt;em&gt;punishes&lt;/em&gt; you for trying to write clean, safe, maintainable software — while rewarding shortcuts that age horribly in production.&lt;/p&gt;

&lt;p&gt;This isn’t about syntax preferences or “PHP vs X” flamewars.&lt;br&gt;
It’s about &lt;strong&gt;direction of least resistance&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In good languages, writing good code is the easiest path.&lt;br&gt;
In bad languages, writing bad code is the easiest path.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP is a masterclass in the second category.&lt;/p&gt;


&lt;h2&gt;
  
  
  Direction of Least Resistance
&lt;/h2&gt;

&lt;p&gt;Every language nudges you in a certain direction.&lt;/p&gt;

&lt;p&gt;Some push you toward correctness.&lt;br&gt;
Some push you toward chaos.&lt;/p&gt;

&lt;p&gt;The language you use shapes your habits — and often your codebase’s long-term fate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rust: Least resistance → correctness
&lt;/h3&gt;

&lt;p&gt;Rust actively fights you when your design is unsafe.&lt;/p&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;forget ownership rules,&lt;/li&gt;
&lt;li&gt;share mutable state incorrectly,&lt;/li&gt;
&lt;li&gt;misuse lifetimes,&lt;/li&gt;
&lt;li&gt;ignore concurrency safety,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…it refuses to compile.&lt;/p&gt;

&lt;p&gt;Rust doesn’t trust you — and that’s a &lt;em&gt;feature&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Yes, it’s harder to write code at first.&lt;br&gt;
But the language is aligned with your &lt;em&gt;intent&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Good design is the shortest path to success.&lt;/p&gt;


&lt;h3&gt;
  
  
  Python: Least resistance → readability
&lt;/h3&gt;

&lt;p&gt;Python is permissive — but not anarchic.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; write awful Python, but you must work at it.&lt;/p&gt;

&lt;p&gt;Idiomatic Python naturally pushes you toward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple structures,&lt;/li&gt;
&lt;li&gt;predictable flow,&lt;/li&gt;
&lt;li&gt;readable naming,&lt;/li&gt;
&lt;li&gt;explicit behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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="c1"&gt;# Python: obvious, readable, boring (perfect)
&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trying to outsmart Python usually makes your code uglier, not shorter — which nudges you back toward clarity.&lt;/p&gt;




&lt;h3&gt;
  
  
  PHP: Least resistance → disaster
&lt;/h3&gt;

&lt;p&gt;PHP’s “happy path” is often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global state&lt;/li&gt;
&lt;li&gt;weak typing&lt;/li&gt;
&lt;li&gt;silent failures&lt;/li&gt;
&lt;li&gt;inconsistent APIs&lt;/li&gt;
&lt;li&gt;magical behavior&lt;/li&gt;
&lt;li&gt;legacy defaults&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; write good PHP.&lt;/p&gt;

&lt;p&gt;But PHP makes you &lt;em&gt;work&lt;/em&gt; for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  PHP Makes Bad Code Easy
&lt;/h2&gt;

&lt;p&gt;Let’s look at what happens when you do the lazy thing in PHP.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Everything is globally accessible
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No structure.&lt;br&gt;
No boundaries.&lt;br&gt;
No encapsulation.&lt;/p&gt;

&lt;p&gt;The language encourages direct access to state from anywhere.&lt;/p&gt;

&lt;p&gt;Good design requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request objects&lt;/li&gt;
&lt;li&gt;input validation layers&lt;/li&gt;
&lt;li&gt;parameter objects&lt;/li&gt;
&lt;li&gt;DTOs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad design requires… nothing.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Type safety is optional (and mostly ignored)
&lt;/h3&gt;

&lt;p&gt;PHP &lt;em&gt;has&lt;/em&gt; types.&lt;/p&gt;

&lt;p&gt;The ecosystem largely ignores them.&lt;/p&gt;

&lt;p&gt;By default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What are &lt;code&gt;$a&lt;/code&gt; and &lt;code&gt;$b&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Integers?&lt;br&gt;
Floats?&lt;br&gt;
Strings?&lt;br&gt;
Objects with &lt;code&gt;__toString()&lt;/code&gt;?&lt;br&gt;
Null?&lt;/p&gt;

&lt;p&gt;Good PHP requires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every call site must cooperate,&lt;/li&gt;
&lt;li&gt;legacy code breaks,&lt;/li&gt;
&lt;li&gt;frameworks fight you,&lt;/li&gt;
&lt;li&gt;plugins betray you,&lt;/li&gt;
&lt;li&gt;runtime errors appear instead of coercion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PHP punishes you for being strict.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Errors default to… ignoring errors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the file doesn’t exist?&lt;/li&gt;
&lt;li&gt;the request fails?&lt;/li&gt;
&lt;li&gt;permissions are wrong?&lt;/li&gt;
&lt;li&gt;timeout occurs?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The function can fail silently.&lt;/p&gt;

&lt;p&gt;No exception.&lt;br&gt;
No structured error.&lt;br&gt;
Just &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now every clean implementation requires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Failed to fetch file"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, the “dirty” version works… until it doesn't.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Frameworks simulate what the language doesn’t provide
&lt;/h3&gt;

&lt;p&gt;Why do PHP frameworks feel so &lt;em&gt;heavy&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Because they fake:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type systems&lt;/li&gt;
&lt;li&gt;DI containers&lt;/li&gt;
&lt;li&gt;routing DSLs&lt;/li&gt;
&lt;li&gt;validation layers&lt;/li&gt;
&lt;li&gt;request lifecycles&lt;/li&gt;
&lt;li&gt;security models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are duct tape on a language that refuses to grow a spine.&lt;/p&gt;

&lt;p&gt;And when you follow best practices?&lt;br&gt;
You fight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configuration hell&lt;/li&gt;
&lt;li&gt;annotation magic&lt;/li&gt;
&lt;li&gt;runtime surprises&lt;/li&gt;
&lt;li&gt;reflection hacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “right” way is the hardest way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cargo Cult vs First-Class Safety
&lt;/h2&gt;

&lt;p&gt;Rust and Python make correctness normal.&lt;/p&gt;

&lt;p&gt;PHP makes correctness custom.&lt;/p&gt;

&lt;p&gt;In PHP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing is extra&lt;/li&gt;
&lt;li&gt;type safety is optional&lt;/li&gt;
&lt;li&gt;architecture is emulated&lt;/li&gt;
&lt;li&gt;tooling is external&lt;/li&gt;
&lt;li&gt;contracts are implied&lt;/li&gt;
&lt;li&gt;discipline is manual&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PHP works great if you don’t care deeply about software engineering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And becomes expensive when you do.&lt;/p&gt;




&lt;h2&gt;
  
  
  So Why Is PHP Popular?
&lt;/h2&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;p&gt;✅ You can deploy something in 10 minutes&lt;br&gt;
✅ You don’t need a build system&lt;br&gt;
✅ You don’t need to understand types&lt;br&gt;
✅ You don’t need architecture&lt;br&gt;
✅ You don’t need experience&lt;br&gt;
✅ You don’t need discipline&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;❌ You pay for all of that later.&lt;/p&gt;

&lt;p&gt;Popularity correlates strongly with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low barrier to entry,&lt;/li&gt;
&lt;li&gt;cheap prototypes,&lt;/li&gt;
&lt;li&gt;instant gratification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long-term maintainability,&lt;/li&gt;
&lt;li&gt;safety,&lt;/li&gt;
&lt;li&gt;correctness.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Most Important Question
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Maybe this is the real reason some languages are popular&lt;br&gt;
— and others are trusted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP optimizes for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shipping now&lt;/li&gt;
&lt;li&gt;understanding later&lt;/li&gt;
&lt;li&gt;fixing in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Languages like Rust optimize for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;thinking now&lt;/li&gt;
&lt;li&gt;shipping safely&lt;/li&gt;
&lt;li&gt;trusting code later&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Take
&lt;/h2&gt;

&lt;p&gt;PHP does not force you to write bad code.&lt;/p&gt;

&lt;p&gt;It just gently rewards you for it.&lt;/p&gt;

&lt;p&gt;And the better you try to be…&lt;br&gt;
the more friction you feel.&lt;/p&gt;

</description>
      <category>php</category>
      <category>programming</category>
      <category>rust</category>
      <category>python</category>
    </item>
    <item>
      <title>Versioned Deploys for Flutter Web: Fast Loads, Instant Updates</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Fri, 07 Nov 2025 11:20:17 +0000</pubDate>
      <link>https://forem.com/justlig/versioned-deploys-for-flutter-web-fast-loads-instant-updates-3o8p</link>
      <guid>https://forem.com/justlig/versioned-deploys-for-flutter-web-fast-loads-instant-updates-3o8p</guid>
      <description>&lt;p&gt;Flutter Web apps are fast—until the browser cache gets in the way. Anyone who’s deployed a new version knows the pain: users stuck on stale bundles, missing features, or broken screens until they hit a hard refresh.&lt;/p&gt;

&lt;p&gt;This post walks through a proven &lt;strong&gt;versioned deploy pattern for Flutter Web&lt;/strong&gt;, using &lt;strong&gt;Nginx&lt;/strong&gt; and simple cache rules to make updates instant and repeat loads nearly instantaneous.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This approach is especially critical for &lt;strong&gt;embedded Flutter Web apps&lt;/strong&gt; like &lt;strong&gt;Telegram Mini Apps&lt;/strong&gt; or other environments that heavily cache web views. Platforms such as Telegram can aggressively cache app bundles—sometimes so persistently that developers need to completely reinstall the app to clear stale versions. Using versioned deploys ensures updates reach users immediately, even in these restrictive caching environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How It All Works
&lt;/h2&gt;

&lt;p&gt;Before we dive deeper, let’s unpack what’s happening under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browsers love caching.&lt;/strong&gt; When they see a file like &lt;code&gt;main.dart.js&lt;/code&gt;, they assume it rarely changes and keep serving it from local storage. That’s great for speed—until you ship an update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter builds are static bundles.&lt;/strong&gt; After &lt;code&gt;flutter build web&lt;/code&gt;, you get JavaScript (&lt;code&gt;main.dart.js&lt;/code&gt;), WebAssembly (&lt;code&gt;canvaskit.wasm&lt;/code&gt;), and other assets. These are ideal for long-term caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx controls caching via HTTP headers.&lt;/strong&gt; By attaching &lt;code&gt;Cache-Control&lt;/code&gt; and &lt;code&gt;immutable&lt;/code&gt; directives, we tell browsers how long to keep files and when to refetch them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version folders act as unique cache keys.&lt;/strong&gt; Instead of trying to invalidate caches, we generate a new folder name per release (like &lt;code&gt;/v1.0.1/&lt;/code&gt;). The browser automatically fetches the new files since the URLs have changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On your first visit, you might download &lt;code&gt;/v1.0.0/main.dart.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;After a new deploy, the page now references &lt;code&gt;/v1.0.1/main.dart.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The browser sees it as a totally new file and fetches it fresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This technique avoids conflicts between versions and keeps old assets available for rollbacks or debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Flutter Web Needs This
&lt;/h2&gt;

&lt;p&gt;When you build Flutter for the web, it produces a set of files like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;main.dart.js
flutter_service_worker.js
assets/
canvaskit/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They’re large but static—perfect for long-term caching. The problem? Browsers keep serving the old ones after each deploy, even when your app should update.&lt;/p&gt;

&lt;p&gt;We fix that by separating &lt;strong&gt;mutable entry points&lt;/strong&gt; from &lt;strong&gt;immutable versioned assets&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entry Points (never cached)         Versioned Assets (aggressively cached)
├─ index.html                       ├─ /v1.0.0/main.dart.js
├─ config.json                      ├─ /v1.0.0/flutter_service_worker.js
└─ flutter_bootstrap.js             └─ /v1.0.0/canvaskit/canvaskit.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry points&lt;/strong&gt; — lightweight files that load the app. Always fetched fresh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned assets&lt;/strong&gt; — compiled Flutter bundles served from a versioned folder like &lt;code&gt;/v1.0.0/&lt;/code&gt;. Cached for a year with &lt;code&gt;immutable&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each new deploy introduces a new folder, effectively cache-busting automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bootstrap Logic
&lt;/h2&gt;

&lt;p&gt;The bootstrap script handles version discovery and initializes Flutter with the correct paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/config.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;assetBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;entrypointBaseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading Flutter app from:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;_flutter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userConfig&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bootstrap error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  config.json
&lt;/h3&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apiUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com"&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;p&gt;Every deploy updates the version field. The Flutter loader then fetches from the corresponding folder.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nginx Configuration
&lt;/h2&gt;

&lt;p&gt;Here’s the minimal Nginx setup to make this work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Never cache entry points&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/index.html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"no-store,&lt;/span&gt; &lt;span class="s"&gt;no-cache,&lt;/span&gt; &lt;span class="s"&gt;must-revalidate,&lt;/span&gt; &lt;span class="s"&gt;proxy-revalidate,&lt;/span&gt; &lt;span class="s"&gt;max-age=0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/config.json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"no-store,&lt;/span&gt; &lt;span class="s"&gt;no-cache,&lt;/span&gt; &lt;span class="s"&gt;must-revalidate,&lt;/span&gt; &lt;span class="s"&gt;proxy-revalidate,&lt;/span&gt; &lt;span class="s"&gt;max-age=0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/flutter_bootstrap.js&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"no-store,&lt;/span&gt; &lt;span class="s"&gt;no-cache,&lt;/span&gt; &lt;span class="s"&gt;must-revalidate,&lt;/span&gt; &lt;span class="s"&gt;proxy-revalidate,&lt;/span&gt; &lt;span class="s"&gt;max-age=0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Aggressively cache versioned assets&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;^/([^/]+)/(.*)$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$req_version&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$asset_path&lt;/span&gt; &lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$req_version&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="nv"&gt;$active_version&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;max-age=31536000,&lt;/span&gt; &lt;span class="s"&gt;immutable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$asset_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$active_version&lt;/code&gt; can be set via an environment variable or included file so Nginx knows which version is current.&lt;/p&gt;

&lt;p&gt;This ensures only the active version is served, preventing old assets from being reused after deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache Behavior
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Entry Points
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Always fetched from the network.&lt;/li&gt;
&lt;li&gt;Detects new versions immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Versioned Assets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache-Control: public, max-age=31536000, immutable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Cached for one year.&lt;/li&gt;
&lt;li&gt;Never revalidated while version remains the same.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New version = new folder = automatic cache bust.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Users Experience
&lt;/h2&gt;

&lt;h3&gt;
  
  
  First Visit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Loads entry points and versioned assets.&lt;/li&gt;
&lt;li&gt;Flutter initializes normally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Second Visit (Same Version)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Entry points fetched fresh.&lt;/li&gt;
&lt;li&gt;Bulk assets loaded instantly from cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After Deployment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Entry points detect new version.&lt;/li&gt;
&lt;li&gt;App loads new version’s assets automatically.&lt;/li&gt;
&lt;li&gt;Old cached files remain but aren’t used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero downtime, no forced refresh.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use semantic versions&lt;/strong&gt; like &lt;code&gt;v1.2.3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep entry points small&lt;/strong&gt; for fast version detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate versions in Nginx&lt;/strong&gt; to avoid stale assets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log the version&lt;/strong&gt; in the browser console for debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purge or rotate old folders&lt;/strong&gt; occasionally to save storage.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Version in URL = cache key&lt;/strong&gt; — instant cache busting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immutable assets&lt;/strong&gt; — best‑case caching for performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fresh entry points&lt;/strong&gt; — ensures the browser always learns about updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server‑side validation&lt;/strong&gt; — protects against stale or mismatched bundles.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This simple pattern turns Flutter Web deploys from a headache into a smooth, predictable process—all with standard Nginx and a few lines of JavaScript.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>flutter</category>
      <category>nginx</category>
      <category>devops</category>
    </item>
    <item>
      <title>Podman on GitLab CI: Fast, Efficient Container Builds — No DinD Required</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Thu, 16 Oct 2025 08:18:15 +0000</pubDate>
      <link>https://forem.com/justlig/podman-on-gitlab-ci-fast-efficient-container-builds-no-dind-required-e2e</link>
      <guid>https://forem.com/justlig/podman-on-gitlab-ci-fast-efficient-container-builds-no-dind-required-e2e</guid>
      <description>&lt;p&gt;If you’re still relying on Docker-in-Docker (DinD) for container builds in GitLab CI, there’s a cleaner, faster way: &lt;strong&gt;Podman&lt;/strong&gt;. Combined with &lt;strong&gt;GitLab’s cache&lt;/strong&gt;, Podman lets you use &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; just like on your local machine — without a privileged Docker service. This approach gives you rootless, reproducible builds that efficiently reuse dependency caches across pipelines.&lt;/p&gt;

&lt;p&gt;Inspired by my tiny repo &lt;a href="https://gitlab.com/lig/coredns-zoner" rel="noopener noreferrer"&gt;lig/coredns-zoner&lt;/a&gt;, this guide generalizes the setup for popular languages and package managers. We’ll also note one caveat about GitLab.com’s cache implementation and how a self-managed GitLab can provide even faster caching.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Use Podman in CI?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Docker-in-Docker service&lt;/strong&gt;: Build inside a single container image (like &lt;code&gt;quay.io/podman/stable&lt;/code&gt;) — no privileged daemon required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rootless operation&lt;/strong&gt;: Works seamlessly with GitLab’s container executor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native caching&lt;/strong&gt;: Podman/Buildah natively support &lt;code&gt;RUN --mount=type=cache&lt;/code&gt;, letting you reuse dependency caches across builds.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; Speeds Up Builds
&lt;/h2&gt;

&lt;p&gt;When you use &lt;code&gt;RUN --mount=type=cache,target=/some/dir&lt;/code&gt; in your Containerfile, the specified directory persists across builds. This avoids redownloading dependencies and refetching packages every run.&lt;/p&gt;

&lt;p&gt;Common examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go: &lt;code&gt;/go/pkg/mod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rust: &lt;code&gt;/cargo/registry&lt;/code&gt;, &lt;code&gt;/cargo/git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Node: &lt;code&gt;/root/.npm&lt;/code&gt; or &lt;code&gt;/usr/local/share/.cache/yarn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;OS packages: &lt;code&gt;/var/cache/apk&lt;/code&gt;, &lt;code&gt;/var/cache/apt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These caches are available only during the &lt;code&gt;RUN&lt;/code&gt; step — they’re not stored in the image itself, just reused between builds to save time.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real-World Example
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://gitlab.com/lig/coredns-zoner" rel="noopener noreferrer"&gt;lig/coredns-zoner&lt;/a&gt;, Podman is used with cache mounts directly in the &lt;a href="https://gitlab.com/lig/coredns-zoner/-/blob/main/Containerfile" rel="noopener noreferrer"&gt;Containerfile&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine/git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;clone&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/src/.git &lt;span class="se"&gt;\
&lt;/span&gt;    git init &lt;span class="nt"&gt;-b&lt;/span&gt; master &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git remote add origin https://github.com/coredns/coredns.git &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git remote set-url origin https://github.com/coredns/coredns.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git fetch &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--prune&lt;/span&gt; &lt;span class="nt"&gt;--auto-gc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git switch &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;git describe &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--no-abbrev&lt;/span&gt; origin/HEAD&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git reset &lt;span class="nt"&gt;--hard&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=clone /src /src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/go/pkg/mod &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;plugin.cfg,target&lt;span class="o"&gt;=&lt;/span&gt;/src/plugin.cfg,relabel&lt;span class="o"&gt;=&lt;/span&gt;shared &lt;span class="se"&gt;\
&lt;/span&gt;    go generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; go build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/coredns"]&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /src/coredns /coredns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key part: &lt;strong&gt;Git and Go caches&lt;/strong&gt; are mounted for reuse between builds — no extra scripting or hacks.&lt;/p&gt;

&lt;p&gt;The project’s &lt;a href="https://gitlab.com/lig/coredns-zoner/-/blob/main/.gitlab-ci.yml" rel="noopener noreferrer"&gt;&lt;code&gt;.gitlab-ci.yml&lt;/code&gt;&lt;/a&gt; runs Podman builds without DinD, caching Buildah’s temporary data inside the project workspace:&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;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&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;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&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;quay.io/podman/stable:latest&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TMPDIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CI_PROJECT_DIR}/.local/tmp&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p ${CI_PROJECT_DIR}/.local/tmp&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "$CI_REGISTRY_PASSWORD" | podman login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;podman build --pull --cache-from "${CI_REGISTRY_IMAGE}" -t "${CI_REGISTRY_IMAGE}" .&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;podman push "${CI_REGISTRY_IMAGE}"&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.local/tmp/buildah-cache-0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Making Cache Persistent Between Pipelines
&lt;/h2&gt;

&lt;p&gt;There are two complementary layers of caching:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GitLab CI file cache&lt;/strong&gt;&lt;br&gt;
A directory (e.g., &lt;code&gt;TMPDIR&lt;/code&gt;) is cached inside the project workspace, persisting data used by &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; between jobs and pipelines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Registry-based image cache&lt;/strong&gt;&lt;br&gt;
Podman/Buildah can also store cached layers in the container registry. Use both &lt;code&gt;--layers&lt;/code&gt; and &lt;code&gt;--cache-to&lt;/code&gt;/&lt;code&gt;--cache-from&lt;/code&gt; flags to enable it:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   podman build &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--layers&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--cache-to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image,ref&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CI_REGISTRY_IMAGE&lt;/span&gt;&lt;span class="s2"&gt;:build-cache"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--cache-from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image,ref&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CI_REGISTRY_IMAGE&lt;/span&gt;&lt;span class="s2"&gt;:build-cache"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CI_REGISTRY_IMAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;strong&gt;both methods&lt;/strong&gt; — file cache for dependency directories and registry cache for build layers — yields the best speed gains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Containerfile Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Go modules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/go/pkg/mod go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/go/pkg/mod &lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /out/app ./cmd/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rust (cargo)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rust:1-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; CARGO_HOME=/cargo&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /cargo/git /cargo/registry
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Cargo.toml Cargo.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/cargo/registry &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/cargo/git cargo fetch
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/cargo/registry &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/cargo/git cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Node (npm)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm npm ci &lt;span class="nt"&gt;--prefer-offline&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Alpine packages (apk)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/var/cache/apk apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; build-base git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚙️ Cache mounts exist only during the &lt;code&gt;RUN&lt;/code&gt; step. If you need artifacts created in a cached directory, copy them elsewhere within the same &lt;code&gt;RUN&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Practical Notes Before Setting Up CI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TMPDIR&lt;/code&gt;&lt;/strong&gt;** controls Buildah’s cache location**. Any &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; targets are automatically stored there — no Containerfile changes are required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep **&lt;/strong&gt;*&lt;strong&gt;&lt;em&gt;&lt;code&gt;TMPDIR&lt;/code&gt;&lt;/em&gt;&lt;/strong&gt;***** inside your project workspace** so GitLab can cache it between jobs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable layer caching&lt;/strong&gt; by using &lt;code&gt;--layers&lt;/code&gt; with &lt;code&gt;--cache-to/--cache-from&lt;/code&gt; for a portable, registry-backed build cache.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Generic &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;variables&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;$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA&lt;/span&gt;
  &lt;span class="na"&gt;CACHE_IMAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_REGISTRY_IMAGE:build-cache&lt;/span&gt;
  &lt;span class="na"&gt;TMPDIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CI_PROJECT_DIR}/.ci/tmp&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quay.io/podman/stable:latest&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p "$TMPDIR"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "$CI_REGISTRY_PASSWORD" | podman login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;podman build --pull --layers \&lt;/span&gt;
        &lt;span class="s"&gt;--cache-from=type=image,ref="$CACHE_IMAGE" \&lt;/span&gt;
        &lt;span class="s"&gt;--cache-to=type=image,ref="$CACHE_IMAGE" \&lt;/span&gt;
        &lt;span class="s"&gt;-t "$IMAGE" .&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;podman tag "$IMAGE" "$CI_REGISTRY_IMAGE:latest"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;podman push "$IMAGE"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;podman push "$CI_REGISTRY_IMAGE:latest"&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.ci/tmp/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Advantages
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Docker-in-Docker&lt;/strong&gt;: Podman runs natively within a single container, eliminating DinD complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-like caching&lt;/strong&gt;: The same &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; behavior you use locally now works seamlessly in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry cache support&lt;/strong&gt;: Combine GitLab cache with Podman’s registry cache for best performance.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Caveat: GitLab.com Cache Behavior
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;GitLab.com’s shared runners&lt;/strong&gt;, caches are &lt;strong&gt;zipped and uploaded to S3&lt;/strong&gt; after each job and restored before the next. While reliable, this process can add extra time for large caches (compression and transfer overhead).&lt;/p&gt;

&lt;h3&gt;
  
  
  A Faster Option for Self-Managed GitLab
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;self-managed GitLab&lt;/strong&gt;, you can mount your S3 storage via &lt;strong&gt;FUSE&lt;/strong&gt; (or another network filesystem). This avoids compression entirely, making caches behave like live directories and dramatically speeding up builds — especially with large dependency sets.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Setup Checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist to ensure your Podman + GitLab CI caching setup works efficiently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prepare the environment&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Use a CI image that includes &lt;code&gt;podman&lt;/code&gt; (e.g., &lt;code&gt;quay.io/podman/stable&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Ensure your GitLab runner supports the container executor (no privileged DinD needed).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set up workspace caching&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Define &lt;code&gt;TMPDIR=${CI_PROJECT_DIR}/.ci/tmp&lt;/code&gt; in your CI variables.&lt;/li&gt;
&lt;li&gt;Create the directory in &lt;code&gt;before_script&lt;/code&gt; with &lt;code&gt;mkdir -p "$TMPDIR"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add it to &lt;code&gt;cache: paths:&lt;/code&gt; in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; so the contents persist across jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configure your Containerfile&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; for dependency-heavy directories (e.g., &lt;code&gt;/go/pkg/mod&lt;/code&gt;, &lt;code&gt;/root/.npm&lt;/code&gt;, &lt;code&gt;/cargo/registry&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;You don’t need to adapt the file structure — Podman automatically caches those directories under &lt;code&gt;TMPDIR&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Optimize layer caching&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Build with &lt;code&gt;--layers&lt;/code&gt; enabled.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;--cache-to&lt;/code&gt; and &lt;code&gt;--cache-from&lt;/code&gt; pointing to your container registry (e.g., &lt;code&gt;$CI_REGISTRY_IMAGE:build-cache&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Authenticate and push&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Log into your registry with &lt;code&gt;podman login&lt;/code&gt; in the &lt;code&gt;before_script&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Push both the image and the cached layers to your registry.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Verify caching efficiency&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Check build logs for reuse of cached layers and cached mounts.&lt;/li&gt;
&lt;li&gt;Ensure cache restoration reduces dependency fetch or install times.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Understand caching trade-offs&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;GitLab.com runners zip and upload caches to S3, which may slow down large caches.&lt;/li&gt;
&lt;li&gt;In self-managed GitLab, prefer a FUSE-mounted S3 bucket or local cache directory for near-instant caching.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrap-Up
&lt;/h2&gt;

&lt;p&gt;Using Podman with GitLab CI delivers &lt;strong&gt;faster, cleaner, and rootless container builds&lt;/strong&gt; — no Docker daemon required. By combining file-based caching (via GitLab CI) and registry-based caching (via &lt;code&gt;--cache-to/--cache-from&lt;/code&gt;), you can achieve near-local build performance.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>cicd</category>
      <category>docker</category>
      <category>podman</category>
    </item>
    <item>
      <title>RSS: A Great Standard That Can’t Keep Up</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Sat, 01 Feb 2025 22:28:48 +0000</pubDate>
      <link>https://forem.com/justlig/rss-a-great-standard-that-cant-keep-up-4ibp</link>
      <guid>https://forem.com/justlig/rss-a-great-standard-that-cant-keep-up-4ibp</guid>
      <description>&lt;p&gt;RSS (&lt;a href="https://en.wikipedia.org/wiki/RSS" rel="noopener noreferrer"&gt;en.wikipedia.org/wiki/RSS&lt;/a&gt;) has long been praised as a decentralized, open standard for content syndication. It allows users to aggregate and consume updates from various sources in a unified way. However, as the web has scaled and usage patterns have evolved, RSS has become increasingly inefficient. Its core limitation? The "pull" model, which places an unsustainable burden on both servers and clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with RSS: Inefficient Polling
&lt;/h2&gt;

&lt;p&gt;At its core, RSS clients periodically check (or "poll") a feed URL to see if there are any new updates. This process occurs regardless of whether the server has new content. While this might seem trivial for a handful of users, the situation quickly deteriorates at scale. If thousands or even millions of clients continuously request updates from a server, that server must respond to each one, whether it has new content or not. The result? Unnecessary traffic, wasted resources, and eventual overload.&lt;/p&gt;

&lt;p&gt;Worse yet, the polling frequency needs to strike a balance. Poll too infrequently, and users receive delayed updates. Poll too often, and the load on the server spikes dramatically. There’s no elegant way around this trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Alternative: The Push Model of ActivityPub
&lt;/h2&gt;

&lt;p&gt;ActivityPub (&lt;a href="https://en.wikipedia.org/wiki/ActivityPub" rel="noopener noreferrer"&gt;en.wikipedia.org/wiki/ActivityPub&lt;/a&gt;), an open standard for federated social networking, takes a fundamentally different approach. Instead of having clients constantly check for updates, it uses a "push" model. When new content is published, the server notifies interested clients (or other federated servers) only when necessary. No polling, no wasted requests—just efficient, on-demand content distribution.&lt;/p&gt;

&lt;p&gt;With ActivityPub, no work is done when there are no updates. The server only engages when something new is published, sending notifications to subscribers efficiently. Additionally, the server can handle updates asynchronously, allowing it to plan resource distribution more effectively while notifying clients. This drastically reduces server load, making it far more scalable than RSS, especially in high-traffic environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why RSS Can’t Keep Up
&lt;/h2&gt;

&lt;p&gt;Despite its strengths—openness, decentralization, and ease of implementation—RSS struggles under modern internet-scale loads. The inefficiencies of its polling mechanism make it unsuitable for large-scale deployments where real-time updates and minimal resource consumption are critical.&lt;/p&gt;

&lt;p&gt;This doesn’t mean RSS has no place; it’s still useful for niche applications, small-scale syndication, and personal use. But for platforms handling significant traffic, push-based alternatives like ActivityPub provide a more efficient and scalable solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Content Distribution
&lt;/h2&gt;

&lt;p&gt;As the internet continues to grow, so too must our tools for content distribution. While RSS was revolutionary in its time, the modern web demands smarter, more scalable solutions. Push-based protocols like ActivityPub represent the next step in decentralized content sharing—one that eliminates unnecessary load and ensures updates are delivered efficiently. Future standards could extend this push model even further, allowing closer integration with clients by building on top of ActivityPub’s efficient server-to-server communication, enabling more seamless, real-time updates without excessive resource consumption.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;RSS isn’t dead, but in an era where scalability matters more than ever, its limitations are impossible to ignore.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rss</category>
      <category>activitypub</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Object-Relational Impedance Mismatch in Database Schema and API Integration</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Mon, 19 Jun 2023 10:25:37 +0000</pubDate>
      <link>https://forem.com/justlig/the-object-relational-impedance-mismatch-in-database-schema-and-api-integration-5afb</link>
      <guid>https://forem.com/justlig/the-object-relational-impedance-mismatch-in-database-schema-and-api-integration-5afb</guid>
      <description>&lt;p&gt;When designing a database-driven application with an accompanying API, developers often encounter the challenge known as the object-relational impedance mismatch. While there is a desire to use the same schema for both components, this mismatch requires additional measures to ensure data security and authorized access. In this blog post, we explore the impact of the object-relational impedance mismatch and the need for a middle layer between the database and API, dispelling the notion that a seamless sharing of schema is feasible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Promise of Consistency
&lt;/h2&gt;

&lt;p&gt;Using a unified schema for the database and API holds the promise of consistency, reducing duplication of efforts and simplifying development. Frameworks like Django Rest Framework provide tools to address the object-relational impedance mismatch and achieve a certain level of cohesion by mapping between object-oriented models and relational database structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrappers and Guards
&lt;/h2&gt;

&lt;p&gt;However, the object-relational impedance mismatch necessitates additional considerations beyond schema alignment. Merely relying on the shared schema is insufficient to address issues related to data filtering and authorized access. Wrappers and guards must be implemented to bridge the gap and mitigate the challenges posed by the mismatch. These components act as intermediaries, addressing the differences in structure and behavior between the object-oriented model and the relational database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering Internal Data
&lt;/h2&gt;

&lt;p&gt;The object-relational impedance mismatch often requires filtering or sanitizing the data exposed through the API. Certain internal data or sensitive information may not be suitable for public consumption. By incorporating a middle layer, developers can address this mismatch and apply filters and transformations to ensure that only relevant and appropriate information is exposed through the API. This middle layer acts as a bridge, harmonizing the differences between the object-oriented representation and the underlying relational database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ensuring Authorized Access
&lt;/h2&gt;

&lt;p&gt;Controlling access to the data is a critical consideration in the presence of the object-relational impedance mismatch. The API should only provide authorized users with the appropriate level of access to the database. The middle layer, acting as a translator between the object-oriented model and the database, can implement robust authentication and authorization mechanisms. By verifying user credentials, roles, and permissions, the middle layer ensures that only authorized users gain access to the requested data, mitigating the challenges presented by the mismatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixing Semantics and API
&lt;/h2&gt;

&lt;p&gt;Furthermore, incorporating semantic data into the API introduces further complexity in the context of the object-relational impedance mismatch. Semantic data adds meaning and context to the information, enabling more sophisticated interactions and intelligent processing. However, this often requires additional layers of abstraction and customization that may not align with the structure of the shared schema. As a result, accommodating semantic data becomes challenging when trying to seamlessly share the same schema for both database access and API consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The object-relational impedance mismatch presents challenges that extend beyond schema alignment in the integration of a database schema and API. While using the same schema holds appeal for its potential simplicity, the need for a middle layer becomes apparent. Wrappers, guards, and additional logic address issues such as data filtering, authorized access, and the incorporation of semantic data. By acknowledging and mitigating the object-relational impedance mismatch, developers can strike a balance between efficiency and security, delivering robust and scalable applications that harmoniously handle the challenges presented by the mismatch.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>api</category>
      <category>rest</category>
    </item>
    <item>
      <title>Tabs vs Spaces Divorce: Judge Rules</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Mon, 12 Jun 2023 23:20:26 +0000</pubDate>
      <link>https://forem.com/justlig/tabs-vs-spaces-divorce-judge-rules-3jan</link>
      <guid>https://forem.com/justlig/tabs-vs-spaces-divorce-judge-rules-3jan</guid>
      <description>&lt;p&gt;Judge: "So, Mrs. Doe, please explain why you're seeking a divorce from your spouse, Mr. Doe."&lt;/p&gt;

&lt;p&gt;Mrs. Doe: "Your Honor, I never thought I'd say this, but it's because of the tabs!"&lt;/p&gt;

&lt;p&gt;Judge: "Tabs? Could you please elaborate?"&lt;/p&gt;

&lt;p&gt;Mrs. Doe: "Certainly, Your Honor. We're both software engineers, and I've always been a firm believer in using spaces for indentation in my code. But Mr. Doe, well, he's a tab enthusiast."&lt;/p&gt;

&lt;p&gt;Judge: "Ah, I see. And that's a deal-breaker for you?"&lt;/p&gt;

&lt;p&gt;Mrs. Doe: "Absolutely, Your Honor. I mean, we've had our differences in the past, like curly braces placement and even semicolon usage, but this tab thing is just too much to handle. It's like trying to decipher hieroglyphics every time I look at his code!"&lt;/p&gt;

&lt;p&gt;Judge: "Interesting. And have you attempted to compromise on this matter?"&lt;/p&gt;

&lt;p&gt;Mrs. Doe: "Oh, we've tried, Your Honor. We've even attended counseling sessions, but we just can't seem to find a common ground. It's like living in two separate code universes."&lt;/p&gt;

&lt;p&gt;Judge: "I can understand the frustration, Mrs. Doe. Code formatting can be a contentious issue. Is there anything else you'd like to add?"&lt;/p&gt;

&lt;p&gt;Mrs. Doe: "Only that this divorce isn't just about our personal lives, Your Honor. It's about the future of clean and readable code. I can't let our disagreement over tabs ruin the harmony of my syntax!"&lt;/p&gt;

&lt;p&gt;Judge: "Very well, Mrs. Doe. I believe I have a good grasp of the situation. We'll proceed accordingly. And may I suggest, for the sake of your professional sanity, you both consider adopting an agreed-upon code style in future relationships?"&lt;/p&gt;

&lt;p&gt;Mrs. Doe: "Thank you, Your Honor. I'll keep that in mind. But for now, I need to move on to someone who understands the importance of proper indentation."&lt;/p&gt;

&lt;p&gt;Judge: "Understandable, Mrs. Doe. The court will take your request into consideration. Case adjourned."&lt;/p&gt;

&lt;p&gt;Remember, it's just a joke, and code style preferences should never be taken as grounds for a real divorce!&lt;/p&gt;

</description>
      <category>jokes</category>
      <category>softwareengineering</category>
      <category>codestyle</category>
    </item>
    <item>
      <title>The Nuance of Code Style: Emphasizing Return Value Significance in Python</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Wed, 31 May 2023 12:18:34 +0000</pubDate>
      <link>https://forem.com/justlig/the-nuance-of-code-style-emphasizing-return-value-significance-in-python-2k9m</link>
      <guid>https://forem.com/justlig/the-nuance-of-code-style-emphasizing-return-value-significance-in-python-2k9m</guid>
      <description>&lt;p&gt;In the world of programming, code style plays a crucial role in making code readable, maintainable, and understandable for developers. While adhering to established coding conventions is essential, there are often nuanced decisions that arise when it comes to certain aspects of code, such as return statements. In this blog post, we'll explore an interesting code style nuance related to return statements and discuss when it might be better to use the bare return as opposed to explicitly returning a value.&lt;/p&gt;

&lt;p&gt;Consider the following code snippet:&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;def&lt;/span&gt; &lt;span class="nf"&gt;odd_or_none&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the function &lt;code&gt;odd_or_none&lt;/code&gt; takes an integer value as input and determines if it's odd or even. If the value is even, the function explicitly returns &lt;code&gt;None&lt;/code&gt;, indicating that the resulting value is meaningful within the context of the program's logic. However, if the value is odd, it returns the value itself.&lt;/p&gt;

&lt;p&gt;The rationale behind this approach is to differentiate between scenarios where the return value has no particular meaning versus cases where the return value holds significance for downstream logic.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;return None&lt;/code&gt; explicitly in the case of an even value signifies that the resulting None will be used in subsequent code, conveying a specific intention. On the other hand, in methods like &lt;code&gt;__init__&lt;/code&gt; or any other method that always returns &lt;code&gt;None&lt;/code&gt; by convention, the bare &lt;code&gt;return&lt;/code&gt; statement emphasizes the fact that the result is not going to be used.&lt;/p&gt;

&lt;p&gt;Upon reflection, following a consistent code style policy can enhance code readability and clarity. One such policy, which can be employed for functions that have a return type of &lt;code&gt;None&lt;/code&gt;, is as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For functions that return &lt;code&gt;None&lt;/code&gt;, use the bare &lt;code&gt;return&lt;/code&gt; statement. This indicates that the function does not return anything and that the resulting value is of no particular significance.&lt;/p&gt;

&lt;p&gt;For functions that return either &lt;code&gt;None&lt;/code&gt; or another value (&lt;code&gt;None | Some&lt;/code&gt;), use an explicit &lt;code&gt;return None&lt;/code&gt; statement. This signifies that the function's return value will be utilized in subsequent code, conveying its relevance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By adhering to this policy, the codebase becomes more consistent, enabling developers to easily grasp the intent behind different return statements and their implications.&lt;/p&gt;

</description>
      <category>python</category>
      <category>codestyle</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python is broken? Strings are sequences of strings 🤦</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Thu, 04 May 2023 08:03:42 +0000</pubDate>
      <link>https://forem.com/justlig/python-is-broken-strings-are-sequences-of-strings-46p1</link>
      <guid>https://forem.com/justlig/python-is-broken-strings-are-sequences-of-strings-46p1</guid>
      <description>&lt;p&gt;This is a piece of Python code that works, and no linter will complain about it.&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;def&lt;/span&gt; &lt;span class="nf"&gt;average_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;total_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;average_length&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spam&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="c1"&gt;#&amp;gt; 3.3333333333333335
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;average_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;#&amp;gt; 1.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like &lt;code&gt;"Hello World!"&lt;/code&gt; shouldn't be a valid input for the &lt;code&gt;average_length&lt;/code&gt; function. The intent is obviously to calculate an average length of a sequence of strings like &lt;code&gt;["foo", "bar", "spam"]&lt;/code&gt;. Why is this happening?&lt;/p&gt;

&lt;p&gt;Well, an &lt;code&gt;str&lt;/code&gt; instance is a sequence of strings as well: it has length, it's iterable, and it even can be reversed (see &lt;a href="https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes" rel="noopener noreferrer"&gt;https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes&lt;/a&gt;). So, from the interpreter's point of view, any string in Python is just a sequence of one-character strings.&lt;/p&gt;

&lt;p&gt;I'm not saying that Python is broken. It is the way it is, and it is not changing soon. However, what could be changed that will cause minimal harm but still will make things a bit better? I suggest introducing a built-in single character type, i.e. &lt;code&gt;char&lt;/code&gt;. This immediately will result in &lt;code&gt;str&lt;/code&gt; becoming &lt;code&gt;Sequence[char]&lt;/code&gt; and not &lt;code&gt;Sequence[str]&lt;/code&gt;. Other than requiring length to be precisely one, the &lt;code&gt;char&lt;/code&gt; type could behave exactly like &lt;code&gt;str&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, things like &lt;code&gt;chr&lt;/code&gt; and &lt;code&gt;ord&lt;/code&gt; functions could benefit from a more precise type annotation using &lt;code&gt;char&lt;/code&gt; instead of &lt;code&gt;str&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I think this would be a great addition to the language.&lt;/p&gt;

</description>
      <category>python</category>
      <category>typing</category>
      <category>programming</category>
      <category>pep</category>
    </item>
    <item>
      <title>Building Resilient Software Architecture: Lessons Learned from the Domino Game</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Thu, 06 Apr 2023 19:12:07 +0000</pubDate>
      <link>https://forem.com/justlig/building-resilient-software-architecture-lessons-learned-from-the-domino-game-1bg4</link>
      <guid>https://forem.com/justlig/building-resilient-software-architecture-lessons-learned-from-the-domino-game-1bg4</guid>
      <description>&lt;p&gt;Software development projects are complex, with many moving parts that must work together seamlessly to achieve the desired outcome. When a single weak point or flaw appears in the software architecture, the entire project can be derailed, much like a single domino piece missing from a chain reaction can ruin the entire game.&lt;/p&gt;

&lt;p&gt;In a game of dominoes, players must carefully arrange the tiles in a pattern, with each piece dependent on the one before it. One missing tile can cause the entire chain to break down, rendering the game unplayable. Similarly, in software development, every component of the architecture is dependent on the others, and a flaw in any one of them can cause the entire system to fail.&lt;/p&gt;

&lt;p&gt;For example, a single security vulnerability in a web application can be exploited to gain access to sensitive data or to bring the entire system down. A weak link in a data processing pipeline can cause errors to propagate through the system, leading to incorrect results or a complete failure of the process. In a distributed system, a single node that goes offline or fails to respond can cause a cascading failure that brings the entire system down.&lt;/p&gt;

&lt;p&gt;Just as a missing domino tile can be hard to spot until it's too late, weak points in software architecture can be difficult to detect early on. They may only become apparent when the system is under stress or when a particular scenario is encountered that exposes the flaw. However, unlike in a game of dominoes where the consequences are limited to that one game, the consequences of a software architecture flaw can be far-reaching and costly.&lt;/p&gt;

&lt;p&gt;To avoid the risk of a single weak point ruining an entire software project, developers must take a proactive approach to identifying and addressing vulnerabilities in the architecture. This requires a thorough understanding of the system's requirements, potential failure modes, and areas of risk, as well as ongoing monitoring and testing to ensure that the system remains secure and stable over time.&lt;/p&gt;

&lt;p&gt;In conclusion, a single domino piece missing can ruin a game, and a single weak point in a software architecture can ruin an entire software project. Both scenarios emphasize the importance of attention to detail, careful planning, and ongoing monitoring to ensure that all components are working together seamlessly. By taking a proactive approach to architecture design and testing, developers can minimize the risk of catastrophic failure and build systems that are reliable, secure, and resilient.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>reliability</category>
    </item>
    <item>
      <title>Beyond the Recipe: Why Communication Needs More than Just a Pinch of AI</title>
      <dc:creator>Serge Matveenko</dc:creator>
      <pubDate>Tue, 04 Apr 2023 23:55:51 +0000</pubDate>
      <link>https://forem.com/justlig/beyond-the-recipe-why-communication-needs-more-than-just-a-pinch-of-ai-pi5</link>
      <guid>https://forem.com/justlig/beyond-the-recipe-why-communication-needs-more-than-just-a-pinch-of-ai-pi5</guid>
      <description>&lt;p&gt;In the age of Artificial Intelligence (AI), it's easy to think that technology has made everything so simple and effortless. From shopping online to booking travel, everything seems to be automated and efficient. However, a &lt;a href="https://marketoonist.com/2023/03/ai-written-ai-read.html" rel="noopener noreferrer"&gt;recent caricature from Marketoonist&lt;/a&gt; has highlighted the importance of human effort and engagement in our communication and understanding of information.&lt;/p&gt;

&lt;p&gt;The caricature shows a man writing an email with the help of AI based on a single sentence, and the recipient using an AI to summarize the email back into one sentence. While this may seem like a quick and easy way to communicate, it emphasizes the fact that just sending a single phrase isn't enough. We still need to see an effort from the author and, more importantly, we need to make some effort to extract an important piece of information out of it. Otherwise, we won't be satisfied enough with that piece of information as we didn't do enough to get it.&lt;/p&gt;

&lt;p&gt;This concept is similar to how we don't just want to consume nutrients in a pure form. We need food to taste nice, to look nice, and we need to feel like we did enough cooking the food and extracting nutrients out of it. Similarly, when we communicate with others, we need to put in effort and care to ensure that we're delivering the information effectively and that the recipient is understanding it correctly.&lt;/p&gt;

&lt;p&gt;In today's fast-paced world, it's easy to fall into the trap of wanting everything to be done quickly and efficiently. However, it's important to remember that sometimes, the best things in life require a bit of effort and engagement. Whether it's taking the time to prepare a delicious meal or putting in the effort to write a thoughtful email, the end result is always more satisfying when we know we've put in the effort.&lt;/p&gt;

&lt;p&gt;The caricature is a reminder that AI is not a substitute for human effort and engagement. While technology has certainly made our lives easier in many ways, we still need to make an effort to connect with others and understand information fully. By doing so, we can ensure that we're not just communicating effectively, but that we're also building meaningful relationships and connections with those around us.&lt;/p&gt;

&lt;p&gt;In conclusion, while the caricature may be a humorous representation of our reliance on technology, it also serves as a reminder that human effort and engagement are essential for effective communication and understanding. Let's not forget to put in the effort to truly connect with others and extract important information, just as we don't want to miss out on the taste and nutrients that come from a well-cooked meal.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>cooking</category>
      <category>communication</category>
    </item>
  </channel>
</rss>
