<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>unkletayo.dev</title>
    <link>https://unkletayo.dev</link>
    <description>Notes on how software works underneath, by Adetayo Akinsanya.</description>
    <language>en</language>
    <managingEditor>contact@unkletayo.dev (Adetayo Akinsanya)</managingEditor>
    <lastBuildDate>Tue, 13 Oct 2026 07:00:00 GMT</lastBuildDate>
    <atom:link href="https://unkletayo.dev/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>How Java ArrayList Works Internally: Resizing, Growth Factor, and Memory</title>
      <link>https://unkletayo.dev/engineering/java-collections/03-building-my-arraylist/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/java-collections/03-building-my-arraylist/</guid>
      <description>Build an ArrayList from scratch and see how resizing, System.arraycopy and garbage collection shape its performance.</description>
      <pubDate>Tue, 13 Oct 2026 07:00:00 GMT</pubDate>
      <category>java</category>
      <category>datastructures</category>
      <category>arraylist</category>
      <category>algorithms</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/java-collections/03-building-my-arraylist.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Before you start: this article assumes you can read a simple Java class. It briefly explains arrays (and counting positions from 0), references, generics and casting, exceptions, the binary shift, and Big-O as they come up.</p>
<p>A list is an ordered collection that grows as you add items, like a shopping list you keep adding to. An array is a fixed-size row of numbered slots, and Java's <code>ArrayList</code> is a list built on top of one: you call <code>add()</code> to append an item and <code>get(i)</code> to read the item at position <code>i</code>. The reason to want a list is that you often don't know in advance how many items you will have, and a plain array can't grow. Behind the scenes, an <code>ArrayList</code> swaps in a bigger array when it runs out of room. "Pre-sizing" means telling it up front how many slots to reserve.</p>
<p>The usual advice is to pre-size your lists. Before building one, I measured what that advice buys.</p>
<p>The test adds 500,000 references to a list, once with <code>new ArrayList&lt;&gt;()</code> and once with <code>new ArrayList&lt;&gt;(500_000)</code>. Setup: OpenJDK 25.0.2, a 512 MB heap, an Intel Core i9-9880H, 200 warm-up rounds, then the median of 301 runs:</p>
<pre><code class="language-text">default   median 3.95 ms   p90 5.35 ms
presized  median 4.04 ms   p90 4.81 ms
</code></pre>
<p>A few terms from that setup. The heap is the memory area where Java objects live. Warm-up rounds run the code first so Java's optimizer has settled before timing starts. The median is the middle result when all the runs are sorted from fastest to slowest. The p90 is the value that 90 percent of the runs were at or below, so it shows the slower runs.</p>
<p>Same speed. The presized list came out a hair slower, which is within the run-to-run noise. In time, the resizing costs almost nothing. Time here means how long the loop took to run. The next measurement looks at memory instead.</p>
<p>It costs memory. I measured allocated bytes for the same two loops:</p>
<pre><code class="language-text">default   6.48 MB
presized  2.00 MB
</code></pre>
<p>That's 3.2 times the allocation for an identical list. Every array except the last one is dead the moment its contents are copied, and the garbage collector has to reclaim it. The garbage collector is the part of Java that automatically frees memory held by objects nothing refers to any more. I measured allocated bytes here, not GC time, so I can't tell you what that costs in your service.</p>
<p>The rest of this article builds the thing that produces those numbers.</p>
<hr />
<h2>The problem a plain array has</h2>
<p>Think of an array as a row of numbered lockers (0, 1, 2, ...). You choose how many when you buy the row, and you can't bolt more on later.</p>
<p>Create an array of 10 and add an 11th element, and Java throws <code>ArrayIndexOutOfBoundsException</code>. Throwing an exception means Java stops the program with an error message unless the code handles it. The length is locked at creation.</p>
<p>You could allocate a million slots up front and never worry. With compressed references that's about 4 MB per list, whether it holds a million elements or three. A reference is a slot's pointer to an object, and compressed references let the JVM (the program that runs Java code) store each one in 4 bytes instead of 8.</p>
<p><code>ArrayList</code> is the compromise: a normal array inside, replaced by a bigger one when it fills. Replacing means making a brand-new, larger array, copying every item from the old one into it, and using the new one from then on. The old array is abandoned.</p>
<h2>What's inside</h2>
<p>Think of a row of numbered mailboxes with a sticky note saying how many have mail. The row is the array and the note is the count. Two fields do all the work:</p>
<ol>
<li><code>Object[] elementData</code> holds the elements. <code>Object[]</code> is an array whose slots can each hold a reference, meaning a pointer to an object that lives elsewhere in memory, to any kind of Java object.</li>
<li><code>int size</code> counts how many you've added.</li>
</ol>
<p>In the picture below, <code>null</code> means "this slot points at nothing". Capacity is the number of slots in the array. Size is how many of them hold real items.</p>
<pre><code class="language-text">After adding "A" and "B" to a list with capacity 4:

elementData:  [ "A" | "B" | null | null ]
size:         2
capacity:     4   (elementData.length)
</code></pre>
<p><code>size</code> is what the caller sees. <code>elementData.length</code> is how much room exists before the next resize. They are different numbers, and the code below keeps them apart.</p>
<h2>Growing the array</h2>
<p>Picture moving house when your boxes no longer fit in the van: you hire a bigger van, move everything across, and give the old one back. The list does the same with arrays.</p>
<p>When <code>size == elementData.length</code> the buffer (the internal array) is full, and the next <code>add()</code> does three things: allocate a bigger array, copy the old elements across with <code>System.arraycopy()</code>, and point <code>elementData</code> at the new one. <code>System.arraycopy()</code> is a built-in Java method that copies a range of slots from one array to another, or within the same array, in one call.</p>
<pre><code class="language-text">BEFORE (full, capacity 4):
[ 10 | 20 | 30 | 40 ]

AFTER (capacity 6 = 4 + 4/2), then 50 goes in the first free slot:
[ 10 | 20 | 30 | 40 | 50 | null ]
</code></pre>
<p>The JDK grows by 1.5 times:</p>
<p>$$\text{newCapacity} = \text{oldCapacity} + (\text{oldCapacity} \gg 1)$$</p>
<p>Computers store whole numbers in binary (base 2). Shifting right by one bit moves every binary digit one place to the right, which halves the number without a division and drops any remainder. For example, <code>10 &gt;&gt; 1</code> is 5 (binary 1010 becomes 101), and <code>5 &gt;&gt; 1</code> is 2. So with an old capacity of 10, the new capacity is 10 + 5 = 15.</p>
<p>A bigger factor means fewer copies but more empty slots after each jump. At 1.5, a list that has just resized is about a third empty, and that is the worst case. For example, a full list of 1,000 slots grows to 1,500. Once the 1,001st item is in, 499 slots are empty, and 499 of 1,500 is about a third.</p>
<p>One difference from the real class: <code>new ArrayList&lt;&gt;()</code> doesn't allocate 10 slots. In the OpenJDK source it starts with a shared empty array and only grows to 10 on the first <code>add()</code>. The version below allocates 10 eagerly because that's easier to read.</p>
<h2>The implementation</h2>
<p>A few pieces of Java syntax show up below. <code>MyArrayList&lt;T&gt;</code> uses <code>&lt;T&gt;</code> as a placeholder for the element type, so <code>MyArrayList&lt;String&gt;</code> holds strings and <code>MyArrayList&lt;Integer&gt;</code> holds integers. <code>private</code> means only code inside this class can touch a field, and <code>static final</code> makes a constant shared by the whole class. <code>this</code> refers to the object being built or used. The array stores plain <code>Object</code>s, so <code>(T)</code> is a cast that tells the compiler to treat a value as type <code>T</code>, and <code>@SuppressWarnings("unchecked")</code> silences the compiler's warning that it cannot verify that cast. <code>Arrays.copyOf(a, n)</code> returns a new array of length <code>n</code> holding a's items, padded with <code>null</code> if <code>n</code> is larger. An index is an item's position number, starting at 0.</p>
<pre><code class="language-java">import java.util.Arrays;

public class MyArrayList&lt;T&gt; {
    private static final int DEFAULT_CAPACITY = 10;
    private Object[] elementData;
    private int size;

    public MyArrayList() {
        this.elementData = new Object[DEFAULT_CAPACITY];
        this.size = 0;
    }

    public MyArrayList(int initialCapacity) {
        if (initialCapacity &lt; 0) {
            throw new IllegalArgumentException("Capacity cannot be negative: " + initialCapacity);
        }
        this.elementData = new Object[initialCapacity];
        this.size = 0;
    }

    public void add(T element) {
        ensureCapacity(size + 1);
        elementData[size++] = element;
    }

    @SuppressWarnings("unchecked")
    public T get(int index) {
        checkBounds(index);
        return (T) elementData[index];
    }

    @SuppressWarnings("unchecked")
    public T remove(int index) {
        checkBounds(index);
        T oldValue = (T) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved &gt; 0) {
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }

        elementData[--size] = null; // clear the vacated slot
        return oldValue;
    }

    public int size() {
        return size;
    }

    private void ensureCapacity(int minCapacity) {
        if (minCapacity &gt; elementData.length) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity &gt;&gt; 1); // 1.5x growth
            if (newCapacity &lt; minCapacity) {
                newCapacity = minCapacity;
            }
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }

    private void checkBounds(int index) {
        if (index &lt; 0 || index &gt;= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
    }
}
</code></pre>
<h3>Walking through it</h3>
<p>Start with <code>list.add("A")</code> on a new list. <code>ensureCapacity(1)</code> sees that 1 is not more than the 10 slots, so it does nothing. Then <code>elementData[size++] = element</code> stores "A" in slot 0 and bumps <code>size</code> from 0 to 1. The <code>++</code> after <code>size</code> means "use the current value as the index, then add 1", so storing the element and updating the count happen in one line.</p>
<p>Now suppose the list holds 10 items and you add an 11th. <code>ensureCapacity(11)</code> finds that 11 is more than 10, so <code>newCapacity</code> becomes 10 + (10 &gt;&gt; 1) = 15. <code>Arrays.copyOf</code> builds a new 15-slot array holding the old 10 items, with <code>null</code> in the rest, and <code>elementData</code> now points at it. The old array has nothing pointing at it any more. Then the 11th item goes into slot 10.</p>
<p>Now <code>remove(1)</code> on <code>[A, B, C, D]</code>, where <code>size</code> is 4. <code>checkBounds(1)</code> passes because 1 is at least 0 and less than 4. <code>oldValue</code> is "B". Then <code>numMoved = 4 - 1 - 1 = 2</code>, the two items after the gap:</p>
<pre><code class="language-text">before:  [ A | B | C | D ]
copy C, D one slot left (from index 2 into index 1):
after:   [ A | C | D | D ]
then --size makes size 3 and nulls slot 3:
final:   [ A | C | D | null ]
</code></pre>
<p>If you ask for <code>get(3)</code> now, <code>checkBounds</code> throws <code>IndexOutOfBoundsException</code>, because 3 is not less than <code>size</code>.</p>
<h2>The line that stops a leak</h2>
<p>Imagine you take a book off a library shelf but leave its card in the catalogue. The library still believes the book is in use, so it never clears the space. A leftover slot in the array does the same thing to the garbage collector.</p>
<p>Look at the last statement in <code>remove()</code>:</p>
<pre><code class="language-java">elementData[--size] = null;
</code></pre>
<p>Here <code>--size</code> subtracts 1 from <code>size</code> first and uses the new value as the index. After removing "B" from <code>[A, B, C, D]</code> and shifting, the array is <code>[A, C, D, D]</code> and <code>size</code> is 4. The statement makes <code>size</code> 3 and sets slot 3 (the stale second "D") to <code>null</code>, giving <code>[A, C, D, null]</code>.</p>
<p>Drop the <code>= null</code> and keep only the <code>--size</code>, and the array still holds a reference to the removed object. No public method can reach it. The garbage collector can't know that; it sees a live reference and keeps the object alive. Objects kept alive this way are called loitering objects.</p>
<h2>Why <code>add()</code> is still O(1)</h2>
<p>Big-O notation describes how the work grows as the number of items grows. $O(1)$ means the work stays about the same however many items there are. $O(N)$ means it grows in proportion to $N$, the number of items. Think of paying a small bill every day versus a huge bill once a year: the yearly bill is big, but spread over 365 days it is a small daily cost.</p>
<p>One resize copies the whole array, which is $O(N)$. So how can <code>add()</code> be constant time?</p>
<p>Because resizes get rarer as the list grows. I simulated the JDK's growth rule from 10 slots up to 500,000 elements and counted: 27 resizes, 1,080,430 element copies in total. That's 2.16 copies per insert, spread over half a million inserts.</p>
<p>It's no accident that the number sits near 2. Each resize copies the old size, and with a 1.5 factor those old sizes form a geometric series:</p>
<p>$$\frac{N}{1.5} + \frac{N}{1.5^2} + \frac{N}{1.5^3} + \dots \approx 2N$$</p>
<p>Each term is the previous one divided by 1.5, so the terms shrink quickly. A series like that adds up to its first term divided by $(1 - 1/1.5)$. The first term is $N/1.5$ and $1 - 1/1.5$ is $1/3$, so the total is $(N/1.5) \div (1/3) = 2N$. For example, with $N = 600$ the copies are 400, then 267, then 178, and so on, and the running total creeps toward 1,200.</p>
<p>Total copying is bounded by about $2N$ for $N$ inserts. The cost per insert is a constant, which is what amortized $O(1)$ means. Amortized means averaged over a long run of operations: a few <code>add()</code> calls are expensive, but the average cost per call stays constant. Individual calls can still be slow; the average can't.</p>
<h2>What I'd do with this</h2>
<p>If you know the final size, pre-size. In my benchmark it didn't speed up the loop, but it cut allocation from 6.48 MB to 2.00 MB. If you don't know the size, leave it alone; the growth rule handles it.</p>
<p>And if you write your own list, put the <code>null</code> in <code>remove()</code>.</p>
<h2>Quick summary</h2>
<ul>
<li>A <code>size</code> field and a flat array make a list. Everything else is bookkeeping.</li>
<li>Full means resize by 1.5 times with <code>Arrays.copyOf()</code>. 27 resizes take it from 10 slots to 500,000 elements.</li>
<li>Resizing barely moved the timing; it tripled the memory allocated.</li>
<li><code>remove()</code> shifts elements left, then nulls the last slot so nothing loiters.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Mastering Cron Expressions: From Syntax to Production-Grade Scheduling</title>
      <link>https://unkletayo.dev/engineering/standalone/mastering-cron-expressions-guide/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/mastering-cron-expressions-guide/</guid>
      <description>How cron expressions work across Unix, Quartz and Spring: special characters, a cheat sheet, time zone and daylight saving edge cases, and distributed locking.</description>
      <pubDate>Tue, 06 Oct 2026 07:00:00 GMT</pubDate>
      <category>softwarearchitecture</category>
      <category>backend</category>
      <category>devops</category>
      <category>java</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/mastering-cron-expressions-guide.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>If you have built software long enough, you have written a background job. Whether it is sending nightly email digests, generating monthly financial reports, or running retry mechanisms over failed payments, scheduled tasks are the quiet backbone of modern backends.</p>
<p>Under the hood, almost every scheduler (from Unix <code>crontab</code> to enterprise Java framework Quartz and cloud orchestrators like AWS EventBridge) uses a variant of one decades-old format: <strong>Cron expressions</strong>.</p>
<p>Despite being ubiquitous, cron syntax remains a frequent source of "production surprise." We have all seen a job run every minute when it was meant to run every hour, or fail to trigger because someone mixed up Day-of-Month and Day-of-Week.</p>
<p>In this guide, we will break down cron syntax step-by-step, dissect real-world patterns, and address common edge cases like time zones, daylight saving time, and duplicate runs across multiple nodes.</p>
<hr />
<h2>1. Dialects: Unix Cron vs. Quartz / Spring Cron</h2>
<p>The first trap developers fall into is assuming all cron strings use the exact same format. There are two primary formats in modern software, plus variants such as Amazon EventBridge Scheduler's six-field format, which requires a year.</p>
<h3>Standard Unix Cron (5 Fields)</h3>
<p>Used natively by the Linux <code>crontab</code>, and by libraries such as Node.js <code>node-cron</code> (which also accepts an optional leading seconds field), Go <code>robfig/cron</code> (seconds are opt-in), and Python Celery (<code>crontab.from_string</code>).</p>
<pre><code class="language-text"> ┌───────────── minute (0 - 59)
 │ ┌─────────── hour (0 - 23)
 │ │ ┌───────── day of the month (1 - 31)
 │ │ │ ┌─────── month (1 - 12)
 │ │ │ │ ┌───── day of the week (0 - 6) (Sunday to Saturday; crontab also accepts 7 for Sunday)
 │ │ │ │ │
 * * * * *
</code></pre>
<h3>Extended Cron: Quartz (6 or 7 Fields) and Spring (6 Fields)</h3>
<p>Used by Java applications running Quartz Scheduler, Spring Framework <code>@Scheduled</code>, or enterprise backend runners.</p>
<pre><code class="language-text"> ┌───────────── second (0 - 59)
 │ ┌─────────── minute (0 - 59)
 │ │ ┌───────── hour (0 - 23)
 │ │ │ ┌─────── day of the month (1 - 31)
 │ │ │ │ ┌───── month (1 - 12)
 │ │ │ │ │ ┌─── day of the week (Quartz: 1 - 7, SUN = 1 | Spring: 0 - 7, SUN = 0 or 7)
 │ │ │ │ │ │
 * * * * * *
</code></pre>
<blockquote>
<p><strong>Note:</strong> Quartz can also append an optional 7th field at the end for the Year, e.g., <code>2026</code>. Spring accepts exactly six fields and rejects a seventh.</p>
</blockquote>
<p>The day-of-week numbers differ between the two: <code>6</code> is Friday in Quartz and Saturday in Spring. Day names (<code>FRI</code>, <code>MON-FRI</code>) mean the same thing in both, so prefer names.</p>
<p>Understanding which scheduler your runtime uses matters. Quartz and Spring both reject a 5-field string with an error. Linux <code>crontab</code> is the opposite case: it reads a sixth field as the command to run.</p>
<hr />
<h2>2. Deconstructing Special Characters</h2>
<p>Cron relies on set operations defined through operators. Here is what each character means:</p>
<h3><code>*</code> (Asterisk) : Every</h3>
<p>Matches every possible value in a field.</p>
<ul>
<li><code>0 * * * *</code> in 5-field syntax means: <em>At minute 0 of every hour, every day, every month.</em></li>
</ul>
<h3><code>,</code> (Comma) : Value List</h3>
<p>Executes on specific, enumerated items.</p>
<ul>
<li><code>0 9,12,15 * * *</code> means: <em>Run at 9:00 AM, 12:00 PM, and 3:00 PM.</em></li>
</ul>
<h3><code>-</code> (Hyphen) : Range</h3>
<p>Defines an inclusive range of values.</p>
<ul>
<li><code>0 9-17 * * MON-FRI</code> means: <em>Run at the top of every hour from 9:00 AM through 5:00 PM, Monday through Friday.</em></li>
</ul>
<h3><code>/</code> (Slash) : Step / Increment</h3>
<p>Specifies step intervals within a range. Written as <code>start/interval</code>.</p>
<ul>
<li><code>0/15 * * * * ?</code> (Quartz) means: <em>Starting at second 0, run every 15 seconds (0, 15, 30, 45 of each minute).</em></li>
<li><code>5/10 * * * * ?</code> means: <em>Starting at second 5, run every 10 seconds (5, 15, 25, 35, 45, 55 of each minute).</em></li>
<li><code>0 0/15 * * * ?</code> means: <em>Every 15 minutes, on the minute (0, 15, 30, 45).</em> In Quartz the first field is seconds, so stepping through minutes means moving the step one field to the right.</li>
</ul>
<h3><code>?</code> (Question Mark) : No Specific Value</h3>
<p>Exclusive to Quartz and Spring cron.</p>
<p>Quartz does not support setting both Day-of-Month and Day-of-Week at once, as they can conflict. One of these two fields must be marked with <code>?</code>. Spring is more lenient and accepts both.</p>
<ul>
<li><code>0 0 12 ? * MON</code> = <em>Run at 12:00 PM every Monday, regardless of what day of the month it falls on.</em></li>
<li><code>0 0 12 15 * ?</code> = <em>Run at 12:00 PM on the 15th of every month, regardless of what day of the week it falls on.</em></li>
</ul>
<hr />
<h2>3. Advanced Operators (Quartz &amp; Enterprise Schedulers)</h2>
<p>For complex domain logic, standard arithmetic intervals fall short. Enterprise schedulers support additional tokens:</p>
<ul>
<li><strong><code>L</code> (Last):</strong>
<ul>
<li><code>0 0 23 L * ?</code> -&gt; Run at 11:00 PM on the last day of the month (handles 28, 29, 30, or 31 automatically).</li>
<li><code>0 0 18 ? * 6L</code> -&gt; Run at 6:00 PM on the last Friday of the month (<code>6</code> = Friday in Quartz).</li>
</ul>
</li>
<li><strong><code>W</code> (Nearest Weekday):</strong>
<ul>
<li><code>0 0 9 15W * ?</code> -&gt; Run at 9:00 AM on the nearest weekday to the 15th of the month. If the 15th is a Saturday, it triggers on Friday the 14th. If Sunday, it triggers on Monday the 16th.</li>
</ul>
</li>
<li><strong><code>#</code> (Nth Weekday of the Month):</strong>
<ul>
<li><code>0 0 10 ? * 2#3</code> -&gt; Run at 10:00 AM on the 3rd Monday of the month (<code>2</code> = Monday in Quartz, <code>#3</code> = 3rd instance). Perfect for payroll or maintenance windows.</li>
</ul>
</li>
</ul>
<p>The weekday numbers above are Quartz's. In Spring, <code>6</code> is Saturday and <code>2#3</code> is the third Tuesday. Names behave the same in both, so <code>0 0 18 ? * FRIL</code> and <code>0 0 10 ? * MON#3</code> do what they say in Quartz and in Spring.</p>
<hr />
<h2>4. Real-World Cheat Sheet</h2>
<p>Here are common schedules used in backend systems:</p>
<table>
<thead>
<tr>
<th>Frequency</th>
<th>Standard Unix (5 fields)</th>
<th>Quartz / Spring (6 fields)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Every 30 seconds</td>
<td><em>N/A</em></td>
<td><code>0/30 * * * * ?</code></td>
</tr>
<tr>
<td>Every 5 minutes</td>
<td><code>*/5 * * * *</code></td>
<td><code>0 0/5 * * * ?</code></td>
</tr>
<tr>
<td>Every hour on the hour</td>
<td><code>0 * * * *</code></td>
<td><code>0 0 * * * ?</code></td>
</tr>
<tr>
<td>Everyday at 2:30 AM</td>
<td><code>30 2 * * *</code></td>
<td><code>0 30 2 * * ?</code></td>
</tr>
<tr>
<td>Business hours (Hourly, 9-5, Mon-Fri)</td>
<td><code>0 9-17 * * 1-5</code></td>
<td><code>0 0 9-17 ? * MON-FRI</code></td>
</tr>
<tr>
<td>First day of every month at midnight</td>
<td><code>0 0 1 * *</code></td>
<td><code>0 0 0 1 * ?</code></td>
</tr>
</tbody>
</table>
<hr />
<h2>5. Production Gotchas: What Can Go Wrong?</h2>
<h3>Time Zone Misalignments</h3>
<p>By default, most cron engines run on the host OS system clock. If your server is in UTC and your business operates in New York, your 9:00 AM job fires at 9:00 UTC, which is 4:00 AM in New York in winter and 5:00 AM in summer.</p>
<p><strong>Fix:</strong> Explicitly define time zones in code rather than relying on system defaults:</p>
<pre><code class="language-java">// Quartz Spring Bean definition with explicit TimeZone
@Bean
public Trigger jobTrigger(JobDetail jobDetail) {
    return TriggerBuilder.newTrigger()
        .forJob(jobDetail)
        .withSchedule(CronScheduleBuilder
            .cronSchedule("0 0 9 * * ?")
            .inTimeZone(TimeZone.getTimeZone("America/New_York")))
        .build();
}
</code></pre>
<h3>Daylight Saving Time (DST) Jumps</h3>
<p>If a job is set to run inside the hour that DST moves:</p>
<ul>
<li>In spring the clocks jump forward (in New York from 2:00 AM to 3:00 AM), so <strong>2:30 AM never happens</strong>. The job can be skipped.</li>
<li>In autumn an hour repeats, and a job scheduled inside it can run twice. In New York the clocks go back from 2:00 AM to 1:00 AM, so 1:30 AM happens twice. In Berlin they go back from 3:00 AM to 2:00 AM, so 2:30 AM happens twice.</li>
</ul>
<p>The <code>crontab(5)</code> man page documents both behaviors, and the Quartz docs warn that the shift "can cause a skip or a repeat."</p>
<p><strong>Fix:</strong> Schedule critical batch jobs during hours unaffected by DST transitions (e.g., 4:00 AM UTC), or run on UTC system time universally.</p>
<h3>Distributed Environments &amp; Race Conditions</h3>
<p>If you deploy 5 instances of a microservice behind a load balancer and all 5 use an in-memory cron runner, your scheduled job will execute 5 times simultaneously.</p>
<p><strong>Fixes:</strong></p>
<ol>
<li>Use a distributed lock: ShedLock for Spring (it keeps its lock in a shared store such as a JDBC database, Redis, or Mongo) or <code>redlock</code> for Node.js.</li>
<li>Store job schedules centrally in a database (e.g., Quartz JDBC Store).</li>
</ol>
<pre><code class="language-java">// ShedLock example in Spring Boot prevents concurrent execution
@Scheduled(cron = "0 0/5 * * * ?")
@SchedulerLock(name = "DepositRetryTask_lock", lockAtLeastFor = "2m", lockAtMostFor = "4m")
public void executeRetry() {
    // Only one instance executes this at any given time
}
</code></pre>
<p>ShedLock only takes effect once you add <code>@EnableSchedulerLock</code> to a configuration class and define a <code>LockProvider</code> bean for your lock store.</p>
<hr />
<h2>Summary Checklist</h2>
<p>When building cron-driven tasks into production, keep this four-point checklist in mind:</p>
<ol>
<li><strong>Know your field count:</strong> Confirm whether your engine expects 5, 6, or 7 fields.</li>
<li><strong>Standardize on UTC:</strong> Run server clocks on UTC to prevent DST transition headaches.</li>
<li><strong>Prevent duplicate runs:</strong> Protect job execution with distributed locks when running multi-node clusters.</li>
<li><strong>Validate early:</strong> Always test expressions before committing, with <code>crontab.guru</code> for Unix-style expressions or <code>cronmaker.com</code> for Quartz.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>OCI Specifications Explained: How runc and Container Images Actually Work</title>
      <link>https://unkletayo.dev/engineering/docker/03-open-container-initiative-oci-specifications/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/docker/03-open-container-initiative-oci-specifications/</guid>
      <description>How the Open Container Initiative standards fit together: Image Spec manifests, configuration JSON, layer diff tarballs, and the Runtime Spec filesystem bundle.</description>
      <pubDate>Tue, 06 Oct 2026 07:00:00 GMT</pubDate>
      <category>docker</category>
      <category>oci</category>
      <category>containerstandards</category>
      <category>runc</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/docker/03-open-container-initiative-oci-specifications.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Before you start: you should know roughly what a container and an image are (Part 1), have seen the containerd, shim and runc chain from Part 2, and be able to read simple JSON (text made of <code>"key": value</code> pairs, with <code>{ }</code> for objects and <code>[ ]</code> for lists).</p>
<p>Shipping containers work all over the world because everyone agreed on one set of sizes and one kind of lock. A crane in any port can lift a box that was loaded in any other country, and nobody has to know who packed it. Electrical plugs and sockets work the same way: any appliance with the right plug fits any socket that follows the same standard. The Open Container Initiative (OCI) is that kind of agreement for software containers. If a tool follows the agreed format, it can build, move, or run what another tool made.</p>
<p>Here is how that agreement came about.</p>
<p>In 2015, the container ecosystem faced a major risk of fragmentation.</p>
<p>Docker was the dominant container engine, but competing vendors and cloud providers began creating rival container formats and execution runtimes (such as CoreOS's <code>rkt</code> and appc specification). <code>rkt</code> was a container runtime from the company CoreOS, and appc (App Container) was CoreOS's own written format for container images, a rival to Docker's.</p>
<p>If every cloud vendor required a different image format or runtime engine, developers would be trapped rebuilding separate container packages for every cloud infrastructure target.</p>
<p>To prevent vendor lock-in and standardize container primitives, tech leaders formed the <strong>Open Container Initiative (OCI)</strong> under the Linux Foundation. Vendor lock-in means being stuck with one company's product because switching away from it costs too much. The Linux Foundation is a non-profit organization that hosts open-source projects, and the OCI is an industry group it hosts that writes shared, public rules (specifications) for containers.</p>
<p>The OCI maintains three open standards. This article covers the first two in depth and names the third at the end:</p>
<ol>
<li><strong>The OCI Image Specification (Image-Spec)</strong>: Defines how container images are formatted, stored, content-addressed, and archived on disk or in registries. Content-addressed means a file is named by a fingerprint of its contents instead of a name someone chose. A registry is a server that stores container images and hands them out on request (Docker Hub is one).</li>
<li><strong>The OCI Runtime Specification (Runtime-Spec)</strong>: Defines how an unpacked container bundle is executed on a host system by a low-level runtime (such as <code>runc</code>).</li>
<li><strong>The OCI Distribution Specification (Distribution-Spec)</strong>: Defines the HTTP API (the agreed set of web requests) that tools use to push images to a registry and pull them back down. It appears again in the table near the end.</li>
</ol>
<p>Understanding the first two specifications explains how any OCI-compliant runtime (like <code>runc</code>, <code>crun</code>, or <code>gVisor</code>) can execute an image built by Docker, Podman, or BuildKit. Compliant means it follows the specification. <code>crun</code> is a runtime written in C, and <code>gVisor</code> is a Google project that provides an OCI runtime (<code>runsc</code>) which adds an extra layer of isolation between the application and the host operating system. Podman is a container tool that, like Docker, can build and run containers, but it does not need a background daemon (a program that keeps running in the background and waits for commands). BuildKit is the build engine Docker uses to turn a Dockerfile into an image. Because the builder and the runner both read the same file layout, they do not need to know anything about each other.</p>
<hr />
<h2>1. The OCI Image Specification</h2>
<p>Think of an image as a recipe box. It has a table of contents (the index), a card listing the parts of one recipe (the manifest), a card with the cooking settings (the config), and the ingredient bags (the layers).</p>
<p>An OCI container image is not a virtual machine disk image (a VM disk image is a full copy of a whole computer's disk, and an image is not that). It is an archived tarball containing a set of cryptographic, content-addressable JSON files and tar compressed layer diffs.</p>
<p>A tarball is a single file that packs many files and folders together, like a zip file (the <code>tar</code> tool does the packing, and <code>gzip</code> is a separate step that compresses the result). A layer is one step's worth of file changes in an image, and a layer diff is just the files that were added, changed, or removed in that step. Stacking the layers in order gives the full set of files.</p>
<p>Two terms appear throughout this section. SHA-256 is a fingerprint function: it turns any file into a fixed 64-character code, and a different file gives a different code. That code is called a digest. A blob is simply a file stored under the name of its digest.</p>
<p>When you inspect an OCI image layout on disk, you find three top-level entries (<code>index.json</code>, <code>oci-layout</code>, and the <code>blobs/</code> folder), and inside <code>blobs/sha256/</code> four blobs that hold the real content:</p>
<pre><code>[ OCI Image Layout Structure ]
├── index.json               &lt;-- Image Index (List of manifests for multi-arch support)
├── oci-layout               &lt;-- Layout version identifier ({"imageLayoutVersion": "1.0.0"})
└── blobs/
    └── sha256/
        ├── e3b0c44298fc1c... &lt;-- Image Manifest JSON
        ├── 7a8b9c1d2e3f4a... &lt;-- Image Configuration JSON
        ├── 1a2b3c4d5e6f7a... &lt;-- Layer 1 tar.gz payload
        └── 9f8e7d6c5b4a3f... &lt;-- Layer 2 tar.gz payload
</code></pre>
<p>The long hex names are the digests (the fingerprints) of each file, and the hashes in these examples are shortened or made up to illustrate the layout. Multi-arch means one image name that has separate builds for different processor types, such as Intel/AMD (<code>amd64</code>) and ARM (<code>arm64</code>). The index is what lists those builds. The <code>oci-layout</code> file only records which version of the layout format the folder uses.</p>
<hr />
<h3>A. The Image Index (<code>index.json</code>) and the Image Manifest (Manifest Blob)</h3>
<p>The index (<code>index.json</code>) lists manifests, one for each platform build of an image. Each manifest lists one image's config and layers.</p>
<p>The <strong>Image Manifest</strong> acts as the entry point for an image version tag (e.g., <code>nginx:latest</code>). A tag is a human-readable label for one version of an image, so <code>nginx:latest</code> means the image named <code>nginx</code> with the tag <code>latest</code>. The manifest links the image configuration JSON file to its underlying filesystem layers:</p>
<pre><code class="language-json">{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:7a8b9c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b",
    "size": 1472
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
      "size": 2814021
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e",
      "size": 1420102
    }
  ]
}
</code></pre>
<p>Notice that every referenced file is identified by its SHA-256 Digest. If a layer's contents change by a single byte, its SHA-256 digest changes, enforcing immutable content-addressable storage. Immutable means it cannot be changed. If anyone alters a file, its fingerprint no longer matches the one written in the manifest, so the change is detected, and the same file can safely be shared between many images.</p>
<p>Here is what each key in the manifest means:</p>
<ul>
<li><code>schemaVersion</code> is the version of the manifest format. It is always <code>2</code> for current manifests.</li>
<li><code>mediaType</code> at the top says what kind of document this file is (an OCI image manifest). Everywhere else in the file, <code>mediaType</code> says what kind of file the entry points to: a config JSON, or a gzip-compressed tar layer.</li>
<li><code>config</code> points to the Image Configuration JSON blob.</li>
<li><code>layers</code> is the list of layer blobs, in the order they are stacked.</li>
<li><code>digest</code> is the SHA-256 fingerprint of the file being pointed to, which is also its name under <code>blobs/sha256/</code>.</li>
<li><code>size</code> is the length of that file in bytes.</li>
</ul>
<hr />
<h3>B. The Image Configuration JSON</h3>
<p>While layers store the filesystem files, the <strong>Image Config JSON</strong> defines execution metadata (data about how to run the image, not part of its files) for the target runtime:</p>
<pre><code class="language-json">{
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "NGINX_VERSION=1.25.1"],
    "Cmd": ["nginx", "-g", "daemon off;"],
    "WorkingDir": "/",
    "ExposedPorts": { "80/tcp": {} }
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:c2ad4e0... (Uncompressed Layer 1 SHA)",
      "sha256:e8f1b2a... (Uncompressed Layer 2 SHA)"
    ]
  }
}
</code></pre>
<p>The hashes here are placeholders. Here is what the keys mean:</p>
<ul>
<li><code>architecture</code> is the processor type the image was built for. <code>amd64</code> is the 64-bit Intel/AMD type.</li>
<li><code>os</code> is the operating system the image expects.</li>
<li><code>Env</code> sets environment variables, which are named settings a program can read. <code>PATH</code> is the list of folders the system searches when you type a command name.</li>
<li><code>Cmd</code> is the command to run when the container starts. In <code>"daemon off;"</code>, a daemon is a program that detaches and keeps running in the background. This nginx setting tells nginx to stay in the foreground instead, because a container exits when its main process exits. (This is nginx's own use of the word, not Docker's daemon.)</li>
<li><code>WorkingDir</code> is the folder the command starts in.</li>
<li><code>ExposedPorts</code> records that the program listens on port 80 over TCP. It is a note for people and tools, and does not open the port by itself.</li>
<li><code>rootfs</code> lists the layers. Each entry in <code>diff_ids</code> is the SHA-256 of one layer after it has been uncompressed.</li>
</ul>
<p>That last point means each layer has two different fingerprints. The <code>digest</code> in the manifest is the fingerprint of the compressed file (the <code>tar+gzip</code> blob that is stored and downloaded). The <code>diff_ids</code> entry in the config is the fingerprint of the same layer's tar once uncompressed, which is what matters after you unpack it.</p>
<hr />
<h2>2. The OCI Runtime Specification &amp; Filesystem Bundle</h2>
<p>The image is the packed, stored form, like a zipped game. The bundle is the unpacked, ready-to-launch form: the game's installed folder plus its launch settings.</p>
<p>While the Image Spec governs static image archives, the <strong>OCI Runtime Specification</strong> governs how a container is executed at runtime.</p>
<p>To execute a container, an OCI-compliant runtime like <code>runc</code> requires an OCI Filesystem Bundle. A filesystem is the organized tree of files and folders on a disk. Part 2 followed the chain <code>containerd</code>, then the shim, then <code>runc</code>, and said the shim hands <code>runc</code> an OCI bundle. This part opens that bundle.</p>
<p>An OCI Bundle is a simple directory on disk containing exactly two things:</p>
<pre><code>[ OCI Bundle Directory ]
├── config.json         &lt;-- Execution configuration required by runc
└── rootfs/             &lt;-- Unpacked root filesystem directory
</code></pre>
<p><code>rootfs/</code> is the folder the container will see as its entire disk, holding <code>/bin</code>, <code>/etc</code>, and so on. The layers from the image, unpacked and stacked, end up here.</p>
<hr />
<h3>The <code>config.json</code> Runtime Contract</h3>
<p>When <code>containerd</code> unpacks an OCI image for <code>runc</code>, it reads the Image Config JSON and translates it into an OCI Runtime <code>config.json</code>.</p>
<p>This is the step where the stored form (the image) becomes the runnable form (the bundle). Recapping Part 2: <code>containerd</code> fetches the image's files and manages the container, the shim stays beside the container and calls <code>runc</code>, and <code>runc</code> reads the bundle to set up and start the process. The translation maps image settings to runtime settings. Image <code>Cmd</code> becomes <code>process.args</code>, image <code>Env</code> becomes <code>process.env</code>, and image <code>WorkingDir</code> becomes <code>process.cwd</code>. <code>ExposedPorts</code> has no counterpart here, because networking is set up by the container engine and not by this file.</p>
<p>This file does not name system calls. It lists the settings <code>runc</code> uses when it asks the Linux kernel to isolate and start the process. The kernel is the core of the operating system, and a system call is a request a program makes to it. The spec describes the file as the container's process, its root filesystem, mounts, namespaces, cgroup resource limits, capabilities, and more:</p>
<pre><code class="language-json">{
  "ociVersion": "1.0.2",
  "process": {
    "terminal": false,
    "user": { "uid": 0, "gid": 0 },
    "args": ["nginx", "-g", "daemon off;"],
    "env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"]
  },
  "root": {
    "path": "rootfs",
    "readonly": true
  },
  "linux": {
    "namespaces": [
      { "type": "pid" },
      { "type": "network" },
      { "type": "ipc" },
      { "type": "uts" },
      { "type": "mount" }
    ],
    "resources": {
      "memory": { "limit": 536870912 }
    }
  }
}
</code></pre>
<p>Here is what each part of the file means. This example is shortened, and the spec also requires a <code>process.cwd</code> entry in a full file.</p>
<ul>
<li><code>ociVersion</code> is the version of the Runtime Spec the file follows.</li>
<li><code>process</code> is what to run and as whom. <code>terminal: false</code> means no terminal is attached. <code>user</code> gives the user and group numbers (<code>uid</code> and <code>gid</code>), where <code>0</code> is the all-powerful root user. <code>args</code> is the command line to start, and <code>env</code> is its environment variables.</li>
<li><code>root</code> is the folder to use as the container's disk. <code>readonly: true</code> means the container cannot write to it.</li>
<li><code>linux.namespaces</code> are the private views to create. A namespace gives the process its own view of one thing: <code>pid</code> is its own process list, <code>network</code> its own network, <code>ipc</code> its own inter-process messaging, <code>uts</code> its own hostname, and <code>mount</code> its own set of mounted filesystems.</li>
<li><code>linux.resources.memory.limit</code> is the cgroup memory cap in bytes. A cgroup is a kernel feature that caps resources such as memory for a group of processes. $512 \times 1024 \times 1024 = 536{,}870{,}912$ bytes, which is 512MB.</li>
</ul>
<p>Part 2 showed the shim calling <code>runc create</code>, which sets up the container without running your program yet. <code>runc start</code> then runs it. <code>runc run</code> does both in one command: it creates the container and starts it. That is why the line below says <code>run</code>.</p>
<p>When you invoke <code>runc run my-container</code>, <code>runc</code> parses this <code>config.json</code> file, configures the requested Linux namespaces and cgroup memory limits (<code>536870912</code> bytes = 512MB), enters <code>rootfs</code>, and executes <code>execve("nginx")</code>. As in Part 2, <code>execve</code> swaps the current process for the <code>nginx</code> program.</p>
<hr />
<h2>OCI Architectural Decoupling Matrix</h2>
<p>Decoupled means each part can be swapped for another without changing the others. A protocol is an agreed way for two programs to talk. In the table, the middle column is the spec, and the third column names example tools that implement it. <code>runc</code> is the reference runtime for the Runtime Spec, and <code>distribution/distribution</code> (the CNCF Distribution project) is the reference registry for the Distribution Spec. BuildKit and Docker Engine are widely used tools that produce OCI images, not reference implementations.</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Standard Specification</th>
<th>Example Tools / Implementations</th>
<th>Responsibilities</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Container Image Format</strong></td>
<td>OCI Image Specification</td>
<td>BuildKit / Docker Engine</td>
<td>Defines image layers, digests, manifests, and config JSON</td>
</tr>
<tr>
<td><strong>Container Execution</strong></td>
<td>OCI Runtime Specification</td>
<td><code>runc</code> / <code>crun</code> / <code>gVisor</code></td>
<td>Converts OCI bundle (<code>config.json</code> + <code>rootfs/</code>) into isolated Linux process</td>
</tr>
<tr>
<td><strong>Registry Protocol</strong></td>
<td>OCI Distribution Specification</td>
<td>CNCF Distribution / Docker Registry</td>
<td>Defines HTTP API for pushing and pulling content-addressed blobs</td>
</tr>
</tbody>
</table>
<hr />
<h2>Summary</h2>
<p>The Open Container Initiative standardizes containerization across vendor ecosystems. In short, the Image Spec is how a package is stored and labeled, and the Runtime Spec is how an unpacked package is started:</p>
<ul>
<li><strong>OCI Image Spec</strong> defines content-addressable manifests, configuration JSON, and tarball layer diffs.</li>
<li>OCI Runtime Spec defines the filesystem bundle (<code>config.json</code> + <code>rootfs/</code>) passed to low-level runtimes.</li>
<li><code>runc</code> reads the OCI bundle configuration to configure namespaces and cgroups before executing the target binary.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Why You Must Always Override equals() and hashCode() Together in Java</title>
      <link>https://unkletayo.dev/engineering/java-collections/02-equals-and-hashcode/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/java-collections/02-equals-and-hashcode/</guid>
      <description>Why equals() and hashCode() have to agree, and how breaking that contract causes silent HashMap lookup bugs and memory leaks.</description>
      <pubDate>Thu, 01 Oct 2026 07:00:00 GMT</pubDate>
      <category>java</category>
      <category>datastructures</category>
      <category>hashmap</category>
      <category>jvm</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/java-collections/02-equals-and-hashcode.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Before you start: this article assumes you know what a class, an object, a field and a method are in Java, and how to create an object with <code>new</code>. Everything else is explained as it comes up.</p>
<p>Suppose it's 2:00 AM on a Friday. Your phone alerts you to a production incident: logged-in users cannot access their subscription accounts. ("Production" means the live system that real users are using right now.)</p>
<p>Your authentication service uses a custom <code>UserSession</code> object as a key inside a <code>HashMap</code> to fetch user roles.</p>
<p>A <code>HashMap</code> is a Java lookup table that stores pairs: you hand it a key and it gives back the value saved for that key, like looking up a name in a phone book to get a number. A key is the item you look something up by (here, a session), and a value is the thing stored under it (here, the user's role). Putting a pair in is called <code>put</code>, and looking a value up by its key is called <code>get</code>. In plain terms, when a user logs in the program stores their role in the map, and later requests ask the map for it.</p>
<p>The logs show that when a user logs in, the service creates a <code>UserSession(userId=981)</code> key and stores their role. One millisecond later, when the user requests their profile page, the server constructs another <code>UserSession(userId=981)</code> key and queries the map.</p>
<p>The result? <code>map.get(session)</code> returns <code>null</code>. The user gets logged out immediately.</p>
<p>Why does <code>HashMap</code> claim the session key does not exist when both key objects contain identical data?</p>
<p>The answer lies in the contract between <code>equals()</code> and <code>hashCode()</code>.</p>
<hr />
<h2>Identity (<code>==</code>) vs Value Equality (<code>equals</code>)</h2>
<p>Picture two printed copies of the same form. They say the same thing, but they are still two separate sheets of paper. Asking "is this the very same sheet?" is what <code>==</code> does. Asking "do these say the same thing?" is what <code>equals</code> does.</p>
<p>In Java, a variable that holds an object really holds a reference, which is the address of where the object lives in memory. The heap is the area of memory where Java stores objects. The <code>==</code> operator compares memory addresses when applied to object references. It returns <code>true</code> only if both variables point to the exact same heap allocation (the same block of memory).</p>
<pre><code class="language-java">User userA = new User(101, "Alex");
User userB = new User(101, "Alex");

boolean sameAddress = (userA == userB); // false! Separate heap allocations.
</code></pre>
<pre><code class="language-text">HEAP
+-----------------------------------+
| User Object 0x1A8F                |
| id: 101, name: "Alex"             |  &lt;--- userA
+-----------------------------------+

+-----------------------------------+
| User Object 0x2B90                |
| id: 101, name: "Alex"             |  &lt;--- userB
+-----------------------------------+
</code></pre>
<p>To compare the fields you care about (here the user ID and name) rather than memory locations, Java provides the <code>equals(Object obj)</code> method on <code>java.lang.Object</code>.</p>
<p>Every Java class automatically inherits from the built-in class <code>java.lang.Object</code>. Inheriting means your class gets the methods <code>Object</code> already has, including <code>equals</code>, without you writing them. To override a method means to write your own version of it in your class, which replaces the inherited one.</p>
<p>By default, <code>Object.equals()</code> simply performs <code>this == obj</code>. You must override it to enforce value-based equality, meaning two objects count as equal when their fields match.</p>
<hr />
<h2>The Rules <code>equals()</code> Must Follow</h2>
<p>Think of "equals" the way you think of "is the same age as": it has to behave sensibly. If Ana is the same age as Ben, then Ben is the same age as Ana.</p>
<p>The Java standard library requires any <code>equals()</code> override to fulfill five mathematical properties. A contract here means a rule that the Java libraries rely on and trust you to follow. The compiler does not check it.</p>
<ol>
<li><strong>Reflexive</strong>: <code>x.equals(x)</code> must return <code>true</code>. (Everything equals itself.)</li>
<li><strong>Symmetric</strong>: <code>x.equals(y)</code> must return <code>true</code> if and only if <code>y.equals(x)</code> returns <code>true</code>. (If A says it equals B, B must say it equals A.)</li>
<li><strong>Transitive</strong>: If <code>x.equals(y)</code> is <code>true</code> and <code>y.equals(z)</code> is <code>true</code>, then <code>x.equals(z)</code> must return <code>true</code>. (If A equals B and B equals C, then A equals C.)</li>
<li><strong>Consistent</strong>: Multiple invocations return identical results unless fields mutated. (Mutated means changed after creation. The answer may change only if a field used in the comparison changed.)</li>
<li><strong>Null comparison</strong>: <code>x.equals(null)</code> must always return <code>false</code>. (<code>null</code> means "no object", and a real object is never equal to it.)</li>
</ol>
<hr />
<h2>The Hash Code Integer</h2>
<p>Think of a coat check. You hand over your coat and get a numbered ticket, and the attendant hangs the coat on the rack that matches the number. When you come back, the ticket tells the attendant which rack to look at, so nobody searches every rack. A hash code works like that ticket: it is a number that acts as a short label for an object, and it tells the map which shelf to look on.</p>
<p>Formally, a hash code is a 32-bit signed integer (<code>int</code>, a whole number that can be negative) computed from an object's internal fields.</p>
<p>Hash-based collections use this integer to pick a spot in an array. <code>HashMap</code> stores key and value pairs, <code>HashSet</code> stores a set of unique items, and <code>LinkedHashMap</code> is a <code>HashMap</code> variant that also remembers the order items were added. An array here is a numbered row of slots, and each slot is called a bucket. Because the map jumps straight to one bucket instead of checking every entry, a lookup takes about the same time no matter how many entries the map holds. This is written $O(1)$ and read as "constant time".</p>
<p>The real <code>HashMap</code> source code (OpenJDK, <code>java.util.HashMap</code>) computes the bucket index in two steps. First it mixes the high bits of the hash code into the low bits, then it keeps only the low bits that fit the array:</p>
<pre><code class="language-java">int h = key.hashCode();
int hash = h ^ (h &gt;&gt;&gt; 16);          // spread: mix the top 16 bits into the bottom 16
int bucketIndex = (n - 1) &amp; hash;   // n is the array length, always a power of two
</code></pre>
<p>Here <code>&gt;&gt;&gt;</code> shifts the bits of a number to the right, <code>^</code> combines two numbers bit by bit (XOR), and <code>&amp;</code> keeps only the bits set in both numbers. Because <code>n</code> is a power of two (the default is 16), <code>(n - 1) &amp; hash</code> gives a number from 0 up to <code>n - 1</code>, so it always lands inside the array.</p>
<p>If two objects have different hash codes, a hash collection immediately knows they cannot be equal. It skips the expensive field-by-field <code>equals()</code> check. This works because of the contract described below: equal objects must have equal hash codes, so different hash codes prove the objects are not equal.</p>
<h3>How <code>put</code> and <code>get</code> use the hash code, step by step</h3>
<p>Putting an entry in with <code>put(key, value)</code>:</p>
<ol>
<li>The map calls <code>key.hashCode()</code> to get a number.</li>
<li>It spreads that number with <code>h ^ (h &gt;&gt;&gt; 16)</code> and keeps the low bits with <code>(n - 1) &amp; hash</code> to get a bucket index.</li>
<li>If that bucket is empty, the map stores the entry there and is done.</li>
<li>If the bucket already holds entries, the map compares the new key with each stored key using <code>equals()</code>. If one is equal, it replaces that entry's value. If none is equal, it adds the new entry to the bucket.</li>
</ol>
<p>Looking a value up with <code>get(key)</code>:</p>
<ol>
<li>The map calls <code>key.hashCode()</code> and computes the bucket index the same way.</li>
<li>It goes straight to that bucket. Nothing else is searched.</li>
<li>Inside the bucket, it checks the stored keys with <code>equals()</code> (the real code also compares the stored hash first as a quick filter). When one matches, it returns that entry's value. If none matches, it returns <code>null</code>.</li>
</ol>
<p>A tiny worked example with made-up numbers, using a map with 16 buckets:</p>
<ul>
<li>Suppose a key has hash code 1012. Since <code>1012 &gt;&gt;&gt; 16</code> is 0, spreading changes nothing: <code>1012 ^ 0</code> is 1012. In binary, 1012 ends in <code>0100</code>, so <code>15 &amp; 1012</code> is 4. The entry goes in bucket 4.</li>
<li>Later, <code>get</code> is called with a second key that <code>equals()</code> the first and so, following the rule, also has hash code 1012. It computes bucket 4, finds the stored key there, <code>equals()</code> says yes, and the value comes back.</li>
<li>Now suppose the second key broke the rule and had hash code 100000. Here <code>100000 &gt;&gt;&gt; 16</code> is 1, spreading gives <code>100000 ^ 1</code>, which is 100001, and <code>15 &amp; 100001</code> is 1. The map looks in bucket 1, finds nothing, and returns <code>null</code>. It never gets to call <code>equals()</code>, because it never looked in bucket 4.</li>
</ul>
<p>That last case is exactly the bug in this article.</p>
<hr />
<h2>The Unbreakable Contract Relationship</h2>
<p>Besides the five rules above, <code>equals()</code> and <code>hashCode()</code> share one more rule. This is the contract that <code>HashMap</code> depends on:</p>
<blockquote>
<p><strong>If two objects are equal according to <code>equals(Object)</code>, they MUST produce the exact same integer result from <code>hashCode()</code>.</strong></p>
</blockquote>
<p>The reverse is not required: two unequal objects CAN produce identical hash codes. That scenario is called a <strong>hash collision</strong>. Two unequal objects that collide end up in the same bucket, and the map then uses <code>equals()</code> to tell them apart.</p>
<pre><code class="language-text">               +----------------------------------+
               |         Equal Objects            |
               |     (userA.equals(userB) == true)|
               +----------------------------------+
                                |
                                | MUST HAVE
                                v
               +----------------------------------+
               |       Identical Hash Codes       |
               | (userA.hashCode() == userB.hashCode())
               +----------------------------------+
</code></pre>
<hr />
<h2>Production Horror Story: The Missing <code>hashCode()</code> Bug</h2>
<p>A bug like this can slip through because the code compiles, runs without errors, and only returns the wrong answer. Here is code that reproduces the problem from the opening scenario. Read the notes after the listing if any line is unfamiliar:</p>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

class UserSession {
    private final int userId;

    public UserSession(int userId) {
        this.userId = userId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserSession session = (UserSession) o;
        return userId == session.userId;
    }

    // MISSING HASHCODE OVERRIDE!
}

public class IncidentDemo {
    public static void main(String[] args) {
        Map&lt;UserSession, String&gt; roles = new HashMap&lt;&gt;();
        
        UserSession loginSession = new UserSession(981);
        roles.put(loginSession, "ADMIN");

        UserSession requestSession = new UserSession(981);
        
        // loginSession.equals(requestSession) is TRUE, but hashCodes almost certainly differ!
        System.out.println("Role found? " + roles.get(requestSession)); // Prints NULL!
    }
}
</code></pre>
<p>How to read the listing:</p>
<ul>
<li><code>private final int userId</code> declares a field that only this class can see (<code>private</code>) and that cannot be reassigned after the constructor sets it (<code>final</code>).</li>
<li><code>@Override</code> is a note to the compiler that the method below replaces one inherited from <code>Object</code>. If the method name or parameters do not match an inherited method, the compiler reports an error.</li>
<li><code>getClass() != o.getClass()</code> compares the exact types of the two objects. <code>getClass()</code> returns the class an object was created from, so the check returns <code>false</code> for an object of a different type.</li>
<li><code>(UserSession) o</code> is a cast. It tells Java to treat <code>o</code>, which is declared as a plain <code>Object</code>, as a <code>UserSession</code>, so its <code>userId</code> can be read. The type check on the line above makes that safe.</li>
<li><code>Map&lt;UserSession, String&gt;</code> uses generics, the angle-bracket notation that says what a collection holds. Here the keys are <code>UserSession</code> objects and the values are <code>String</code>s. The <code>new HashMap&lt;&gt;()</code> on the right lets Java fill in the same types.</li>
</ul>
<p>What happens when <code>main</code> runs:</p>
<ol>
<li><code>roles.put(loginSession, "ADMIN")</code> hashes <code>loginSession</code> and stores "ADMIN" in the bucket that hash code picks.</li>
<li><code>requestSession</code> is a second, separate object with the same <code>userId</code>. <code>equals()</code> would say it matches <code>loginSession</code>.</li>
<li><code>roles.get(requestSession)</code> hashes <code>requestSession</code>. Without a <code>hashCode()</code> override, that number is almost certainly different, so the map looks in a different bucket, finds nothing, and returns <code>null</code>.</li>
</ol>
<p>Because <code>hashCode()</code> was omitted, <code>loginSession</code> and <code>requestSession</code> inherited the default identity hash code from <code>java.lang.Object</code>. Identity means it is tied to the object itself, not to its field values. The Java documentation says only that <code>Object.hashCode()</code> returns distinct integers for distinct objects as far as is reasonably practical. It does not promise that the number is the memory address, so do not rely on that. The result is that two separate objects get different hash codes in practice.</p>
<p><code>roles.get(requestSession)</code> hashed <code>requestSession</code>, computed a completely different bucket index (very likely, though not guaranteed), inspected an empty array slot, and returned <code>null</code>. It never reached <code>equals()</code>.</p>
<hr />
<h2>Correct Implementation Pattern</h2>
<p>Always override both methods together using the exact same fields. If <code>equals()</code> compares <code>userId</code>, then <code>hashCode()</code> must be built from <code>userId</code> too, so any two objects that <code>equals()</code> calls the same get the same number. <code>Objects.hash(...)</code> is a ready-made helper that combines the fields you give it into one hash code:</p>
<pre><code class="language-java">@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserSession session = (UserSession) o;
    return userId == session.userId;
}

@Override
public int hashCode() {
    return Objects.hash(userId);
}
</code></pre>
<hr />
<h2>Quick Summary</h2>
<ul>
<li><code>==</code> checks reference addresses. <code>equals()</code> checks business value equality (equal in the sense your program cares about, such as the same user ID).</li>
<li>Equal objects must return identical <code>hashCode()</code> values.</li>
<li>Omitting <code>hashCode()</code> causes hash collections to inspect wrong bucket indices, triggering silent lookup failures in production.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>What Happens When You Type docker run? From CLI to containerd to runc</title>
      <link>https://unkletayo.dev/engineering/docker/02-docker-toolchain-cli-daemon-containerd-runc/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/docker/02-docker-toolchain-cli-daemon-containerd-runc/</guid>
      <description>Follow one docker run command through the docker CLI, the dockerd daemon, containerd over gRPC, containerd-shim and runc, the pieces that actually start a container.</description>
      <pubDate>Tue, 29 Sep 2026 07:00:00 GMT</pubDate>
      <category>docker</category>
      <category>architecture</category>
      <category>containerd</category>
      <category>runc</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/docker/02-docker-toolchain-cli-daemon-containerd-runc.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Before you start: you should know what a container and an image are (part 1 of this series) and be able to type a command into a terminal. You do not need to know anything about operating systems. Every unfamiliar term is explained where it first appears.</p>
<p>You log into a production host (the computer, often a remote Linux server, that runs your containers) and execute a simple command:</p>
<pre><code class="language-bash">docker run -d --name web-server -p 80:80 nginx
</code></pre>
<p>The flags are small instructions to Docker. <code>-d</code> runs the container in the background so your terminal is free again. <code>--name web-server</code> gives the container a name you can use later. <code>-p 80:80</code> forwards port 80 on the host to port 80 inside the container (a port is a numbered doorway for network traffic, and web servers listen on port 80 by default). <code>nginx</code> is the image to run, and Nginx is a widely used web server program.</p>
<p>The command returns a 64-character container ID, and Nginx starts serving web traffic.</p>
<p>Later, an engineer performs a rolling software update on the Docker system daemon:</p>
<pre><code class="language-bash">systemctl restart docker
</code></pre>
<p><code>systemctl</code> is the Linux command that starts, stops and restarts background services, and this one restarts the Docker background program. A daemon is a program that runs in the background with no window, waiting for requests. <code>dockerd</code> is the daemon that does Docker's work.</p>
<p>While the <code>dockerd</code> daemon restarts, <strong>not a single HTTP request to the running Nginx container drops</strong>. The web server keeps running uninterrupted.</p>
<p>How does Docker restart its main control daemon without killing active running containers?</p>
<p>To answer this, we must examine the multi-layered process architecture that executes underneath the <code>docker</code> command line interface (CLI, a program you control by typing commands): CLI to <code>dockerd</code> to <code>containerd</code> to <code>containerd-shim</code> to <code>runc</code>. The answer arrives in the process tree section near the end of this article.</p>
<p>An everyday picture helps. Think of a restaurant. The host who takes your order is the CLI. The manager is <code>dockerd</code>, who sets up the table and the bill. The kitchen lead is <code>containerd</code>, who runs each dish from start to finish. A cook assigned to your table is the shim, and the builder who prepares the cooking station and then leaves is <code>runc</code>. If the manager goes home, the cook who is already cooking your dish keeps going. Each name in the chain is a separate program that hands work to the next one, and the rest of the article walks down that chain.</p>
<hr />
<h2>The Architecture of the Process Chain</h2>
<p>Modern Docker is not a single monolithic process. It is a collection of modular tools connected via UNIX sockets and gRPC interfaces.</p>
<p>A UNIX socket is a special file that two programs on the same machine use to send messages to each other. gRPC is a standard way for one program to call another program's functions by sending structured messages. A process is one running program, and every process is started by a parent process.</p>
<p>Read the diagram below from top to bottom. Each box receives a request from the box above it and passes work to the box below it. The paths like <code>/usr/bin/docker</code> show where each program file lives on a Linux disk, and you do not need to memorize them.</p>
<pre><code>[ User Shell ]
      |
      |  docker run nginx
      v
+-------------------------------------------------------------------+
| 1. Docker CLI (/usr/bin/docker)                                   |
+-------------------------------------------------------------------+
      |
      |  HTTP REST API payload over /var/run/docker.sock
      v
+-------------------------------------------------------------------+
| 2. Docker Daemon (/usr/bin/dockerd)                               |
|    - Handles image builds, network bridges, volume management     |
+-------------------------------------------------------------------+
      |
      |  gRPC Requests over /run/containerd/containerd.sock
      v
+-------------------------------------------------------------------+
| 3. containerd Daemon (/usr/bin/containerd)                        |
|    - Manages image snapshots, container lifecycles, content store |
+-------------------------------------------------------------------+
      |
      |  Forks a light daemonless shim process per container
      v
+-------------------------------------------------------------------+
| 4. containerd-shim (/usr/bin/containerd-shim-runc-v2)             |
|    - Holds open stdin/stdout/stderr file descriptors              |
|    - Manages container exit status codes                          |
+-------------------------------------------------------------------+
      |
      |  Executes OCI Bundle
      v
+-------------------------------------------------------------------+
| 5. runc CLI (/usr/bin/runc)                                       |
|    - Sets up Linux namespaces &amp; cgroups via libcontainer          |
|    - Executes entrypoint binary (PID 1)                           |
|    - Replaces itself with that binary (execve), so it is gone     |
+-------------------------------------------------------------------+
      |
      v
+-------------------------------------------------------------------+
| 6. Containerized Process (nginx: master process)                  |
+-------------------------------------------------------------------+
</code></pre>
<p>In plain words, the diagram says this:</p>
<ol>
<li>You type <code>docker run nginx</code>. The CLI turns it into a web-style request and sends it through the socket file <code>/var/run/docker.sock</code>.</li>
<li><code>dockerd</code> receives the request. It prepares the parts around the container (images, networking, storage) and then asks <code>containerd</code> to run it, using gRPC over <code>/run/containerd/containerd.sock</code>.</li>
<li><code>containerd</code> manages the container's life: fetching its files and deciding when it starts and stops. For each container it starts one small helper process, the shim.</li>
<li>The shim stays beside the container. It keeps the container's input and output connected (stdin is where a program reads input, stdout is where it prints normal output, and stderr is where it prints errors) and records how the container ends. It calls <code>runc</code> to do the actual setup.</li>
<li><code>runc</code> asks the Linux kernel (the core of the operating system) to build the isolated environment, then turns itself into the container's program. Namespaces and cgroups, explained in section 5, are how that isolation is built.</li>
<li>The last box is the container's own program, here the Nginx master process, which is the main Nginx process that starts the others.</li>
</ol>
<p>The shim's real file name is <code>containerd-shim-runc-v2</code>. The <code>containerd-shim</code> in the box title and in the process tree later is the same program, shortened. Tools that list processes often show a shortened name, because Linux keeps only the first 15 characters of a process name, and <code>containerd-shim</code> is exactly 15 characters.</p>
<hr />
<h2>1. Docker CLI (<code>/usr/bin/docker</code>)</h2>
<p>The <code>docker</code> command is a client-side Go binary. A binary is a ready-to-run program file, Go is the programming language it was written in, and client-side means it only sends requests and does not do the work itself. It performs no container management or namespace creation itself.</p>
<p>When you type <code>docker run</code>, the CLI translates flags (<code>-d</code>, <code>-p 80:80</code>) into an HTTP JSON payload and transmits it across a local UNIX domain socket at <code>/var/run/docker.sock</code> to the Docker Daemon. HTTP is the request and reply format web browsers use, JSON is a text format for structured data such as <code>{"name": "web"}</code>, and a payload is the data being sent. The socket is a file path standing in for a website address, so the request never leaves the machine:</p>
<pre><code class="language-http">POST /v1.43/containers/create?name=web-server HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "Image": "nginx",
  "ExposedPorts": { "80/tcp": {} },
  "HostConfig": {
    "PortBindings": { "80/tcp": [{ "HostPort": "80" }] }
  }
}
</code></pre>
<p><code>POST</code> means "create something", and <code>/containers/create</code> names what to create. The <code>v1.43</code> is the version number of Docker's API, and the <code>name=web-server</code> part comes from your <code>--name</code> flag. An API (application programming interface) is a set of web-style addresses a program exposes so that other programs can ask it to do things, and a REST API is one built in that style. In the body, <code>80/tcp</code> means port 80 using TCP, the usual protocol for web traffic.</p>
<hr />
<h2>2. Docker Daemon (<code>dockerd</code>)</h2>
<p>The <code>dockerd</code> process acts as the high-level orchestrator for host-level container features. In the restaurant picture, it is the manager who sets up everything around the dish:</p>
<ul>
<li><strong>Build Engine</strong>: Translates Dockerfile instructions into image layers. A Dockerfile is a text recipe of steps for building an image, an image is a read-only package of files plus start settings, and a layer is one step's file changes stacked on the previous ones.</li>
<li><strong>Virtual Networking</strong>: Allocates IP addresses, updates iptables rules, and sets up virtual bridge interfaces (<code>docker0</code>). An IP address is the numeric address that identifies a machine on a network, and each container gets its own. iptables is Linux's built-in list of rules for filtering and routing network traffic. A virtual bridge is a software network switch that connects containers to each other and to the host.</li>
<li><strong>Volume Management</strong>: Mounts host directories or cloud block storage into container paths. Mounting makes a folder or disk appear at a chosen path inside the container, and a host directory is a folder on the real machine.</li>
<li><strong>REST API Server</strong>: Authenticates requests and exposes endpoints.</li>
</ul>
<p>Once <code>dockerd</code> completes high-level network and storage preparation, <strong>it hands off container execution to <code>containerd</code></strong>.</p>
<hr />
<h2>3. <code>containerd</code></h2>
<p>If <code>dockerd</code> is the manager who takes the customer's requirements, <code>containerd</code> is the kitchen lead who runs each dish from start to finish.</p>
<p><code>containerd</code> is an industry-standard, CNCF-graduated container runtime daemon. CNCF is the Cloud Native Computing Foundation, a non-profit that hosts open-source infrastructure projects, and graduated is its highest maturity level. A container runtime is the software that actually starts and manages containers.</p>
<p>While <code>dockerd</code> handles high-level user features, <code>containerd</code> manages low-level container lifecycle execution:</p>
<ul>
<li>Pulling and pushing container images from registries. A registry is a server that stores images for download, and Docker Hub is one.</li>
<li>Unpacking image layers into root filesystems using OverlayFS snapshotters. A root filesystem is the folder tree that becomes the container's <code>/</code>. OverlayFS is a Linux feature that stacks read-only layers into one combined view, and a snapshotter is the <code>containerd</code> component that prepares that view.</li>
<li>Managing container execution state (start, stop, pause).</li>
</ul>
<p><code>containerd</code> listens on a gRPC socket at <code>/run/containerd/containerd.sock</code>. It can operate completely independent of Docker (for example, Kubernetes uses <code>containerd</code> directly via CRI without <code>dockerd</code>). Kubernetes is software that runs many containers across many machines, and CRI (Container Runtime Interface) is the interface it uses to ask a runtime to start containers.</p>
<hr />
<h2>4. <code>containerd-shim</code></h2>
<p>A shim is a small adapter that sits between two parts. Here it is a tiny babysitter program that stays alive next to one container even if its bosses restart.</p>
<p>When <code>containerd</code> is instructed to start a container, it does <strong>not</strong> launch the container process directly under its own process tree. Every running program is a process started by a parent process, and together they form a tree. Fork means to create a new child process.</p>
<p>Instead, <code>containerd</code> forks an intermediate process called <code>containerd-shim</code>. The file on disk is named <code>containerd-shim-runc-v2</code>, which is the same program.</p>
<h3>Why the Shim Exists:</h3>
<ol>
<li><strong>Daemonless Container Execution</strong>: The shim acts as the parent process for the container's PID 1. If <code>dockerd</code> or <code>containerd</code> crash or undergo system upgrades, the shim keeps running, keeping the container process alive! A PID is the number Linux gives each process, and PID 1 here means the first process inside the container, the one the container lives and dies with. The parent matters because a process is tied to the one that started it. If the container were a direct child of <code>dockerd</code>, stopping <code>dockerd</code> would leave it orphaned, with no parent left to track it. Because the container's parent is the shim, and the shim is not <code>dockerd</code>, restarting <code>dockerd</code> does not touch the container. This is the answer to the question at the top, and the process tree below shows it.</li>
<li><strong>I/O Streaming</strong>: Holds open the standard I/O pipes (<code>stdin</code>, <code>stdout</code>, <code>stderr</code>) so logs are captured even if the daemon restarts. Stdin is where a program reads input, stdout is where it prints normal output, stderr is where it prints errors, and a pipe carries that data between programs. Each of these is a file descriptor, a small number a process uses to refer to an open file or pipe.</li>
<li><strong>Exit Code Reporting</strong>: Waits for the container PID 1 process to terminate and reports its exit code back to <code>containerd</code>. An exit code is the number a program returns when it ends, and 0 usually means success.</li>
</ol>
<hr />
<h2>5. <code>runc</code></h2>
<p><code>runc</code> is like the builder who sets up a sealed room (the container), moves the program in, and leaves. It does not stay to supervise.</p>
<p><code>runc</code> is the reference implementation of the Open Container Initiative (OCI) Runtime Specification. A specification is a written rulebook, and a reference implementation is a program that shows how to follow it. OCI is a group that writes shared standards for containers, and part 3 covers it.</p>
<p>When <code>containerd-shim</code> starts, it invokes <code>runc create</code> passing an <strong>OCI Bundle</strong> (a directory containing a <code>config.json</code> file and a <code>rootfs/</code> directory). The <code>config.json</code> file holds the container's settings, and <code>rootfs/</code> holds the files the container will see as its whole disk.</p>
<h3>How <code>runc</code> Creates the Container:</h3>
<p><code>runc</code> translates the settings file into requests to the Linux kernel. Those requests are system calls: the way a program asks the kernel (the core of the operating system) to do something. The <code>(2)</code> after each name is the manual page section number for system calls, and you can ignore it.</p>
<ol>
<li>Reads <code>config.json</code> to extract namespace settings, cgroup limits, capabilities, and environment variables. A namespace gives a process its own private view of one system resource, such as the process list, the network or the files. A cgroup limits how much CPU or memory a group of processes may use. Capabilities are the individual special privileges a process may hold, and environment variables are named settings handed to a program when it starts.</li>
<li>Invokes Linux system calls (<code>clone(2)</code> or <code>unshare(2)</code>) to create new namespaces (PID, NET, MNT, UTS, IPC). These are the process IDs, the network, the mounts (what is attached where in the file tree), the hostname, and the messaging between processes. <code>clone</code> creates a new process, and <code>unshare</code> moves the calling process into new namespaces.</li>
<li>Configures cgroup resource limits in <code>/sys/fs/cgroup/</code>, a special folder where Linux exposes cgroup settings as files.</li>
<li>Executes <code>pivot_root(2)</code> to lock the process into <code>rootfs/</code>. This makes <code>rootfs/</code> appear as the whole filesystem, so the process cannot see the host's files.</li>
<li>Executes <code>execve(2)</code> to replace its own process memory with the target container binary (e.g., <code>nginx</code>). <code>execve</code> swaps the program running in a process for another program, inside the same process.</li>
</ol>
<p><strong>Crucially, <code>execve(2)</code> does not start a second process. The <code>runc</code> process replaces itself with Nginx, so no <code>runc</code> is left running.</strong> Because the same process simply becomes Nginx, <code>runc</code> uses no memory or CPU of its own while the container runs.</p>
<hr />
<h2>Inspecting the Live Process Tree</h2>
<p><code>pstree</code> prints running programs as a family tree. In the command below, <code>pgrep containerd</code> finds containerd's process number and <code>$(...)</code> pastes that number into the command. You can observe this process chain directly on any Linux host using <code>pstree</code>:</p>
<pre><code class="language-bash">$ pstree -plu $(pgrep containerd)
containerd(1042)─┬─containerd-shim(8920)───nginx(8945)───nginx(8990)
                 └─{containerd}(1043)
</code></pre>
<p>The shim appears here as <code>containerd-shim</code>, the shortened form of <code>containerd-shim-runc-v2</code>. The line <code>{containerd}(1043)</code> is a thread of <code>containerd</code>, meaning a separate strand of work inside the same process. In the output, the number in parentheses is each process's ID.</p>
<p>Notice the process tree hierarchy:</p>
<ul>
<li><code>containerd</code> (PID 1042) is the parent of <code>containerd-shim</code> (PID 8920).</li>
<li><code>containerd-shim</code> is the parent of the <code>nginx</code> master process (PID 8945).</li>
<li><code>runc</code> is nowhere in the process tree because it replaced itself with Nginx after setup!</li>
<li><code>dockerd</code> is absent from the container process parentage, which is why restarting <code>dockerd</code> does not disrupt Nginx. It handed the job to <code>containerd</code> and only talks to it by message, so it is not a parent of anything here.</li>
</ul>
<hr />
<h2>Summary &amp; Next Steps</h2>
<p>Docker's modular toolchain separates high-level developer APIs from low-level kernel isolation execution:</p>
<ul>
<li><strong><code>docker</code> CLI</strong> converts terminal commands into REST API requests sent over <code>/var/run/docker.sock</code>.</li>
<li><code>dockerd</code> orchestrates networks, volumes, and API requests.</li>
<li><code>containerd</code> manages image snapshots and container lifecycles via gRPC.</li>
<li><code>containerd-shim</code> keeps container processes alive independently of daemon restarts.</li>
<li><code>runc</code> executes system calls to set up namespaces/cgroups and then replaces itself with the container process.</li>
</ul>
<p>In the next article, we examine The Open Container Initiative (OCI): Image Specification and Runtime Specification.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How the JVM Actually Lays Out Arrays in Memory</title>
      <link>https://unkletayo.dev/engineering/java-collections/01-java-memory-and-arrays/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/java-collections/01-java-memory-and-arrays/</guid>
      <description>How HotSpot lays out primitive arrays compared with arrays of object references, across stack frames, object headers, and CPU caches.</description>
      <pubDate>Thu, 24 Sep 2026 07:00:00 GMT</pubDate>
      <category>java</category>
      <category>datastructures</category>
      <category>memory</category>
      <category>jvm</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/java-collections/01-java-memory-and-arrays.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Before you start: you should know that arrays are numbered from 0 (so <code>numbers[3]</code> is the fourth item), that <code>new</code> creates an object, and that a byte is 8 bits. Everything else is explained below.</p>
<p>Java hides memory addresses by default. Most of the time, that's a blessing: you instantiate an array (create it), append items, pass it to a method, and never think about pointers (variables that hold the memory address of something, like a street address instead of the house) or memory offsets (how far past the start of something a piece of data sits).</p>
<p>Until you scale throughput (how much work your program finishes per second) or inspect a heap dump (a snapshot file listing every object in memory).</p>
<p>Then you notice something baffling: an array holding one million <code>Integer</code> wrappers takes about 20 megabytes of heap, while an array holding one million primitive <code>int</code> values takes 4 (measured on OpenJDK 25 with default settings).</p>
<p>Two terms there need a plain definition. A primitive such as <code>int</code> stores just the number itself. An <code>Integer</code> is an object that wraps one number inside it, and Java converts between the two automatically, which is called boxing. The heap is the large area of memory where Java keeps every object you create with <code>new</code>. That is a five-to-one gap, and the section called The Numbers in Practice, further down, shows where every byte goes.</p>
<p>To understand why custom data structures perform well or fail under memory pressure, you have to look past the Java syntax and see how the HotSpot JVM actually organizes bytes in RAM. The JVM (Java Virtual Machine) is the program that runs your compiled Java code and manages its memory, HotSpot is the most common JVM implementation, and RAM is the computer's working memory.</p>
<hr />
<h2>Stack Frames vs The JVM Heap</h2>
<p>Think of the stack as a sticky note on your desk: you jot down what you need for the current task and throw the note away when the task ends. The heap is a warehouse: bigger items are stored there and found later by a shelf address.</p>
<p>In Java terms, a thread is one line of execution in a running program. Each time a thread calls a method, the JVM sets aside a small block of memory for that call, called a stack frame, and the frames pile up in order on the thread's call stack. When a thread executes a Java method, the JVM divides working memory between two distinct regions, the stack (the frames) and the heap (the shared warehouse of objects):</p>
<pre><code class="language-text">  THREAD STACK (Local execution)             JVM HEAP (Shared Objects)
+-----------------------------+          +----------------------------------+
| process() frame             |          | int[] Object Allocation          |
|                             |          |                                  |
| primitiveVal = 42           |          | Header (Mark Word + Klass Word)  |
| numbersRef -----[0x7A4F00]----------&gt;  | Length: 4                        |
|                             |          | [ 10 | 20 | 30 | 40 ]            |
+-----------------------------+          +----------------------------------+
</code></pre>
<p>Local primitive variables (<code>int</code>, <code>double</code>, <code>boolean</code>) live directly inside the stack frame slot. When the method returns, the frame pops off the call stack (is removed from the top of the pile) and that memory is immediately reclaimed without any garbage collection involvement. Garbage collection is the JVM's automatic cleanup that finds heap objects nothing uses any more and frees their memory.</p>
<p>Reference variables (<code>int[]</code>, <code>String</code>, custom objects) hold a memory address. A reference is Java's word for a pointer. An address is normally 8 bytes (64 bits), but by default the JVM shrinks it to 4 bytes (32 bits) using a feature called Compressed OOPs (compressed ordinary object pointers), which works while the heap stays below roughly 32 gigabytes. This article assumes that default, so every reference below is 4 bytes. That address points to a block of memory allocated inside the shared JVM heap.</p>
<p>Reading the diagram above: <code>primitiveVal</code> is a local variable, so the number 42 sits right in the frame. <code>numbersRef</code> is also a local variable, but it holds only the address <code>0x7A4F00</code> (the <code>0x</code> prefix means the number is written in hexadecimal, base 16). The array itself, including its length, sits in the heap at that address. When the method returns, both locals vanish from the stack, but the array stays in the heap until the garbage collector finds that nothing points to it.</p>
<hr />
<h2>Contiguous Memory: The Primitive Array Layout</h2>
<p>A primitive array is like a row of numbered lockers, all the same size, built side by side. Contiguous means exactly that: back to back with no gaps, like seats in one row of a theater.</p>
<p>When you run:</p>
<pre><code class="language-java">int[] numbers = new int[4];
</code></pre>
<p>The HotSpot JVM allocates a single contiguous slice of bytes on the heap (a byte is the unit memory is counted in, and an <code>int</code> takes 4 of them):</p>
<pre><code class="language-text">Memory Offset   | Field / Content
----------------|----------------------------------------------
0x00 - 0x07     | Mark Word (8 bytes: GC metadata, locks, hash)
0x08 - 0x0B     | Klass Word (4 bytes under Compressed OOPs)
0x0C - 0x0F     | Array Length = 4 (4 bytes)
0x10 - 0x13     | numbers[0] (4 bytes)
0x14 - 0x17     | numbers[1] (4 bytes)
0x18 - 0x1B     | numbers[2] (4 bytes)
0x1C - 0x1F     | numbers[3] (4 bytes)
</code></pre>
<p>How to read the table: the first column is the offset, the number of bytes past the start of the object, written in hexadecimal (<code>0x10</code> is 16). Every object begins with a header, a fixed block the JVM uses for its own bookkeeping. You never write to it, but it costs space. The Mark Word holds things like garbage collection information, locks, and the object's hash code. The Klass Word records which class the object belongs to. For an array the header is 8 + 4 + 4 = 16 bytes (mark word, class word, length), and the elements start right after it, at offset <code>0x10</code>.</p>
<p>Because 32-bit integers sit right next to each other in physical RAM, reading <code>numbers[3]</code> doesn't require scanning through previous elements. Every element is the same size, so the address of element 3 is computed directly from the start of the array rather than found by counting up from element 0.</p>
<p>The CPU calculates the memory address in hardware:</p>
<p>$$\text{Address}(\text{numbers}[i]) = \text{BaseAddress} + 16 + (i \times 4)$$</p>
<p>Here BaseAddress is where the array begins, 16 is the header size from the table above, and 4 is the byte size of each <code>int</code>. Multiplying by the index $i$ jumps straight to element $i$. Worked example: for $i = 3$, the offset is $16 + 3 \times 4 = 28$, which is <code>0x1C</code> in hexadecimal, exactly where <code>numbers[3]</code> sits in the table.</p>
<p>This is why random indexing in an array is strictly $O(1)$. O(1), or constant time, means the work does not grow as the array gets bigger: getting item 3 and item 3,000,000 takes the same number of steps.</p>
<p>There is a second benefit. The CPU keeps recently used memory in a small, very fast storage area called a cache, and it loads memory from RAM in fixed-size chunks called cache lines (commonly 64 bytes). When the CPU reads <code>numbers[0]</code>, its hardware prefetcher pulls the entire 64-byte cache line into the L1 cache (the smallest and fastest level), so <code>numbers[1]</code>, <code>numbers[2]</code> and the rest of that chunk are already waiting. Items stored next to each other usually arrive together, which makes sequential reads very fast.</p>
<hr />
<h2>The Indirection Trap: Object Reference Arrays</h2>
<p>Indirection means reaching a value through a middleman, like a contact list that holds addresses instead of the people. An object array is that list: each locker holds a note saying where to find the real item, and each real item lives somewhere else.</p>
<p>Now contrast primitive arrays with an array of objects:</p>
<pre><code class="language-java">String[] names = new String[3];
names[0] = "Alice";
names[1] = "Bob";
</code></pre>
<p>It's tempting to think this allocates strings contiguously in RAM. It doesn't.</p>
<p><code>String[]</code> is an array of pointers:</p>
<pre><code class="language-text">  STACK                                     HEAP
+----------+          +-----------------------------------------------+
| names    |---------&gt;| String[] Array (Container)                    |
+----------+          | Length: 3                                     |
                      | [0]: 0x9B10 --------------------+             |
                      | [1]: 0x9C40 ------------------+ |             |
                      | [2]: null                     | |             |
                      +-------------------------------|-|-------------+
                                                      | |
                                                      | +--&gt; [ String: "Alice" ]
                                                      +----&gt; [ String: "Bob" ]
</code></pre>
<p>Each index in <code>names</code> stores only a 4-byte pointer (a reference, using the Compressed OOPs size described earlier). The actual <code>String</code> instances live somewhere else in heap memory, often scattered in unrelated places. <code>null</code> in slot <code>[2]</code> means that slot holds no reference yet: it points at nothing.</p>
<p>Walkthrough: <code>new String[3]</code> creates three empty slots, all <code>null</code>. The two assignment lines create the strings <code>"Alice"</code> and <code>"Bob"</code> elsewhere in the heap and store their addresses in slots 0 and 1. The array holds addresses only, never the text itself.</p>
<p>When you loop through <code>names</code>, the CPU has to follow each pointer to a new address, jumping across cache lines and causing CPU cache misses. A cache miss happens when the CPU needs data that is not in its cache and has to fetch it from slower main memory.</p>
<hr />
<h2>The Numbers in Practice</h2>
<p>Start with one element. In a primitive array, each element is a 4-byte number sitting in its locker. In a boxed array, each element is a 4-byte note in the locker (the pointer) plus a separate 16-byte <code>Integer</code> object somewhere else. That is 4 bytes versus 4 + 16 = 20 bytes per element, a ratio of 5 to 1. Multiply by one million and you get the figures in the code below.</p>
<p>Here is what one million elements look like in JVM heap memory:</p>
<pre><code class="language-java">public class MemoryFootprint {
    public static void main(String[] args) {
        int size = 1_000_000;

        // Primitive int array:
        // 16 bytes header + (1,000,000 * 4 bytes) ≈ 4.0 MB total.
        int[] rawNumbers = new int[size];

        // Boxed Integer array:
        // 16 bytes array header + (1,000,000 * 4 bytes pointers) = 4.0 MB
        // PLUS 1,000,000 separate Integer objects (16 bytes each) = 16.0 MB
        // Total footprint: ~20.0 MB, measured with allocated-bytes counters.
        Integer[] boxedNumbers = new Integer[size];
        for (int i = 0; i &lt; size; i++) {
            boxedNumbers[i] = i;
        }
    }
}
</code></pre>
<p>Walkthrough: <code>new Integer[size]</code> creates only the million pointer slots, all <code>null</code> at first. Each <code>boxedNumbers[i] = i;</code> line makes Java box the <code>int</code> (automatically convert it to an <code>Integer</code>), which creates a new <code>Integer</code> object on the heap and stores its address in slot <code>i</code>. Every <code>Integer</code> is an object, so it carries its own header (12 bytes: the 8-byte mark word and a 4-byte class word) plus the 4 bytes of the number, which makes 16 bytes. The array itself costs 16 bytes of header plus 4 million bytes of pointers, about 4 MB. The million objects add 16 million bytes, about 16 MB. Together that is about 20 MB, against about 4 MB for the <code>int</code> array.</p>
<p>That 5x memory bloat isn't just wasted RAM. With compressed references off, an <code>Integer</code> grows to 24 bytes and the ratio to 6x.</p>
<p>It also means the Garbage Collector has to track, mark, and sweep one million individual objects during a collection cycle instead of inspecting a single contiguous byte buffer. Marking means finding which objects are still in use, and sweeping means freeing the ones that are not.</p>
<hr />
<h2>What Comes Next</h2>
<p>Understanding contiguous memory allocation and reference pointer indirection is the foundation for everything we will build in this series.</p>
<p>In Part 2, we dissect how the JVM calculates <code>equals()</code> and <code>hashCode()</code> contract rules, why hash collisions (terms we define in Part 2) degrade memory lookups, and how that shapes custom collection implementations.</p>
]]></content:encoded>
    </item>
    <item>
      <title>What Docker Actually Adds to Linux Isolation Primitives</title>
      <link>https://unkletayo.dev/engineering/docker/01-what-docker-adds-to-kernel-primitives/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/docker/01-what-docker-adds-to-kernel-primitives/</guid>
      <description>What Docker added on top of the namespaces, cgroups and OverlayFS already in the Linux kernel, and why raw kernel interfaces needed a container runtime around them.</description>
      <pubDate>Tue, 22 Sep 2026 07:00:00 GMT</pubDate>
      <category>docker</category>
      <category>linux</category>
      <category>containers</category>
      <category>kernel</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/docker/01-what-docker-adds-to-kernel-primitives.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Before you start: this article assumes you have run a few shell commands, know that a process is one running instance of a program, and have typed <code>docker run</code> at least once. Every Linux term is explained where it first appears.</p>
<p>Think of a container as a hotel room. You share the building (the machine) with other guests, but your room has its own door, its own bathroom, and a meter that limits how much water you can use. Nothing in the building was invented for you. The hotel just arranges existing walls and plumbing so that your room feels private.</p>
<p>Docker didn't invent container isolation.</p>
<p>A container is an ordinary program run so that it sees only its own files, its own network, and a limited share of the machine's resources, as if it had a computer to itself. Linux can do this without Docker.</p>
<p>The kernel is the core of the operating system: the part that controls the hardware, the memory, and the running programs on behalf of every other program. A process is one running instance of a program, and Linux gives each process a number called a process ID (PID).</p>
<p>By the late 2000s, the Linux kernel already had the building blocks for running an isolated process:</p>
<ul>
<li><strong>Namespaces</strong> (<code>clone(2)</code> flags like <code>CLONE_NEWPID</code>, <code>CLONE_NEWNET</code>, and <code>CLONE_NEWNS</code>) to give processes their own isolated view of system trees. A namespace gives a process its own private view of one kind of system resource, such as the list of process IDs, the network interfaces, or the mounted filesystems. <code>clone(2)</code> is a system call, which is a request a program makes to the kernel for something it cannot do itself, such as creating a process. The <code>CLONE_NEW...</code> flags ask the kernel to create new namespaces for that process. The <code>(2)</code> is the section of the Linux manual where system calls are documented.</li>
<li><strong>Control Groups (cgroups)</strong> to cap CPU cycles, RAM, and block I/O. A cgroup is a kernel feature that sets limits on how much processor time, memory, and disk reading and writing (block I/O) a group of processes may use.</li>
<li><strong><code>pivot_root</code> and <code>chroot</code></strong> to swap root filesystems. Both are system calls that change what a process treats as the top-level folder <code>/</code>, so it cannot see files outside that folder. A root filesystem is the full directory tree a program expects to find, starting at <code>/</code>.</li>
</ul>
<p>Yet almost nobody was deploying microservices in containers back then. Microservices means building one application as many small programs that talk to each other over a network.</p>
<p>The reason was practical ergonomics: stitching those primitives together by hand was a nightmare. Setting up an isolated environment meant calling those system calls yourself, mounting virtual <code>/proc</code> filesystems manually, setting up virtual ethernet pairs with raw <code>ip link</code> commands, and tweaking pseudo-files inside <code>/sys/fs/cgroup/</code>.</p>
<p>Some of those terms need unpacking. Mounting attaches a filesystem to a folder so its contents appear there. <code>/proc</code> and <code>/sys</code> are special folders where Linux exposes kernel information as files, and writing to some of them changes kernel settings. A virtual ethernet pair (veth) is two software network ports joined like the two ends of a cable: whatever enters one end comes out the other. <code>ip link</code> is a command-line tool for managing network interfaces.</p>
<p>Miss a single cleanup step in a bash script (a text file of shell commands that run in order), and your process leaked network interfaces into the host namespace or left root mount points exposed. The host is the real machine, or the operating system, that runs the containers. Leaking means leaving stray network devices or mounts behind on it.</p>
<p>Docker succeeded because it solved the distribution and developer experience problem. It took the disconnected kernel interfaces and wrapped them in a consistent runtime model. A runtime is the software that actually starts and manages containers, and distribution means getting software onto other machines.</p>
<hr />
<h2>The Raw Primitive Gap</h2>
<p>To see why the abstraction matters, contrast what the kernel expects with what Docker presents to the user. It is like cooking from raw ingredients versus ordering a ready meal: the left side is the manual steps, and the right side is the one Docker command that does them all.</p>
<pre><code>RAW LINUX KERNEL CALLS                         DOCKER RUNTIME ABSTRACTION
+---------------------------------------+     +---------------------------------------+
|  sys_clone(CLONE_NEWPID | NEWNET...)  | --&gt; |  docker run -d --name web -p 80:80 \  |
|  /sys/fs/cgroup/memory/my_app/        |     |             -m 512m nginx             |
|  mount -t overlay ...                 |     |  (Single declarative execution)       |
+---------------------------------------+     +---------------------------------------+
</code></pre>
<p>In the Docker command, <code>-d</code> runs the container in the background, <code>--name web</code> names it, <code>-p 80:80</code> connects port 80 on the host to port 80 in the container, <code>-m 512m</code> caps its memory at 512 megabytes, and <code>nginx</code> is the image to run. nginx is a popular web server program, and an image is the packaged software the container starts from (more on images below).</p>
<p>Without Docker or a container manager, running an isolated web server requires orchestrating five separate subsystems:</p>
<ol>
<li><strong>Process Isolation</strong>: Calling <code>clone(2)</code> or <code>unshare(2)</code> with half a dozen flags to detach PID, mount, IPC, and network trees. <code>unshare(2)</code> is a system call that detaches the calling process from namespaces it shares with others. PID means process ID numbers, and IPC (inter-process communication) means shared memory and message queues.</li>
<li><strong>Filesystem Pivoting</strong>: Unpacking a rootfs tarball, setting up <code>tmpfs</code> mounts, and executing <code>pivot_root(2)</code> so the process can't escape into the host root. A tarball is a single archive file (<code>.tar</code>) that bundles many files, and <code>tmpfs</code> is a filesystem kept in memory instead of on disk.</li>
<li><strong>Resource Guardrails</strong>: Writing memory limits into <code>/sys/fs/cgroup/memory.max</code> and writing the target process ID into <code>cgroup.procs</code>. In plain terms, you write a number into one file and the process's ID into another, and the kernel reads those files as instructions.</li>
<li><strong>Network Plumbing</strong>: Giving the container a working network connection and making it reachable from outside. In plain terms, this takes five steps:
<ul>
<li>Create a <code>veth</code> pair, the two linked virtual ports described earlier.</li>
<li>Move one end into the container's network namespace, so the container owns it.</li>
<li>Attach the other end to a bridge on the host. A bridge is a software network switch that connects several virtual ports.</li>
<li>Give the container an IP address from a subnet. An IP address identifies a machine on a network, and a subnet is a block of related addresses.</li>
<li>Add an <code>iptables</code> rule for port forwarding. <code>iptables</code> is the Linux firewall and packet-rewriting tool. DNAT (destination network address translation) rewrites a packet's destination address, which is how traffic arriving at a port on the host gets sent on to the container. A port is a numbered door on an IP address that a program listens on.</li>
</ul>
</li>
<li><strong>Artifact Packaging</strong>: Figuring out how to ship dependencies and runtime binaries in a repeatable bundle. Dependencies are the other pieces of software a program needs to run, and binaries are compiled, ready-to-run program files.</li>
</ol>
<p>Docker packaged those responsibilities into four core components. These are the four numbered sections below. They are a different grouping from the five jobs above, not a one-to-one match, so do not expect job 1 to line up with section 1.</p>
<hr />
<h3>1. The Portable Root Filesystem (OCI Image Format)</h3>
<p>A shipping container holds goods in a standard box that any ship or truck can carry. A Docker image does the same for software: the program plus everything it needs, in one standard package. OCI (Open Container Initiative) is the published standard for that image format.</p>
<p>Before container images became standard, distributing software meant building RPMs, Debian packages, or shipping fat tarballs. RPM and Debian packages are the install formats used by the Red Hat and Debian families of Linux. If the host machine had a mismatched <code>glibc</code> version or was missing <code>libssl.so.1.1</code>, the deployment broke on startup. <code>glibc</code> is the standard C library most Linux programs depend on, and <code>libssl.so.1.1</code> is a shared library file (a file of code that many programs load) used for encryption. If the machine has a different version than the program expects, the program fails.</p>
<p>Docker paired the root filesystem with a JSON manifest, a small text file that describes what the image contains and how to run it. The image contains:</p>
<ul>
<li>The exact root directory tree (<code>rootfs</code>) required by the app.</li>
<li>Every shared library, binary, and configuration file.</li>
<li>Metadata describing entrypoints, environment defaults, and exposed ports. The entrypoint is the command that starts when the container runs, and environment variables are named settings passed to the program.</li>
</ul>
<p>Because the process runs against its own bundled rootfs rather than the host's <code>/usr/lib</code> (the folder where Linux keeps shared libraries for installed programs), it runs with identical behavior on a local laptop or an AWS instance running a completely different Linux distribution. A distribution is a packaged version of Linux, such as Debian or Fedora, and an AWS instance is a virtual server rented from Amazon Web Services.</p>
<hr />
<h3>2. Copy-on-Write Layering via OverlayFS</h3>
<p>Shipping a full root filesystem on every minor code change would slow deployment and waste disk space.</p>
<p>Imagine a printed page covered by clear plastic sheets. You write on the top sheet and never touch the page underneath. Layers work like those sheets. Copy-on-write means a file is copied only at the moment someone first changes it. OverlayFS is the Linux filesystem that stacks layers so they appear as one folder.</p>
<p>Docker solved this with layered storage drivers (primarily OverlayFS). When building an image, each instruction produces an immutable layer identified by a SHA-256 digest. Immutable means never changed after creation. A SHA-256 digest (a hash) is a fixed-length fingerprint computed from content, so identical content always gets the same fingerprint. If five different services build on <code>debian:bookworm-slim</code> (the name and tag of a small Debian base image), they all share the exact same base layer on disk and in the page cache, which is the memory the kernel uses to keep recently read file data:</p>
<pre><code>+---------------------------------------------------------------+
| Container Writable Layer (Read/Write Delta)                    |
+---------------------------------------------------------------+
| Layer 3: Application Binary (12 MB)                           |
+---------------------------------------------------------------+
| Layer 2: Go Runtime / Libraries (110 MB)                      |
+---------------------------------------------------------------+
| Layer 1: Debian Base OS (55 MB) [Shared across all containers]|
+---------------------------------------------------------------+
</code></pre>
<p>The sizes in this diagram are illustrative example values, not measurements. The example assumes the app was written in Go, a programming language, so layer 2 holds the Go runtime and libraries. The top box is the container's own writable layer, where its changes are stored.</p>
<p>The container only writes changes to its thin top layer. The heavy base remains read-only and shared. If a program edits a file that lives in a lower layer, that file is first copied up into the top layer and edited there, and the lower layers stay unchanged.</p>
<hr />
<h3>3. Automated Bridge Networking and Embedded DNS</h3>
<p>Each container is like a separate house that needs a street, an address, and a way for mail to reach it. Docker lays the street (a bridge), assigns the addresses, and forwards the mail (ports).</p>
<p>Configuring Linux network namespaces by hand is tedious and error-prone. Here, <code>&lt;container_pid&gt;</code> stands for the process ID of the container's main process, and <code>br0</code> is a bridge you created earlier:</p>
<pre><code class="language-bash"># Manual Linux Networking setup (WITHOUT Docker):
# 1. Create a linked pair of virtual ports, veth0 and veth1.
ip link add veth0 type veth peer name veth1
# 2. Move one end (veth1) into the container's network namespace.
ip link set veth1 netns &lt;container_pid&gt;
# 3. Give that end an IP address. The /24 means the first 24 bits
#    identify the network (CIDR notation for the subnet size).
ip addr add 192.168.1.2/24 dev veth1
# 4. Plug the other end (veth0) into the bridge br0.
ip link set veth0 master br0
# 5. Forward TCP traffic arriving on port 80 to the container.
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.2:80
</code></pre>
<p>With Docker, <code>-p 8080:80</code> triggers all of this automatically. It means connections to port 8080 on the host go to port 80 inside the container. The daemon (a program that runs continuously in the background; Docker's is called <code>dockerd</code>) manages virtual bridge devices (<code>docker0</code>), allocates private IPs from an internal subnet, sets up kernel forwarding rules, and runs an internal DNS resolver at <code>127.0.0.11</code> so containers talk to each other by container name instead of volatile IP addresses. DNS translates names into IP addresses. The <code>127.x.x.x</code> addresses refer to the local machine, so <code>127.0.0.11</code> is reachable only from inside the container. Volatile means the address can change each time a container restarts.</p>
<hr />
<h3>4. Content-Addressable Distribution (Registry Protocol)</h3>
<p>A registry is like a library of images: you push to upload one and pull to download one. Content-addressable means each item is looked up by a fingerprint of its content, not by a name someone chose.</p>
<p>Docker standardized how images get pushed, pulled, and verified through the Docker Registry HTTP API V2. HTTP is the standard protocol web browsers use, and this API is how Docker talks to a registry over it.</p>
<p>Because every layer is identified by its SHA-256 hash, pushing an update transfers only the modified layer delta, meaning only the part that changed. If your base layers didn't change, the registry skips them entirely. Before uploading each layer, Docker checks whether the registry already has one with the same fingerprint, and skips it if so.</p>
<hr />
<h2>What Comes Next</h2>
<p>Understanding these boundaries changes how you debug container issues. When an application gets killed with exit code 137, that's not a mystery inside Docker. An exit code is the number a program returns when it stops, and 137 means the process was killed by signal 9 (SIGKILL), because shells report 128 plus the signal number. One common cause is the kernel's cgroup memory controller enforcing a memory limit, but any SIGKILL produces 137, so treat it as a hint and check the container's memory limit and logs. When a port is unreachable, the culprit is usually an <code>iptables</code> rule or a missing route on the bridge. A route is a rule telling the kernel where to send packets for a given address.</p>
<p>In Part 2, we trace the execution path from the Docker CLI command down through <code>dockerd</code>, <code>containerd</code>, and <code>runc</code> (the three programs in Docker's execution chain, each explained there) to see what happens the moment you press enter.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Why I Take Software Apart: Learning from First Principles</title>
      <link>https://unkletayo.dev/essays/learning-from-first-principles/</link>
      <guid isPermaLink="true">https://unkletayo.dev/essays/learning-from-first-principles/</guid>
      <description>Reflections on why high-level frameworks break under pressure and why understanding lower layers makes you a calmer engineer.</description>
      <pubDate>Sat, 05 Sep 2026 07:00:00 GMT</pubDate>
      <category>learning</category>
      <category>systemsengineering</category>
      <category>career</category>
      <category>philosophy</category>
      <enclosure url="https://unkletayo.dev/cover/essays/learning-from-first-principles.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>It is easy to spend an entire software career as a consumer of abstractions.</p>
<p>You install an npm package, import a Spring annotation, paste a Dockerfile snippet, and move on. As long as the happy path holds, you can ship features quickly without ever knowing what the machine is actually doing underneath.</p>
<p>Then production throws an edge case.</p>
<p>A React component stutters because a V8 garbage collection cycle pauses the main thread long enough to drop frames. A container gets abruptly killed with exit code 137 because the JVM heap size wasn't calibrated against cgroup v2 memory limits. A microservice deadlocks because two database transactions acquired row locks in reverse order.</p>
<p>In those moments, the abstractions vanish. The framework documentation can't save you because the problem isn't inside the framework; it's happening in the operating system, the runtime memory model, or the network socket buffer.</p>
<hr />
<h2>What First-Principles Engineering Actually Means</h2>
<p>To me, first-principles thinking in engineering isn't an academic exercise. It's asking the mechanical question before reaching for a tool:</p>
<ul>
<li>Instead of asking <em>"Which state library is trending?"</em>, asking <em>"How does the browser event loop schedule microtasks against layout and GPU composite passes?"</em></li>
<li>Instead of asking <em>"Why is Docker slow?"</em>, asking <em>"How does OverlayFS traverse directory layers when a process issues a write syscall?"</em></li>
</ul>
<p>When you understand the byte layout of an array or how an OS kernel schedules threads across CPU cores, modern frameworks stop feeling like mysterious black boxes. You start seeing them for what they actually are: pragmatic collections of trade-offs made by engineers dealing with the exact same physical constraints.</p>
<hr />
<h2>Why I Write Publicly</h2>
<p>This site is my notebook. When I study how V8 compiles bytecodes, how Kafka achieves zero-copy I/O with <code>sendfile</code>, or why <code>ArrayList</code> grows its internal array by half each time it fills, writing it down forces me to test whether I understand it or if I'm just nodding along with documentation.</p>
<p>If you can't explain the failure modes in plain code, you don't understand the abstraction yet.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Part 2: Web Server Mastery - Serving Static Content Like a Pro</title>
      <link>https://unkletayo.dev/engineering/standalone/part-2-web-server-mastery-serving-static-content-like-a-pro/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/part-2-web-server-mastery-serving-static-content-like-a-pro/</guid>
      <description>Part 2 of the Nginx series. Serve several websites from one server, cache static files and turn on compression.</description>
      <pubDate>Sat, 09 Aug 2025 07:00:00 GMT</pubDate>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/part-2-web-server-mastery-serving-static-content-like-a-pro.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxv9u83559xjyzeh1mx3.png" alt="Nginx Logo" /></p>
<p><em>Building on our foundation from <a href="https://unkletayo.dev/engineering/standalone/the-complete-nginx-mastery-series-from-zero-to-production-hero/">Part 1</a>, let's transform your basic Nginx setup into a high-performance web server that can handle multiple sites, optimize content delivery, and provide an excellent user experience.</em></p>
<hr />
<h2>What We'll Accomplish Today</h2>
<p>By the end of this part, you'll have:</p>
<ul>
<li>Multiple websites running on a single server</li>
<li>Lightning-fast static file serving with caching</li>
<li>Automatic compression for faster loading</li>
<li>Professional error pages that don't break user experience</li>
<li>Smart redirects and URL rewriting</li>
<li>Performance optimizations that make your sites fly</li>
</ul>
<hr />
<h2>The Multi-Site Challenge</h2>
<p>Remember our simple single-site setup from <a href="https://unkletayo.dev/engineering/standalone/the-complete-nginx-mastery-series-from-zero-to-production-hero/">Part 1</a>? In the real world, you'll often need to serve multiple websites from one server. Let's see how Nginx makes this incredibly straightforward.</p>
<h3>Understanding Server Blocks</h3>
<p>Think of server blocks as separate apartments in a building. Each has its own address (domain name), its own space (directory), and its own rules, but they all share the same building infrastructure (your server).</p>
<h3>Setting Up Multiple Sites</h3>
<p>Let's create three different websites to demonstrate various scenarios:</p>
<h4>Site 1: Personal Portfolio (Static HTML/CSS/JS)</h4>
<p>Create <code>/etc/nginx/sites-available/portfolio.conf</code>:</p>
<pre><code class="language-nginx">server {
    listen 80;
    server_name portfolio.local www.portfolio.local;
    
    root /var/www/portfolio;
    index index.html index.htm;
    
    # Enable efficient file serving
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # Main location block
    location / {
        try_files $uri $uri/ =404;
        
        # Add security headers
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
    }
    
    # Optimize static assets
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary Accept-Encoding;
        
        # Enable compression for text-based assets
        gzip_static on;
    }
    
    # Handle favicon requests gracefully
    location = /favicon.ico {
        log_not_found off;
        access_log off;
        expires 1y;
    }
    
    # Custom error pages
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
</code></pre>
<h4>Site 2: Blog (Static Site Generator - Jekyll/Hugo style)</h4>
<p>Create <code>/etc/nginx/sites-available/blog.conf</code>:</p>
<pre><code class="language-nginx">server {
    listen 80;
    server_name blog.local www.blog.local;
    
    root /var/www/blog;
    index index.html;
    
    # Handle clean URLs (no .html extension)
    location / {
        try_files $uri $uri.html $uri/ =404;
    }
    
    # Blog-specific optimizations
    location ~* \.(css|js)$ {
        expires 30d;
        add_header Cache-Control "public";
        gzip_static on;
    }
    
    # Images with longer cache
    location ~* \.(png|jpg|jpeg|gif|webp|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # RSS and sitemap
    location ~* \.(xml|rss)$ {
        expires 1h;
        add_header Content-Type application/xml;
    }
    
    # Deny access to admin/private files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    # Pretty URLs for blog posts
    location ~* ^/posts/(.*)$ {
        try_files /posts/$1.html /posts/$1/index.html =404;
    }
}
</code></pre>
<h4>Site 3: Single Page Application (React/Vue/Angular)</h4>
<p>Create <code>/etc/nginx/sites-available/app.conf</code>:</p>
<pre><code class="language-nginx">server {
    listen 80;
    server_name app.local www.app.local;
    
    root /var/www/app/dist;
    index index.html;
    
    # Handle client-side routing
    location / {
        try_files $uri $uri/ /index.html;
        
        # Don't cache the main HTML file
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        add_header Pragma "no-cache";
        add_header Expires "0";
    }
    
    # Static assets with hash-based filenames
    location ~* \.(js|css)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        gzip_static on;
    }
    
    # Images and fonts
    location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # API proxy (if your SPA needs to call APIs)
    location /api/ {
        proxy_pass http://localhost:3001/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    
    # Health check for load balancers
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}
</code></pre>
<h3>Enabling Your Sites</h3>
<pre><code class="language-bash"># Create symbolic links to enable sites
sudo ln -s /etc/nginx/sites-available/portfolio.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/blog.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/

# Create directory structure
sudo mkdir -p /var/www/{portfolio,blog,app/dist}

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx
</code></pre>
<hr />
<h2>Performance Optimization: Making Your Sites Lightning Fast ⚡</h2>
<h3>1. Compression Configuration</h3>
<p>Create <code>/etc/nginx/conf.d/gzip.conf</code>:</p>
<pre><code class="language-nginx"># Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;

# Specify which file types to compress
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml+rss
    application/atom+xml
    image/svg+xml
    font/truetype
    font/opentype
    application/font-woff
    application/font-woff2;

# Don't compress already compressed files
gzip_disable "MSIE [1-6]\.";

# Serve pre-compressed files if available
gzip_static on;
</code></pre>
<h3>2. Advanced Caching Strategy</h3>
<p>Create <code>/etc/nginx/conf.d/cache.conf</code>:</p>
<pre><code class="language-nginx"># Browser caching for different file types
map $sent_http_content_type $expires {
    default                    off;
    text/html                  1h;
    text/css                   1y;
    application/javascript     1y;
    application/json           1h;
    image/png                  1y;
    image/jpg                  1y;
    image/jpeg                 1y;
    image/gif                  1y;
    image/svg+xml             1y;
    image/x-icon              1y;
    font/woff                 1y;
    font/woff2                1y;
    font/ttf                  1y;
    font/otf                  1y;
    application/pdf           1M;
    video/mp4                 1M;
    video/webm                1M;
}

expires $expires;

# Add cache control headers
add_header Cache-Control "public" always;

# Conditional requests support
if_modified_since before;
</code></pre>
<h3>3. File Type Optimization</h3>
<p>Update your main <code>nginx.conf</code> to include:</p>
<pre><code class="language-nginx">http {
    # ... existing configuration ...
    
    # Optimize file serving
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # Increase buffer sizes for better performance
    client_body_buffer_size 128k;
    client_max_body_size 50m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    
    # Output compression
    output_buffers 1 32k;
    postpone_output 1460;
    
    # Include our optimization configs
    include /etc/nginx/conf.d/gzip.conf;
    include /etc/nginx/conf.d/cache.conf;
}
</code></pre>
<hr />
<h2>Creating Professional Error Pages 🎨</h2>
<p>Default error pages are boring and unprofessional. Let's create custom ones that match your brand.</p>
<h3>Custom 404 Page</h3>
<p>Create <code>/var/www/errors/404.html</code>:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Page Not Found - 404&lt;/title&gt;
    &lt;style&gt;
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }
        
        .error-container {
            text-align: center;
            max-width: 600px;
            padding: 2rem;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 20px;
            backdrop-filter: blur(10px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        }
        
        .error-code {
            font-size: 8rem;
            font-weight: bold;
            margin-bottom: 1rem;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
        }
        
        .error-message {
            font-size: 1.5rem;
            margin-bottom: 2rem;
            opacity: 0.9;
        }
        
        .error-description {
            font-size: 1rem;
            margin-bottom: 2rem;
            opacity: 0.8;
            line-height: 1.6;
        }
        
        .home-button {
            display: inline-block;
            padding: 12px 30px;
            background: rgba(255, 255, 255, 0.2);
            color: white;
            text-decoration: none;
            border-radius: 25px;
            transition: all 0.3s ease;
            border: 2px solid rgba(255, 255, 255, 0.3);
        }
        
        .home-button:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: translateY(-2px);
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        
        .search-box {
            margin: 2rem 0;
        }
        
        .search-input {
            padding: 12px 20px;
            border: none;
            border-radius: 25px;
            width: 300px;
            max-width: 100%;
            background: rgba(255, 255, 255, 0.9);
            color: #333;
            font-size: 1rem;
        }
        
        .search-button {
            padding: 12px 20px;
            background: #ff6b6b;
            color: white;
            border: none;
            border-radius: 25px;
            margin-left: 10px;
            cursor: pointer;
            transition: background 0.3s ease;
        }
        
        .search-button:hover {
            background: #ff5252;
        }
        
        .helpful-links {
            margin-top: 2rem;
        }
        
        .helpful-links a {
            color: rgba(255, 255, 255, 0.8);
            text-decoration: none;
            margin: 0 15px;
            transition: color 0.3s ease;
        }
        
        .helpful-links a:hover {
            color: white;
            text-decoration: underline;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="error-container"&gt;
        &lt;div class="error-code"&gt;404&lt;/div&gt;
        &lt;h1 class="error-message"&gt;Oops! Page Not Found&lt;/h1&gt;
        &lt;p class="error-description"&gt;
            The page you're looking for seems to have wandered off into the digital wilderness. 
            Don't worry though, even the best explorers sometimes take a wrong turn!
        &lt;/p&gt;
        
        &lt;div class="search-box"&gt;
            &lt;input type="text" class="search-input" placeholder="Search for what you need..." id="searchInput"&gt;
            &lt;button class="search-button" onclick="performSearch()"&gt;Search&lt;/button&gt;
        &lt;/div&gt;
        
        &lt;a href="/" class="home-button"&gt;🏠 Take Me Home&lt;/a&gt;
        
        &lt;div class="helpful-links"&gt;
            &lt;a href="/about"&gt;About&lt;/a&gt;
            &lt;a href="/contact"&gt;Contact&lt;/a&gt;
            &lt;a href="/blog"&gt;Blog&lt;/a&gt;
            &lt;a href="/sitemap.xml"&gt;Sitemap&lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;script&gt;
        function performSearch() {
            const query = document.getElementById('searchInput').value;
            if (query.trim()) {
                window.location.href = `/search?q=${encodeURIComponent(query)}`;
            }
        }
        
        document.getElementById('searchInput').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                performSearch();
            }
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3>Custom 50x Error Page</h3>
<p>Create <code>/var/www/errors/50x.html</code>:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Server Error - We'll Be Right Back&lt;/title&gt;
    &lt;style&gt;
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #ff7b7b 0%, #ff416c 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }
        
        .error-container {
            text-align: center;
            max-width: 600px;
            padding: 2rem;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 20px;
            backdrop-filter: blur(10px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        }
        
        .robot {
            font-size: 4rem;
            margin-bottom: 1rem;
            animation: shake 2s infinite;
        }
        
        @keyframes shake {
            0%, 100% { transform: rotate(0deg); }
            25% { transform: rotate(-5deg); }
            75% { transform: rotate(5deg); }
        }
        
        .error-message {
            font-size: 1.8rem;
            margin-bottom: 1rem;
            font-weight: 600;
        }
        
        .error-description {
            font-size: 1rem;
            margin-bottom: 2rem;
            opacity: 0.9;
            line-height: 1.6;
        }
        
        .status-info {
            background: rgba(255, 255, 255, 0.1);
            padding: 1rem;
            border-radius: 10px;
            margin: 1rem 0;
            font-family: monospace;
        }
        
        .retry-button {
            display: inline-block;
            padding: 12px 30px;
            background: rgba(255, 255, 255, 0.2);
            color: white;
            text-decoration: none;
            border-radius: 25px;
            transition: all 0.3s ease;
            border: 2px solid rgba(255, 255, 255, 0.3);
            margin: 0 10px;
        }
        
        .retry-button:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: translateY(-2px);
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="error-container"&gt;
        &lt;div class="robot"&gt;🤖&lt;/div&gt;
        &lt;h1 class="error-message"&gt;Our Servers Are Taking a Coffee Break&lt;/h1&gt;
        &lt;p class="error-description"&gt;
            We're experiencing some technical difficulties. Our team of digital mechanics 
            is working hard to get everything running smoothly again.
        &lt;/p&gt;
        
        &lt;div class="status-info"&gt;
            Error Code: 500-503&lt;br&gt;
            Estimated Fix Time: A few minutes&lt;br&gt;
            Status: Engineers Deployed ☕
        &lt;/div&gt;
        
        &lt;a href="javascript:location.reload()" class="retry-button"&gt; Try Again&lt;/a&gt;
        &lt;a href="/" class="retry-button"&gt;Go Home&lt;/a&gt;
    &lt;/div&gt;
    
    &lt;script&gt;
        // Auto-refresh every 30 seconds
        setTimeout(() =&gt; {
            location.reload();
        }, 30000);
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3>Update Your Sites to Use Custom Error Pages</h3>
<p>Add this to each of your site configurations:</p>
<pre><code class="language-nginx"># Custom error pages
error_page 404 /errors/404.html;
error_page 500 502 503 504 /errors/50x.html;

location = /errors/404.html {
    root /var/www;
    internal;
}

location = /errors/50x.html {
    root /var/www;
    internal;
}
</code></pre>
<hr />
<h2>Smart Redirects and URL Rewriting 🔄</h2>
<h3>Common Redirect Scenarios</h3>
<pre><code class="language-nginx">server {
    listen 80;
    server_name example.com www.example.com;
    
    # Redirect old blog structure to new one
    location ~ ^/blog/(\d{4})/(\d{2})/(.+)$ {
        return 301 /posts/$1-$2-$3;
    }
    
    # Redirect old file extensions
    location ~ ^(.+)\.php$ {
        return 301 $1;
    }
    
    # Redirect trailing slashes for files
    location ~ ^(.+)/$ {
        return 301 $1;
    }
    
    # Force HTTPS (when you have SSL)
    if ($scheme != "https") {
        return 301 https://$server_name$request_uri;
    }
    
    # Redirect www to non-www (or vice versa)
    if ($host = 'www.example.com') {
        return 301 $scheme://example.com$request_uri;
    }
    
    # Handle common typos in domain
    if ($host ~ ^(.*\.)?exampl\.com$) {
        return 301 $scheme://example.com$request_uri;
    }
}
</code></pre>
<h3>Advanced URL Rewriting</h3>
<pre><code class="language-nginx"># Pretty URLs for a PHP application
location / {
    # Try exact file, then directory, then rewrite
    try_files $uri $uri/ @rewrite;
}

location @rewrite {
    # Convert /category/subcategory/item to /index.php?cat=category&amp;sub=subcategory&amp;item=item
    rewrite ^/([^/]+)/([^/]+)/([^/]+)/?$ /index.php?cat=$1&amp;sub=$2&amp;item=$3 last;
    
    # Convert /category/item to /index.php?cat=category&amp;item=item
    rewrite ^/([^/]+)/([^/]+)/?$ /index.php?cat=$1&amp;item=$2 last;
    
    # Convert /item to /index.php?item=item
    rewrite ^/([^/]+)/?$ /index.php?item=$1 last;
}

# API versioning
location ~ ^/api/v(\d+)/(.*)$ {
    proxy_pass http://backend/api/$2;
    proxy_set_header X-API-Version $1;
    proxy_set_header Host $host;
}
</code></pre>
<hr />
<h2>Performance Monitoring and Optimization</h2>
<h3>Enable Status Monitoring</h3>
<p>Add this to your main nginx.conf:</p>
<pre><code class="language-nginx">server {
    listen 8080;
    server_name localhost;
    
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        allow 10.0.0.0/8;
        deny all;
    }
    
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}
</code></pre>
<h3>Performance Testing Commands</h3>
<pre><code class="language-bash"># Test your site performance
curl -H "Accept-Encoding: gzip" -H "Cache-Control: no-cache" -w "@curl-format.txt" -o /dev/null -s "http://localhost/"

# Create curl-format.txt for detailed timing
echo 'time_namelookup:  %{time_namelookup}s
time_connect:     %{time_connect}s
time_appconnect:  %{time_appconnect}s
time_pretransfer: %{time_pretransfer}s
time_redirect:    %{time_redirect}s
time_starttransfer: %{time_starttransfer}s
time_total:       %{time_total}s
http_code:        %{http_code}
size_download:    %{size_download}bytes
speed_download:   %{speed_download}bytes/sec' &gt; curl-format.txt

# Check compression
curl -H "Accept-Encoding: gzip,deflate" -v http://localhost/style.css | head

# Monitor connections
watch -n 1 'ss -tuln | grep :80'
</code></pre>
<hr />
<h2>Real-World Performance Tips 🚀</h2>
<h3>1. Preload Critical Resources</h3>
<p>Add to your HTML head:</p>
<pre><code class="language-html">&lt;!-- Preload critical CSS --&gt;
&lt;link rel="preload" href="/css/critical.css" as="style"&gt;

&lt;!-- Preload important fonts --&gt;
&lt;link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin&gt;

&lt;!-- DNS prefetch for external domains --&gt;
&lt;link rel="dns-prefetch" href="//fonts.googleapis.com"&gt;
&lt;link rel="dns-prefetch" href="//cdn.example.com"&gt;
</code></pre>
<h3>2. Optimize Images</h3>
<pre><code class="language-nginx"># WebP support with fallback
location ~* \.(png|jpg|jpeg)$ {
    # Try WebP version first
    try_files $uri.webp $uri =404;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# Serve different image sizes
location ~ ^/images/(.+)_(small|medium|large)\.(jpg|png)$ {
    alias /var/www/images/$1_$2.$3;
    expires 1y;
}
</code></pre>
<h3>3. Security Headers for Production</h3>
<pre><code class="language-nginx"># Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;

# HSTS (when using HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
</code></pre>
<hr />
<h2>Troubleshooting Common Issues 🔧</h2>
<h3>Problem: CSS/JS Not Loading</h3>
<pre><code class="language-bash"># Check file permissions
sudo chown -R www-data:www-data /var/www/
sudo chmod -R 755 /var/www/

# Check MIME types
grep "text/css" /etc/nginx/mime.types
grep "application/javascript" /etc/nginx/mime.types
</code></pre>
<h3>Problem: Pages Not Updating</h3>
<pre><code class="language-bash"># Clear any caches
sudo rm -rf /var/cache/nginx/*

# Check if files are actually updated
ls -la /var/www/yoursite/

# Force reload without cache
curl -H "Cache-Control: no-cache" http://localhost/
</code></pre>
<h3>Problem: Slow Performance</h3>
<pre><code class="language-bash"># Check access logs for slow requests
sudo tail -f /var/log/nginx/access.log | grep -E " [5-9][0-9]{3} "

# Monitor server resources
htop
iotop -o
</code></pre>
<hr />
<h2>What's Next? 🎯</h2>
<p>Congratulations! You now have a professional, high-performance web server setup that can:</p>
<ul>
<li>✅ Serve multiple websites efficiently</li>
<li>✅ Optimize content delivery with compression and caching</li>
<li>✅ Handle errors gracefully with custom pages</li>
<li>✅ Redirect and rewrite URLs intelligently</li>
<li>✅ Monitor performance and troubleshoot issues</li>
</ul>
<p>In <strong>Part 3: Reverse Proxy Magic</strong>, we'll dive into:</p>
<ul>
<li>Connecting your frontend to backend APIs</li>
<li>Handling WebSocket connections</li>
<li>Setting up development vs production environments</li>
<li>Load balancing basics</li>
<li>SSL termination and security</li>
</ul>
<hr />
<h2>Practice Exercises 📝</h2>
<p>Before moving to Part 3, try these:</p>
<h3>Exercise 1: Multi-Site Setup</h3>
<p>Create three different websites with different purposes and optimize each one differently.</p>
<h3>Exercise 2: Performance Audit</h3>
<p>Use tools like Google PageSpeed Insights or GTmetrix to test your sites and implement their suggestions.</p>
<h3>Exercise 3: Custom Error Pages</h3>
<p>Design error pages that match your brand and include helpful navigation.</p>
<h3>Exercise 4: URL Restructuring</h3>
<p>Implement a redirect strategy for an old site structure to a new one.</p>
<hr />
<h2>Quick Reference Commands 📋</h2>
<pre><code class="language-bash"># Test configuration
sudo nginx -t

# Reload configuration
sudo systemctl reload nginx

# Check active sites
ls -la /etc/nginx/sites-enabled/

# Monitor access logs
sudo tail -f /var/log/nginx/access.log

# Check server status
curl http://localhost:8080/nginx_status

# Performance test
ab -n 1000 -c 10 http://localhost/
</code></pre>
<hr />
<p><em>You're building real expertise here! Each part of this series builds practical skills that you'll use every day as a developer. In Part 3, we'll connect all these optimized frontend sites to powerful backend services. Keep experimenting!</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>The Complete Nginx Mastery Series: From Zero to Production Hero</title>
      <link>https://unkletayo.dev/engineering/standalone/the-complete-nginx-mastery-series-from-zero-to-production-hero/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/the-complete-nginx-mastery-series-from-zero-to-production-hero/</guid>
      <description>Part 1 of the Nginx series. What Nginx is, how to set it up from scratch, and what the rest of the series covers.</description>
      <pubDate>Thu, 07 Aug 2025 07:00:00 GMT</pubDate>
      <category>webdev</category>
      <category>nginx</category>
      <category>beginners</category>
      <category>softwareengineering</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/the-complete-nginx-mastery-series-from-zero-to-production-hero.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u327rdk5y586nr1a676z.jpg" alt="Swiss Army Knife" />
<em>Image source: <a href="https://unsplash.com/photos/red-and-silver-multi-tool--J1cTtVpj8k">Unsplash</a> - Swiss Army Knife representing Nginx's versatility</em></p>
<p><em>Welcome to the most comprehensive Nginx tutorial series you'll ever need. Whether you're a complete beginner or looking to level up your server management skills, this series will take you from "What is Nginx?" to deploying production-ready configurations.</em></p>
<hr />
<h2>What You'll Learn in This Series</h2>
<p>By the end of this series, you'll be able to:</p>
<ul>
<li>Set up Nginx from scratch on any system</li>
<li>Configure high-performance web servers</li>
<li>Build robust reverse proxy setups</li>
<li>Implement advanced load balancing strategies</li>
<li>Secure your applications with SSL/TLS</li>
<li>Create API gateways for microservices</li>
<li>Optimize performance for high-traffic applications</li>
<li>Monitor and troubleshoot like a pro</li>
</ul>
<hr />
<h2>Who This Series Is For</h2>
<p><strong>Complete Beginners</strong>: Never touched a server configuration? No problem! We start from the absolute basics.</p>
<p><strong>Frontend Developers</strong>: Want to understand how your apps get served? This series bridges that gap.</p>
<p><strong>Backend Developers</strong>: Ready to deploy your APIs professionally? We've got you covered.</p>
<p><strong>DevOps Engineers</strong>: Looking to master one of the most important tools in your toolkit? Let's dive deep.</p>
<p><strong>System Administrators</strong>: Want to squeeze every bit of performance from your servers? We'll show you how.</p>
<h2>Part 1: Nginx Fundamentals - Your First Steps</h2>
<h2>What Exactly Is Nginx?</h2>
<p>Think of Nginx (pronounced "engine-x") as a super-efficient digital traffic controller. Just like a traffic controller at a busy intersection manages the flow of cars, Nginx manages the flow of web requests to ensure everything runs smoothly.</p>
<h3>The Simple Analogy</h3>
<p>Imagine you own a popular restaurant:</p>
<ul>
<li><strong>Without Nginx</strong>: Customers have to find their own seats, get their own water, and somehow figure out where the kitchen is. Chaos!
</li>
<li><strong>With Nginx</strong>: You have a professional host who greets customers, assigns tables efficiently, serves water quickly, and makes sure the kitchen gets orders in the right order. Much better!
</li>
</ul>
<p>That's essentially what Nginx does for your web applications.</p>
<h2>Why Nginx? The Numbers Don't Lie</h2>
<ul>
<li><strong>Used by 30%+ of all websites</strong> (including Netflix, Airbnb, WordPress.com)</li>
<li><strong>Can handle 10,000+ concurrent connections</strong> on modest hardware</li>
<li><strong>Uses minimal memory</strong> compared to alternatives</li>
<li><strong>Extremely stable</strong> - some servers run for years without restart</li>
</ul>
<h2>What Makes Nginx Special?</h2>
<h3>1. <strong>It's a Swiss Army Knife</strong> 🔧</h3>
<p>Nginx isn't just one tool - it's many:</p>
<pre><code class="language-mermaid">graph LR
    A[Nginx] --&gt; B[Web Server]
    A --&gt; C[Reverse Proxy]
    A --&gt; D[Load Balancer]
    A --&gt; E[SSL Terminator]
    A --&gt; F[Cache Server]
    A --&gt; G[API Gateway]
</code></pre>
<h3>2. <strong>Event-Driven Architecture</strong></h3>
<p>Traditional servers create a new process for each visitor (imagine hiring a new employee for each customer). Nginx uses an event-driven model (one efficient employee handling multiple customers). This is why it's so fast and memory-efficient.</p>
<h3>3. <strong>Battle-Tested Reliability</strong></h3>
<p>Netflix uses Nginx to serve millions of hours of video. If it's good enough for Netflix, it's probably good enough for your project!</p>
<h2>Installation: Getting Nginx on Your System</h2>
<h3>Ubuntu/Debian (Most Common)</h3>
<pre><code class="language-bash"># Update package list
sudo apt update

# Install Nginx
sudo apt install nginx

# Start Nginx
sudo systemctl start nginx

# Enable auto-start on boot
sudo systemctl enable nginx

# Check if it's running
sudo systemctl status nginx
</code></pre>
<h3>CentOS/RHEL/Fedora</h3>
<pre><code class="language-bash"># Install Nginx
sudo dnf install nginx  # or 'yum install nginx' on older systems

# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx
</code></pre>
<h3>macOS (for Development)</h3>
<pre><code class="language-bash"># Using Homebrew
brew install nginx

# Start Nginx
brew services start nginx
</code></pre>
<h3>Docker (Platform Independent)</h3>
<pre><code class="language-bash"># Quick test
docker run -d -p 8080:80 nginx

# Visit http://localhost:8080 to see it working!
</code></pre>
<h2>Your First Nginx Configuration</h2>
<p>Let's start with something simple but powerful. We'll create a configuration that serves a basic website.</p>
<h3>Understanding the Config Structure 🏗️</h3>
<p>Nginx configuration follows a hierarchy:</p>
<pre><code>Main Context (Global settings)
├── Events Context (Connection handling)
└── HTTP Context (Web-related settings)
    └── Server Context (Website-specific settings)
        └── Location Context (URL-specific settings)
</code></pre>
<h3>The Basic Configuration File</h3>
<p>Create or edit <code>/etc/nginx/nginx.conf</code>:</p>
<pre><code class="language-nginx"># Main context - global settings that affect everything
user nginx;
worker_processes auto;  # Use all available CPU cores
error_log /var/log/nginx/error.log;

# Events context - how Nginx handles connections
events {
    worker_connections 1024;  # Each worker can handle 1024 connections
}

# HTTP context - all web-related settings go here
http {
    # Basic settings
    include /etc/nginx/mime.types;  # Tells Nginx about file types
    default_type application/octet-stream;
    
    # Logging format
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent"';
    
    access_log /var/log/nginx/access.log main;
    
    # Performance settings
    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    
    # Include server configurations
    include /etc/nginx/conf.d/*.conf;
}
</code></pre>
<h3>Your First Website Configuration</h3>
<p>Create <code>/etc/nginx/conf.d/my-first-site.conf</code>:</p>
<pre><code class="language-nginx">server {
    # Listen on port 80 (standard HTTP port)
    listen 80;
    
    # Your domain name (use 'localhost' for testing)
    server_name localhost;
    
    # Where your website files are stored
    root /var/www/html;
    
    # Default file to serve
    index index.html index.htm;
    
    # How to handle different URLs
    location / {
        # Try to serve the exact file, then try as a directory,
        # finally return 404 if nothing found
        try_files $uri $uri/ =404;
    }
    
    # Custom error pages (optional but nice)
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
</code></pre>
<h3>Creating Your Website Content</h3>
<pre><code class="language-bash"># Create the website directory
sudo mkdir -p /var/www/html

# Create a simple index.html
sudo tee /var/www/html/index.html &gt; /dev/null &lt;&lt;EOF
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;My First Nginx Site&lt;/title&gt;
    &lt;style&gt;
        body { 
            font-family: Arial, sans-serif; 
            max-width: 800px; 
            margin: 50px auto; 
            padding: 20px; 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        .container { 
            background: rgba(255,255,255,0.1); 
            padding: 30px; 
            border-radius: 10px; 
            text-align: center;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="container"&gt;
        &lt;h1&gt;🎉 Congratulations!&lt;/h1&gt;
        &lt;p&gt;Your Nginx server is working perfectly!&lt;/p&gt;
        &lt;p&gt;You're now ready to serve the world.&lt;/p&gt;
        &lt;small&gt;Powered by Nginx&lt;/small&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
EOF

# Set proper permissions
sudo chown -R nginx:nginx /var/www/html
sudo chmod -R 755 /var/www/html
</code></pre>
<h3>Testing Your Configuration</h3>
<pre><code class="language-bash"># Always test before applying changes!
sudo nginx -t

# If the test passes, reload Nginx
sudo systemctl reload nginx

# Check if everything is working
curl http://localhost
</code></pre>
<p>Visit <code>http://localhost</code> in your browser - you should see your beautiful new website!</p>
<h2>Understanding What Just Happened</h2>
<p>Let's break down what occurs when someone visits your website:</p>
<ol>
<li><strong>Browser Request</strong>: User types <code>http://localhost</code> in browser</li>
<li><strong>DNS Resolution</strong>: Browser finds your server's IP address</li>
<li><strong>Connection</strong>: Browser connects to port 80 on your server</li>
<li><strong>Nginx Receives</strong>: Nginx accepts the connection</li>
<li><strong>Configuration Match</strong>: Nginx matches the request to your server block</li>
<li><strong>File Serving</strong>: Nginx finds and serves <code>/var/www/html/index.html</code></li>
<li><strong>Response</strong>: Browser receives and displays your webpage</li>
</ol>
<h2>Pro Tips for Beginners 💡</h2>
<h3>1. <strong>Always Test First</strong></h3>
<pre><code class="language-bash"># This command is your best friend
sudo nginx -t
</code></pre>
<p>Never reload Nginx without testing first. A syntax error can take down your website!</p>
<h3>2. <strong>Check the Logs</strong></h3>
<pre><code class="language-bash"># Error logs (when something goes wrong)
sudo tail -f /var/log/nginx/error.log

# Access logs (who's visiting your site)
sudo tail -f /var/log/nginx/access.log
</code></pre>
<h3>3. <strong>Reload vs Restart</strong></h3>
<pre><code class="language-bash"># Reload: applies new config without dropping connections
sudo systemctl reload nginx

# Restart: stops and starts again (brief downtime)
sudo systemctl restart nginx
</code></pre>
<h3>4. <strong>Keep Backups</strong></h3>
<pre><code class="language-bash"># Before making changes, backup your config
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
</code></pre>
<h2>Common Beginner Mistakes (And How to Avoid Them) ⚠️</h2>
<h3>Mistake 1: Forgetting Semicolons</h3>
<pre><code class="language-nginx"># Wrong 
server_name example.com

# Right 
server_name example.com;
</code></pre>
<h3>Mistake 2: Incorrect File Permissions</h3>
<pre><code class="language-bash"># Fix permission issues
sudo chown -R nginx:nginx /var/www/html
sudo chmod -R 755 /var/www/html
</code></pre>
<h3>Mistake 3: Not Testing Configuration</h3>
<pre><code class="language-bash"># Always do this before reloading
sudo nginx -t
</code></pre>
<h3>Mistake 4: Confusing Contexts</h3>
<pre><code class="language-nginx"># Wrong  - server directive in http context
http {
    server_name example.com;  # This doesn't belong here!
}

# Right  - server directive in server context
http {
    server {
        server_name example.com;  # Perfect!
    }
}
</code></pre>
<h2>Quick Troubleshooting Guide</h2>
<h3>Problem: "Permission Denied" Error</h3>
<pre><code class="language-bash"># Check SELinux (if on CentOS/RHEL)
sudo setsebool -P httpd_can_network_connect 1

# Check file permissions
sudo chown -R nginx:nginx /var/www/html
sudo chmod -R 755 /var/www/html
</code></pre>
<h3>Problem: "Address Already in Use"</h3>
<pre><code class="language-bash"># Check what's using port 80
sudo netstat -tulpn | grep :80

# If Apache is running, stop it
sudo systemctl stop apache2  # or httpd
</code></pre>
<h3>Problem: Changes Not Appearing</h3>
<pre><code class="language-bash"># Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

# Clear browser cache or try incognito mode
</code></pre>
<h2>What's Next?</h2>
<p>Congratulations! You've successfully:</p>
<ul>
<li>Installed Nginx</li>
<li>Created your first configuration</li>
<li>Served your first website</li>
<li>Learned basic troubleshooting</li>
</ul>
<p>In <strong>Part 2: Web Server Mastery</strong>, we'll dive into:</p>
<ul>
<li>Serving multiple websites on one server</li>
<li>Handling different file types efficiently</li>
<li>Setting up caching for lightning-fast loading</li>
<li>Creating beautiful error pages</li>
<li>Implementing redirects and URL rewriting</li>
</ul>
<h2>Practice Exercises 📝</h2>
<p>Before moving to Part 2, try these exercises:</p>
<h3>Exercise 1: Multiple Pages</h3>
<p>Create additional HTML pages and link them together:</p>
<ul>
<li>About page (<code>/var/www/html/about.html</code>)</li>
<li>Contact page (<code>/var/www/html/contact.html</code>)</li>
</ul>
<h3>Exercise 2: Custom Error Page</h3>
<p>Create a custom 404.html page with your own design.</p>
<h3>Exercise 3: Different Server Name</h3>
<p>Modify your configuration to respond to a different domain name (you can use your computer's IP address).</p>
<h3>Exercise 4: Log Analysis</h3>
<p>Visit your website a few times, then examine the access logs to see what Nginx recorded.</p>
<hr />
<h2>Resources and Further Reading</h2>
<ul>
<li><a href="https://nginx.org/en/docs/">Official Nginx Documentation</a></li>
<li><a href="https://www.nginx.com/resources/wiki/start/">Nginx Configuration Examples</a></li>
<li><a href="https://nginx.org/en/docs/beginners_guide.html">Understanding Nginx Context</a></li>
</ul>
<hr />
<p><em>Ready for Part 2? We'll transform your basic website into a high-performance web server capable of handling multiple sites, optimized caching, and professional-grade configurations. See you there! 🎯</em></p>
<hr />
<p><strong>Questions or stuck somewhere?</strong> Drop a comment below! The Nginx community is incredibly helpful, and there's no such thing as a stupid question when you're learning.</p>
<p><strong>Share this series</strong> with fellow developers who want to level up their server skills!</p>
<hr />
<p><em>This is Part 1 of our comprehensive Nginx Mastery Series. Each part builds upon the previous, so make sure you're comfortable with these concepts before moving forward.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>How I Fixed the Mystery of Corrupted File Downloads from AWS S3</title>
      <link>https://unkletayo.dev/engineering/standalone/how-i-fixed-the-mystery-of-corrupted-file-downloads-from-aws-s3/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/how-i-fixed-the-mystery-of-corrupted-file-downloads-from-aws-s3/</guid>
      <description>Users said downloaded documents were corrupted, yet the files in S3 were fine. How I tracked down the cause, and the fix.</description>
      <pubDate>Mon, 02 Jun 2025 07:00:00 GMT</pubDate>
      <category>filesignatures</category>
      <category>filehandling</category>
      <category>typescript</category>
      <category>programming</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/how-i-fixed-the-mystery-of-corrupted-file-downloads-from-aws-s3.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<h2>How I Fixed the Mystery of Corrupted File Downloads from AWS S3</h2>
<p><em>When your users report "corrupted files" but everything looks fine on your end</em></p>
<p>Last month, I ran into one of those bugs that makes you question everything you think you know about web development. Users were complaining that downloaded files from our document management system were "corrupted" and wouldn't open. The twist? The files were perfectly fine in our AWS S3 bucket.</p>
<h2>The Problem That Started It All</h2>
<p>Picture this: You're building an application where users upload important kyc documents—driver's licenses, bank statements, tax documents. Everything seems to work perfectly during testing. Files upload fine, they're stored securely in S3, and when you download them through your admin panel, they open without issues.</p>
<p>Then the support tickets start rolling in:</p>
<blockquote>
<p>"I can't open the driver's license I just downloaded"</p>
<p>"The bank statement file seems corrupted"</p>
<p>"Why is my tax document showing as a 'Blob' file?"</p>
</blockquote>
<p>My first instinct was to blame the users (classic developer move, I know). Maybe they're using old browsers? Maybe their antivirus is interfering? But when I started investigating, I realized the problem was much closer to home.</p>
<h2>The Investigation</h2>
<p>I pulled up our file download function—a simple, straightforward piece of code that I'd written dozens of times before:</p>
<pre><code class="language-typescript">export async function downloadBlobFile(
  blobUrl: string,
  fileName: string
): Promise&lt;void&gt; {
  try {
    const response = await fetch(blobUrl);
    const blob = await response.blob();
    const url = window.URL.createObjectURL(blob);

    const a = document.createElement('a');
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    // cleanup...
  } catch (err) {
    // error handling...
  }
}
</code></pre>
<p>Looks innocent enough, right? The issue became clear when I started examining what was actually happening:</p>
<ol>
<li><strong>Our S3 objects had no file extensions</strong> - Files were stored as <code>doc-12381-0530103115-bank-statement</code> instead of <code>bank-statement.pdf</code></li>
<li><strong>S3 wasn't setting Content-Type headers</strong> - Everything was coming back as <code>application/octet-stream</code> or no content type at all</li>
<li><strong>Files were being saved as <code>.bin</code> files</strong> - Because the browser had no idea what they actually were</li>
</ol>
<p>The "corruption" wasn't corruption at all—it was a case of mistaken identity. A PDF being saved as a <code>.bin</code> file naturally won't open in a PDF viewer.</p>
<h2>My First (Failed) Approach</h2>
<p>My initial thought was simple: "I'll just add the file extension to the filename when saving!"</p>
<p>But how do you know if a file should be <code>.pdf</code>, <code>.jpg</code>, or <code>.docx</code> when the server isn't telling you?</p>
<p>I tried extracting it from the URL, but our presigned URLs looked like this:</p>
<pre><code>https://uploads.s3.amazonaws.com/bank-statement-12381-0530103115?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=...
</code></pre>
<p>No extension in sight.</p>
<p>I considered asking users to specify the file type when uploading, but that felt like admitting defeat. There had to be a better way.</p>
<h2>The Eureka Moment</h2>
<p>Then I remembered something from blogs and few articles I've seen before now: file signatures (or "magic numbers"). Every file type has a unique byte sequence at the beginning that identifies what it is, regardless of its name or extension.</p>
<ul>
<li>PDFs start with <code>%PDF</code> (hex: <code>25 50 44 46</code>)</li>
<li>JPEGs start with <code>FF D8 FF</code></li>
<li>PNGs start with <code>89 50 4E 47</code></li>
</ul>
<p>I could read the first few bytes of each downloaded file, identify its actual type, and assign the correct extension!</p>
<h2>Building the Solution</h2>
<p>Here's the approach I took:</p>
<ol>
<li><strong>Download the file as a blob</strong> (necessary for CORS anyway)</li>
<li><strong>Check if the server provided a useful Content-Type</strong></li>
<li><strong>If not, read the first 20 bytes and analyze the file signature</strong></li>
<li><strong>Map the signature to the correct MIME type and extension</strong></li>
<li><strong>Download with the proper filename</strong></li>
</ol>
<p>The implementation handles the most common file types we encounter:</p>
<pre><code class="language-typescript">async function detectFileType(
  blob: Blob
): Promise&lt;{ mimeType: string; extension: string } | null&gt; {
  const arrayBuffer = await blob.slice(0, 20).arrayBuffer();
  const bytes = new Uint8Array(arrayBuffer);

  // PDF signature
  if (
    bytes[0] === 0x25 &amp;&amp;
    bytes[1] === 0x50 &amp;&amp;
    bytes[2] === 0x44 &amp;&amp;
    bytes[3] === 0x46
  ) {
    return { mimeType: 'application/pdf', extension: '.pdf' };
  }

  // JPEG signature
  if (bytes[0] === 0xff &amp;&amp; bytes[1] === 0xd8 &amp;&amp; bytes[2] === 0xff) {
    return { mimeType: 'image/jpeg', extension: '.jpg' };
  }

  // ... more file types
}
</code></pre>
<h2>The Results</h2>
<p>The transformation was immediate and dramatic:</p>
<ul>
<li><strong>Zero corruption complaints</strong> since deploying the fix</li>
<li><strong>User satisfaction improved</strong> - files now open in the correct applications automatically</li>
<li><strong>Support tickets dropped</strong> by about 60% for file-related issues</li>
<li><strong>Developer confidence restored</strong> - no more mysterious "it works on my machine" scenarios</li>
</ul>
<p>Users went from downloading <code>bank-statement.blob</code> files that wouldn't open, to getting <code>bank-statement.pdf</code> files that opened immediately in their PDF viewers.</p>
<h2>Current Limitations (And Future Plans)</h2>
<p>While this solution works great for our use case, it's not perfect:</p>
<p><strong>What it handles well:</strong></p>
<ul>
<li>Common image formats (JPEG, PNG, GIF, WebP)</li>
<li>PDF documents</li>
<li>Basic Office documents (as ZIP files)</li>
<li>Text-based formats (JSON, XML, HTML)</li>
</ul>
<p><strong>What it doesn't handle yet:</strong></p>
<ul>
<li>Audio/video files (MP3, MP4, etc.)</li>
<li>More exotic document formats</li>
<li>Encrypted or password-protected files</li>
<li>Very large files (though it only reads the first 20 bytes, so this shouldn't be an issue)</li>
</ul>
<p><strong>Performance considerations:</strong></p>
<ul>
<li>Adds a small overhead to read file signatures</li>
<li>Uses more memory than direct downloads</li>
<li>Still requires the blob method for CORS compatibility</li>
</ul>
<h2>Lessons Learned</h2>
<p>This experience taught me a few valuable lessons:</p>
<ol>
<li><strong>User complaints about "corruption" aren't always about actual file corruption</strong> - sometimes it's about incorrect file associations</li>
<li><strong>Cloud storage services don't always preserve file metadata</strong> - you can't assume Content-Type headers will be present or accurate</li>
<li><strong>File extensions matter more than we think</strong> - they're not just cosmetic; they determine how operating systems handle files</li>
<li><strong>Sometimes the old-school approaches (like file signatures) are still the best solutions</strong> to modern problems</li>
</ol>
<h2>Want to Use This Solution?</h2>
<p>I've open-sourced this file download utility because I suspect other developers are running into similar issues. The complete implementation handles edge cases, includes extensive logging for debugging, and supports the most common file types you'll encounter in web applications.</p>
<p>You can find it in <a href="https://gist.github.com/unkletayo/f424759db36f47edb92a8d9b1f6d9b46/e150661c24040b751dfeecfc9f331a9dbca9a797">this GitHub Gist</a> with full documentation and usage examples.</p>
<p>Have you run into similar file download mysteries in your applications? I'd love to hear about your experiences and solutions in the comments below.</p>
<hr />
<p><em>Building reliable file handling in web applications is trickier than it looks. Sometimes the solution isn't more sophisticated cloud configuration—it's going back to the fundamentals of how computers identify files.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Setting Up PostgreSQL on macOS: A Fresh Start Guide</title>
      <link>https://unkletayo.dev/engineering/standalone/setting-up-postgresql-on-macos-a-fresh-start-guide/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/setting-up-postgresql-on-macos-a-fresh-start-guide/</guid>
      <description>Install PostgreSQL on a Mac with Homebrew, start the service, create a database and a user, and connect your app to it.</description>
      <pubDate>Mon, 02 Jun 2025 07:00:00 GMT</pubDate>
      <category>postgres</category>
      <category>database</category>
      <category>beginners</category>
      <category>softwaredevelopment</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/setting-up-postgresql-on-macos-a-fresh-start-guide.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3miphq6knhufrm2myzxs.jpg" alt="black-and-white-photo-of-elephants-on-a-field" /></p>
<p>If you've ever faced the frustration of setting up a database from scratch, you're not alone. I remember the first time I installed PostgreSQL on my Mac, a mix of excitement and confusion. "Where do I even start? How do I create users? And how do I connect my app to it?"</p>
<p>If that sounds familiar, don't worry. In this guide, I'll walk you through how to start fresh with PostgreSQL on macOS using Homebrew, including how to launch the service, create a new database and user, and connect everything to your app — step by step, in a way that just makes sense.</p>
<p><em>Note: This guide focuses on macOS, but many of the PostgreSQL commands will work similarly on Linux. Windows users might want to look into PostgreSQL's official installer or WSL.</em></p>
<h2>Why PostgreSQL?</h2>
<p>PostgreSQL is a powerful, open-source relational database used by startups and giants alike. It's reliable, scalable, and has features that can grow with your project, making it a great choice whether you're just starting out or levelling up your skills. Plus, it's completely free and has excellent community support.</p>
<h2>Step 1: Make Sure PostgreSQL Is Installed</h2>
<p>Since you're on macOS, Homebrew is your friend for installing and managing software like PostgreSQL. If you don't have Homebrew installed yet, head to <a href="https://brew.sh">brew.sh</a> and follow the installation instructions.</p>
<p>Open your Terminal and check if PostgreSQL is installed and what version you have:</p>
<pre><code class="language-bash">psql --version
</code></pre>
<p>If it shows something like <code>psql (PostgreSQL) 17.x</code>, you're good to go!</p>
<p>If it says "command not found," you need to install PostgreSQL:</p>
<pre><code class="language-bash">brew install postgresql@17
</code></pre>
<p>After installation, you might need to add PostgreSQL to your PATH. Homebrew will usually show you the command to run, but it typically looks like this:</p>
<pre><code class="language-bash">echo 'export PATH="/opt/homebrew/bin:$PATH"' &gt;&gt; ~/.zshrc
source ~/.zshrc
</code></pre>
<h2>Step 2: Start the PostgreSQL Service</h2>
<p>Now that PostgreSQL is installed, it needs to be running so your app can talk to it. Think of this like starting your car before you can drive somewhere.</p>
<p>Use Homebrew to start PostgreSQL:</p>
<pre><code class="language-bash">brew services start postgresql@17
</code></pre>
<p>This command tells macOS to launch PostgreSQL in the background and keep it running, even after you restart your computer.</p>
<p>To check that it's running:</p>
<pre><code class="language-bash">brew services list | grep postgresql
</code></pre>
<p>You should see something like <code>postgresql@17 started</code>.</p>
<p>If you ever need to manage the service later, these commands will help:</p>
<pre><code class="language-bash"># Restart PostgreSQL
brew services restart postgresql@17

# Stop PostgreSQL 
brew services stop postgresql@17

# Check status
brew services info postgresql@17
</code></pre>
<h2>Step 3: Access PostgreSQL for the First Time</h2>
<p>PostgreSQL creates a default superuser that matches your macOS username when it's first installed. Let's connect to the default <code>postgres</code> database:</p>
<pre><code class="language-bash">psql postgres
</code></pre>
<p>If successful, you'll see a prompt like this:</p>
<pre><code>postgres=#
</code></pre>
<p>Congrats — you're inside the PostgreSQL command line interface! The <code>#</code> symbol indicates you're connected as a superuser.</p>
<p><strong>Troubleshooting:</strong> If you get a connection error, make sure PostgreSQL is running (Step 2) and try specifying your username explicitly:</p>
<pre><code class="language-bash">psql -U $(whoami) postgres
</code></pre>
<h2>Step 4: Create a New User and Database</h2>
<p>For your projects, it's a security best practice to create a dedicated user and database rather than using the default superuser for everything. This limits potential damage if something goes wrong.</p>
<p>At the <code>postgres=#</code> prompt, run the following commands. Feel free to replace <code>myapp_user</code> and <code>myapp_db</code> with your own names, and choose a secure password:</p>
<pre><code class="language-sql">-- Create a new user with a password
CREATE USER myapp_user WITH PASSWORD 'your_secure_password_here';

-- Create a new database owned by this user
CREATE DATABASE myapp_db OWNER myapp_user;

-- Grant all privileges on the database to the new user
GRANT ALL PRIVILEGES ON DATABASE myapp_db TO myapp_user;

-- Grant the user permission to create databases (useful for testing)
ALTER USER myapp_user CREATEDB;
</code></pre>
<p><strong>Security tip:</strong> Use a strong password with a mix of letters, numbers, and symbols. You can generate one using:</p>
<pre><code class="language-bash">openssl rand -base64 32
</code></pre>
<p>Once done, exit the PostgreSQL prompt:</p>
<pre><code class="language-sql">\q
</code></pre>
<h2>Step 5: Test Your New User Connection</h2>
<p>Let's make sure everything works by connecting to your new database as your new user:</p>
<pre><code class="language-bash">psql -U myapp_user -d myapp_db -h localhost
</code></pre>
<p>You'll be prompted for the password you set. If you see a prompt like <code>myapp_db=&gt;</code>, you're successfully connected! The <code>&gt;</code> symbol (instead of <code>#</code>) shows you're connected as a regular user, not a superuser.</p>
<p>Try a simple command to make sure everything works:</p>
<pre><code class="language-sql">SELECT version();
</code></pre>
<p>This should show your PostgreSQL version information.</p>
<p>Exit when you're done:</p>
<pre><code class="language-sql">\q
</code></pre>
<h2>Step 6: Set Up Your Application Configuration</h2>
<p>Your app needs connection details to talk to PostgreSQL. The exact setup depends on your framework, but here are the most common patterns:</p>
<h3>Environment Variables (.env file)</h3>
<p>Create or update your <code>.env</code> file:</p>
<pre><code class="language-bash"># Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp_user
DB_PASSWORD=your_secure_password_here
DB_NAME=myapp_db

# Optional: Full connection string format
DATABASE_URL=postgresql://myapp_user:your_secure_password_here@localhost:5432/myapp_db
</code></pre>
<h3>TypeORM Configuration</h3>
<p>If you're using TypeORM with Node.js, your <code>datasource.ts</code> might look like this:</p>
<pre><code class="language-typescript">import { DataSource } from "typeorm";
import { config } from "dotenv";

config(); // Load environment variables

export const AppDataSource = new DataSource({
  type: "postgres",
  host: process.env.DB_HOST || "localhost",
  port: parseInt(process.env.DB_PORT || "5432"),
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  synchronize: process.env.NODE_ENV === "development", // Only in development!
  logging: process.env.NODE_ENV === "development",
  entities: ["src/entities/**/*.ts"],
  migrations: ["src/migrations/**/*.ts"],
});
</code></pre>
<h3>Django Configuration</h3>
<p>For Django users, update your <code>settings.py</code>:</p>
<pre><code class="language-python">DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': os.environ.get('DB_HOST', 'localhost'),
        'PORT': os.environ.get('DB_PORT', '5432'),
    }
}
</code></pre>
<h2>Step 7: Essential PostgreSQL Commands to Remember</h2>
<p>Now that you're set up, here are some handy commands you'll use regularly:</p>
<pre><code class="language-bash"># Connect to your database
psql -U myapp_user -d myapp_db -h localhost

# Connect using a connection string
psql "postgresql://myapp_user:password@localhost:5432/myapp_db"
</code></pre>
<p>Once connected, these SQL commands are useful:</p>
<pre><code class="language-sql">-- List all databases
\l

-- List all tables in current database
\dt

-- Describe a table structure
\d table_name

-- List all users
\du

-- Switch to a different database
\c database_name

-- Show current connection info
\conninfo

-- Get help
\h

-- Quit
\q
</code></pre>
<h2>Troubleshooting Common Issues</h2>
<p><strong>"psql: command not found"</strong>: PostgreSQL isn't in your PATH. Try the PATH export command from Step 1.</p>
<p><strong>"connection refused"</strong>: PostgreSQL service isn't running. Check with <code>brew services list | grep postgresql</code>.</p>
<p><strong>"password authentication failed"</strong>: Double-check your username and password. Remember, PostgreSQL is case-sensitive.</p>
<p><strong>"database does not exist"</strong>: Make sure you created the database in Step 4, or check the database name for typos.</p>
<h2>Next Steps</h2>
<p>With PostgreSQL running, you're ready to start building! Consider learning about:</p>
<ul>
<li><strong>Database migrations</strong>: How to version-control your database schema changes</li>
<li><strong>Connection pooling</strong>: For better performance in production apps</li>
<li><strong>Backup strategies</strong>: Using <code>pg_dump</code> to backup your data</li>
<li><strong>Performance monitoring</strong>: Understanding EXPLAIN and query optimization</li>
<li><strong>Extensions</strong>: PostgreSQL has powerful extensions like PostGIS for geographic data</li>
</ul>
<h2>Wrapping Up</h2>
<p>Starting fresh with PostgreSQL might feel daunting at first, but once you've set up your database and user correctly, you unlock a reliable backend foundation for any app you build.</p>
<p>You've learned how to:</p>
<ul>
<li>Verify and install PostgreSQL 17 with Homebrew</li>
<li>Start and manage the PostgreSQL service</li>
<li>Create a new database user and database with proper permissions</li>
<li>Connect to your database as that user</li>
<li>Configure your app to connect to PostgreSQL</li>
<li>Use essential PostgreSQL commands for da</li>
</ul>
<p>The best part? This setup will serve you well whether you're building a simple prototype or preparing for production. PostgreSQL scales with your ambitions.</p>
<p>Happy coding, and welcome to the world of robust database management!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Understanding Linux File and Folder Permissions</title>
      <link>https://unkletayo.dev/engineering/standalone/understanding-linux-file-and-folder-permissions/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/understanding-linux-file-and-folder-permissions/</guid>
      <description>How to read a permission string such as drwxr-xr-x, what each part means for the owner, the group and everyone else, and how to set it.</description>
      <pubDate>Thu, 26 Oct 2023 07:00:00 GMT</pubDate>
      <category>linux</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>productivity</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/understanding-linux-file-and-folder-permissions.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p><strong>Prerequisites:</strong></p>
<p>Before diving into the world of Linux file and folder permissions, you'll need basic knowledge of using the command line interface (CLI) and access to a Linux system. If you're new to these concepts, a brief introduction to CLI and basic Linux commands will be helpful.</p>
<h2>Introduction</h2>
<p>Linux file and folder permissions can be daunting, but they are crucial for securing your data and system. You've probably seen permission strings like <code>drwxr-xr-x</code> and wondered what they mean. In this article, we'll demystify these permissions, explain how they work, and how you can generate them.</p>
<h2>Understanding the Permission String</h2>
<p>The Linux file and folder permission string <code>drwxr-xr-x</code> is a way of representing the file permissions of a directory or file in a concise and easy-to-read format. It is made up of a series of characters, each of which represents a different permission. The characters are grouped into three sets of three, each of which represents the permissions for a different group of users:</p>
<ul>
<li>The first set of three characters represents the permissions for the owner of the file or directory.
</li>
<li>The second set of three characters represents the permissions for the group of users that the file or directory belongs to.
</li>
<li>The third set of three characters represents the permissions for all other users.
</li>
</ul>
<p>The specific permissions that are granted or denied are represented by the following characters:</p>
<ul>
<li><code>r</code> - Read permission</li>
<li><code>w</code> - Write permission</li>
<li><code>x</code> - Execute permission</li>
</ul>
<p>When any of the character is represented by an hyphen <code>(-)</code> this indicates that the permission is denied.</p>
<h2>How to generate the Linux file and folder permission string</h2>
<p>The file or directory permissions can be generated  using the <code>ls -l</code> command.</p>
<p>For example, if you run the following command:</p>
<pre><code class="language-bash">ls -l /home/user
</code></pre>
<p>The output might be something like this:</p>
<pre><code class="language-bash">drwxr-xr-x 2 user user 4096 Oct 26 16:56 /home/user
</code></pre>
<h2>Understanding the Permission String</h2>
<p>Let's break down the permission string <code>drwxr-xr-x</code> into its individual components and give meaning to them:</p>
<ol>
<li><strong>d</strong>: This stands for "directory." It indicates that the object (file or folder) is, in fact, a directory. If it were a regular file, you would see a "-" here instead.
</li>
<li><strong>rwx</strong>: The next three characters (r, w, and x) represent the permissions for the owner of the file or directory.
<code>r (Read)</code>: The owner can view the contents of the file or list the directory.
<code>w (Write)</code>: The owner can modify the file or add/remove files within the directory.
<code>x (Execute)</code>: The owner can enter the directory (for folders) or run the file (for executable files).
</li>
<li><strong>r-x</strong>: The next three characters represent the permissions for the group.
<code>r (Read)</code>: Members of the group can view the contents of the file or list the directory.
<code>- (Write)</code>: Members of the group cannot modify the file or add/remove files within the directory.
<code>x (Execute)</code>: Members of the group can enter the directory or run the file.
</li>
<li><strong>r-x</strong>: The last three characters are the permissions for others (everyone else who is not the owner or in the group). The same rules apply:
</li>
</ol>
<p><code>r (Read)</code>: Others can view the contents of the file or list the directory.
<code>- (Write)</code>: Others cannot modify the file or add/remove files within the directory.
<code>x (Execute)</code>: Others can enter the directory or run the file.</p>
<h2>Setting Permissions for file or folder</h2>
<p>You can set permissions using the <code>chmod</code> command in Linux. Here's how to generate a permission string:</p>
<ol>
<li><strong>Owner Permissions</strong>: Use <code>chmod</code> followed by the owner's permission characters. For example, to give the owner read, write, and execute permissions, use:</li>
</ol>
<pre><code class="language-bash">chmod u=rwx file_or_directory
</code></pre>
<ol>
<li><strong>Group Permissions:</strong> To set group permissions, use:</li>
</ol>
<pre><code class="language-bash">chmod g=rx file_or_directory
</code></pre>
<ol>
<li><strong>Others Permissions</strong>: To set permissions for others, use:</li>
</ol>
<pre><code class="language-bash">chmod o=x file_or_directory
</code></pre>
<p><strong>Usage</strong></p>
<p>Let's say you want to set the permission string <code>drwxr-xr-x</code> for a directory called "my_folder." You would use the following commands:</p>
<pre><code class="language-bash">chmod u=rwx my_folder
chmod g=rx my_folder
chmod o=rx my_folder
</code></pre>
<p>After executing these commands, you'll have the desired permission string.</p>
<p>Whether you're new to Linux or an experienced user, there's always something new to explore. If you've gained new insights, I'm delighted, and if you're an experienced Linux user, thank you for reading this far.</p>
<p>Stay tuned for more fun and engaging content on Linux and other tech topics! Happy learning.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to stop individual and all Docker processes</title>
      <link>https://unkletayo.dev/engineering/standalone/how-to-stop-individual-and-all-docker-processes/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/how-to-stop-individual-and-all-docker-processes/</guid>
      <description>The docker stop commands I use almost every day: stop one container by ID or name, or stop every running container at once.</description>
      <pubDate>Mon, 23 Oct 2023 07:00:00 GMT</pubDate>
      <category>docker</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>programming</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/how-to-stop-individual-and-all-docker-processes.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<h2>Why This Article</h2>
<p>This blog post was born out of the need to share a code snippet that I use almost every day at work. I stop and start Docker processes frequently, and I wanted to create a resource that would help others do the same.</p>
<p>I hope you find this blog post helpful!</p>
<h2>Introduction</h2>
<p>Docker is a popular containerization platform that allows you to run applications in isolated environments. This can be useful for improving performance, security, and portability. However, it can also be confusing to manage multiple Docker processes.</p>
<p>This blog post will explain how to stop individual and all Docker processes using the Docker CLI.</p>
<h2>Stopping an individual Docker process</h2>
<p>To stop an individual Docker process, you can use the docker stop command. This command takes the container ID or name as an argument. For example, to stop the container with the ID a0c59618bf9e, you would run the following command:</p>
<pre><code class="language-bash">docker stop a0c59618bf9e
</code></pre>
<p>You can also stop multiple Docker processes at once by passing multiple container IDs or names to the docker stop command. For example, to stop the containers with the IDs a0c59618bf9e and b1d2f3g4h5i6, you would run the following command:</p>
<pre><code class="language-bash">docker stop a0c59618bf9e b1d2f3g4h5i6
</code></pre>
<p><strong>Stopping all Docker processes</strong>
To stop all Docker processes, you can use the docker stop $(docker ps -a -q) command. This command will pipe the output of the docker ps -a -q command, which lists all container IDs, to the docker stop command.</p>
<p>For example, to stop all Docker processes, you would run the following command:</p>
<pre><code class="language-bash">docker stop $(docker ps -a -q)
</code></pre>
<p>Note: This command will stop all Docker processes, including those that are running in detached mode.</p>
<p>Explanation of the commands
The following is a brief explanation of the commands used in this blog post:</p>
<p><code>docker stop</code>: This command stops a Docker container.</p>
<p><code>docker ps</code>: This command lists Docker containers.
<code>-a</code>: This option tells the docker ps command to list all containers, even those that are stopped.
<code>-q</code>: This option tells the docker ps command to only list the container IDs.
`$(command)``: This is a shell syntax that allows you to execute the output of a command.</p>
<h3>Conclusion</h3>
<p>Stopping Docker processes is a simple task, but it is important to understand the different commands that you can use. This blog post has explained how to stop individual and all Docker processes using the Docker CLI.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Learning JavaScript the Easy Way: Unlocking the Power of Scope with Closures</title>
      <link>https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-unlocking-the-power-of-scope-with-closures/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-unlocking-the-power-of-scope-with-closures/</guid>
      <description>A closure is a function that keeps access to its parent&apos;s scope after the parent has finished. A short explanation with examples.</description>
      <pubDate>Wed, 06 Sep 2023 07:00:00 GMT</pubDate>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/learning-javascript-the-easy-way-unlocking-the-power-of-scope-with-closures.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>JavaScript's concept of closure is often misunderstood and confused with lexical scope. To truly grasp the concept of closures, it's essential to have a <a href="https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-scopes-and-hoisting/">good understanding of scopes</a>.</p>
<h2>What is a Closure?</h2>
<p>A closure is a fundamental JavaScript concept where a function retains access to its parent scope, even after the parent function has finished executing. Closures are defined when a function is declared, not when it is executed.</p>
<p>Imagine a child function nested within a parent function. A closure defines the boundaries within which the child function operates. This means the child function can access not only its own variables but also those defined in its parent function and even global scope variables, if any.</p>
<p>Now what does it mean to have access to the scope of its parent even after its parent function has called. Let look at simple example</p>
<pre><code class="language-JS">let x = 1;

function parentFunction() {
  let count = 2;

  console.log(x);
  console.log(count);

  function childFunction() {
    console.log(x += 1);
    console.log(count += 2);
  }

  return childFunction;
}

const result = parentFunction()
</code></pre>
<p>In this example, if we check the console, we'll see <code>1</code> and <code>2</code> printed, respectively, but we won't see any result from the child function. This is because the child function has not been executed yet; we've only executed the parent function. However, we've also returned the child function, which can now be invoked using result.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4t0aufo4tmdylhge411d.png" alt="Code Snippet showing Closure Execution" /></p>
<p>To demonstrate this, let's log the result to the console, revealing that it's an anonymous function, which is essentially the child function:</p>
<pre><code class="language-JS">console.log(result);
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsy95x4akohz570jqcdz.png" alt="Code Snippet for result" /></p>
<p>Now, when we invoke the child function using result(), observe the output:</p>
<pre><code class="language-JS">result()
result()
</code></pre>
<p>As we can the child function still it access to count variable even though the parent function has already been called and returned and also the x which is a global variable.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u43zcejx7gocp9ntnxrf.png" alt="Picture showing code result on the console" /></p>
<p>As you can see, the child function still has access to the count variable, even though the parent function has already been executed and returned. It also has access to the global variable <code>x</code>. However, trying to access <code>count</code> from the global scope would result in a reference error since it's not accessible at that level.</p>
<p><strong>An  Analogy</strong></p>
<p>To simplify our understanding of closures, let's adopt <a href="https://fs.blog/feynman-learning-technique/">Feyman's Technique</a>:</p>
<p>Imagine you have a cookie jar at home with delicious cookies that you love. However, you don't want anyone in your family to eat them when you're not looking. To keep your cookies safe, you place them inside a special box with a lock. This box is analogous to a closure in JavaScript, safeguarding your cookies from others.</p>
<p>When you want to enjoy a cookie, you take out the key, unlock the box, and retrieve your cookie. You can enjoy as many cookies as you like. Once you're done, you put the remaining cookies back inside the box, lock it again, and hide the key.</p>
<p>This way, your cookies remain safe and private, and you can savor them whenever you want without worrying about someone else taking them.</p>
<p>In JavaScript, a closure is like that special box. It protects specific data or variables, keeping them hidden from the rest of the code. When you need to use these variables, you can open the closure, perform your tasks, and then close it to maintain security. Closures empower programmers to manage and safeguard their data, just like your cookie box protects your cookies from family members!</p>
<p><strong>Conclusion</strong></p>
<p>In conclusion, closures in JavaScript are a powerful concept that allows functions to retain access to their parent scope, even after the parent function has completed its execution. They are not only a fundamental aspect of JavaScript but also a tool that can be harnessed to create private variables and manage data effectively.</p>
<p>As we journey through the fascinating world of JavaScript, I urge you to stay curious and explore more intriguing concepts.
So, stay tuned, and let's learn JavaScript the easy way together!</p>
<p>Further Reading</p>
<p>Excellent article by MDN on closures:<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Learning JavaScript the easy way: Scopes and Hoisting</title>
      <link>https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-scopes-and-hoisting/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-scopes-and-hoisting/</guid>
      <description>What scope means in JavaScript, how global and local scope differ, and what hoisting does to your variables, from beginner level up.</description>
      <pubDate>Sat, 26 Aug 2023 07:00:00 GMT</pubDate>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/learning-javascript-the-easy-way-scopes-and-hoisting.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Scopes and hoisting are fundamental concepts in JavaScript that play a crucial role in how variables are accessed and executed within your code. Understanding these concepts is essential for writing efficient and error-free JavaScript programs.
This guide will cover scopes and hoisting, starting from the beginner level and progressing to more advanced concepts.</p>
<h2>Scopes</h2>
<p>Scope in JavaScript refers to the context in which variables are declared and accessed.
This simply means that a scope in JavaScript defines the accessibility and visibility of variables.</p>
<p>There are two main types of scopes: global scope and local scope.</p>
<h3>Global Scope:</h3>
<p>Variables declared outside of any function are in the global scope and can be accessed from anywhere in the code. The variables are declared using <code>const</code> and <code>let</code> and are accessible globally, just like with <code>var</code>.</p>
<pre><code class="language-JS">const globalVariable = 30;

function printGlobal() {
    console.log(globalVariable);
}

printGlobal(); // prints: 30

</code></pre>
<h2>Local Scope:</h2>
<p>Variables declared inside a function are in the local scope and can only be accessed within that function. It defines how variable names are resolved in nested functions.</p>
<pre><code class="language-JS">function printLocal() {
    var localVar = 20;
    console.log(localVar); 
}

printLocal(); // prints: 20
console.log(localVar); // Throws an error - localVar is not defined
</code></pre>
<h3>Other forms of scopes</h3>
<ul>
<li><strong>Block Scopes with let and const:</strong></li>
</ul>
<p>Introduced in ES6, let and const allow you to declare variables with block scope.</p>
<pre><code class="language-JS">if (true) {
    let blockVar = 40;
    const blockConst = 50;
}
// console.log(blockVar); // Error: blockVar is not defined
// console.log(blockConst); // Error: blockConst is not defined
</code></pre>
<ul>
<li><strong>Function Scopes and Closures:</strong></li>
</ul>
<p>Functions create their own scope, and inner functions can access variables from their containing (outer) function's scope. This behavior is known as a closure. We will take a deeper dive into closures in upcoming chapters</p>
<pre><code>function outer() {
    var outerVar = 60;
    function inner() {
        console.log(outerVar);
    }
    return inner;
}

var closure = outer();
closure(); // Outputs: 60
</code></pre>
<ul>
<li><strong>Lexical Scoping:</strong></li>
</ul>
<p>Lexical scoping means that a function's scope is determined by its location during author-time, and not runtime.</p>
<ul>
<li><strong>IIFE (Immediately Invoked Function Expression):</strong></li>
</ul>
<p>An IIFE is a function that is immediately executed after being defined. It helps create a private scope for your code.</p>
<pre><code class="language-JS">(function() {
    var privateVar = "I'm private!";
    console.log(privateVar);
})();
console.log(privateVar); // Error: privateVar is not defined
</code></pre>
<ul>
<li><strong>Temporal Dead Zone (TDZ):</strong></li>
</ul>
<p>The TDZ is the phase in which variables are declared but not yet initialized. Accessing variables in this state results in a <code>ReferenceError</code>.</p>
<pre><code class="language-JS">console.log(tempVar); // Error: Cannot access 'tempVar' before initialization

let tempVar = 80;
</code></pre>
<p>&lt;br/&gt;</p>
<h2>What is Hoisting?</h2>
<p>Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during compilation.</p>
<h3>Variable Hoisting:</h3>
<p>Variable declarations are hoisted, but not their assignments.</p>
<pre><code class="language-JS">console.log(hoistedVar); // Outputs: undefined
var hoistedVar = 30;
console.log(hoistedVar); // Outputs: 30
</code></pre>
<h3>Function Hoisting:</h3>
<p>Function declarations are also hoisted, allowing you to call functions before they are defined.</p>
<pre><code class="language-JS">hoistedFunction(); // Outputs: "Hello, hoisting!"
function hoistedFunction() {
    console.log("Hello, hoisting!");
}
</code></pre>
<h2>Conclusion:</h2>
<p>Scopes and hoisting are integral to JavaScript's behavior. Mastering these concepts empowers you to write clean, efficient, and bug-free code. Remember that understanding these concepts deeply will not only help you avoid pitfalls but also enable you to make the most of JavaScript's capabilities.</p>
<p>Anticipate our upcoming article where we'll explore another exciting topic in JavaScript, thereby taking your JavaScript skills to the next level.</p>
<p><em>Stay curious and keep coding!</em></p>
<p><em>Stay tuned for our next article!</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Learning JavaScript the easy way: Variables</title>
      <link>https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-variables/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-variables/</guid>
      <description>What JavaScript variables are, how to declare them, and why they are the starting point for everything else, explained for beginners.</description>
      <pubDate>Thu, 17 Aug 2023 07:00:00 GMT</pubDate>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>webdev</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/learning-javascript-the-easy-way-variables.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p><strong>Reviving JavaScript Learning: Let's Dive into Variables!</strong></p>
<p>Hey there ✋, wonderful readers! It's been a little while, but guess what? I'm back and more excited than ever to embark on a journey of creating some fantastic beginner-friendly content. If you're new here, welcome aboard; if you've been with us before, it's great to have you back! Get ready to roll up your sleeves and dig into the world of JavaScript variables in this refreshing new part.</p>
<h2>JavaScript Variables: Your Gateway to Coding Awesomeness</h2>
<p>In our ongoing series, "Learning JavaScript the Easy Way," we're all about simplifying the complexities of coding. Today, we're going to tackle a fundamental concept that's like the building block of your coding adventures: JavaScript variables.</p>
<h2>What Are Variables and Why Should You Care?</h2>
<p>In the programming world, variables act as the ultimate containers for your data. Whether it's a person's name, an age, or even the result of a mind-bending calculation, variables give meaning to your data. Imagine a labeled box that holds your precious information, and you're pretty much there!</p>
<p>And the best part? JavaScript variables are super flexible. They can hold various types of data, from numbers to text and everything in between. This flexibility is what we mean by JavaScript being "loosely typed." It's like a wild card that lets you adapt to different situations effortlessly.</p>
<h2>Declaring Variables</h2>
<p>Alright, let's get hands-on! To start using variables, you need to declare them. Think of it as marking your territory in the coding playground. In JavaScript, we've got a few friends to help us out: <code>var</code>, <code>let</code>, and <code>const</code>.</p>
<h3>Unveiling the Power with <code>var</code> and <code>let</code></h3>
<p>To declare a variable, you'll use <code>var</code> or <code>let</code>, followed by the variable's name:</p>
<p>To declare a variable in JavaScript, we use keywords such as <code>var</code>, <code>let</code>, or <code>const</code>. These keywords signal how the variable can be used and the scope it's associated with.</p>
<pre><code class="language-JS">var message; // Declaration without initialization

let count;   // Another variable declaration
</code></pre>
<p>You can even declare and initialize multiple variables in a single line:</p>
<p><code>JS let firstName = "Alice", lastName = "Johnson";</code></p>
<h3>Using the <code>var</code> and <code>let</code> Keywords</h3>
<p>For variable declaration, you use the <code>var</code> or <code>let</code> keyword followed by the variable name:</p>
<pre><code class="language-JS">var message; // Declaration without initialization  let count;   // Another variable declaration
</code></pre>
<p>If you declare a variable without assigning a value, like in the examples above, the variable is said to be declared but not initialized. It holds the value <code>undefined</code>.</p>
<p>To initialize a variable during declaration, you can do:</p>
<pre><code class="language-JS">var username = "John"; // Declare and initialize  
let age = 30;          // Another example
</code></pre>
<p>You can declare and initialize multiple variables in a single statement:</p>
<pre><code class="language-JS">let firstName = "Alice", lastName = "Johnson";
</code></pre>
<h3>Undefined and Undeclared Variables</h3>
<p>Ever met someone you've heard about but never talked to? That's an <strong>undefined variable</strong>. It's been introduced (declared), but it hasn't spilled the beans about itself (assigned a value). So, it's kind of like an unfinished story – you know there's something there, but you don't know what.</p>
<p>On the flip side, an <strong>undeclared variable</strong> is like trying to find Bigfoot – it just doesn't exist. If you use an undeclared variable, JavaScript gets all puzzled and throws a <code>ReferenceError</code> at you.</p>
<pre><code class="language-JS">let greeting;          // Undefined variable console.log(greeting); // Outputs: undefined  console.log(response); // Throws: ReferenceError: response is not defined
</code></pre>
<h2>Ready to Dive Deeper? Scopes Await!</h2>
<p>In JavaScript, the fun doesn't stop at variables – it's all about where they live and who they hang out with. That's where scopes come into play. We've got global, function, and even block scopes to keep things interesting. Don't worry, we'll explore these captivating territories in our upcoming chapters.</p>
<h2>Let's Create Something Awesome Together</h2>
<p>So, there you have it, the spark to reignite your coding journey! As we embark on this thrilling adventure together, remember that variables are your trusty companions on the road to coding awesomeness. Buckle up, because we're just getting started. Stay tuned for more beginner-friendly insights, tips, and tricks – the coding world is at your fingertips, and I'm here to guide you every step of the way! and at the same time learn with you.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Rename Git Branches Locally and Remotely</title>
      <link>https://unkletayo.dev/engineering/standalone/how-to-rename-git-branch-locally-and-remotely/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/how-to-rename-git-branch-locally-and-remotely/</guid>
      <description>Rename a Git branch on your machine and on the remote, step by step, using a branch called feature/new-feature as the example.</description>
      <pubDate>Wed, 01 Jun 2022 07:00:00 GMT</pubDate>
      <category>git</category>
      <category>programming</category>
      <category>webdev</category>
      <category>tutorial</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/how-to-rename-git-branch-locally-and-remotely.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>In software development, version control is crucial for managing and collaborating on code. Git, a distributed version control system, is widely used for tracking changes in code repositories. Occasionally, you might need to rename a Git branch for various reasons such as improving clarity or consistency. This guide will walk you through the process of renaming Git branches both locally and remotely, using a real-world scenario to illustrate the steps.</p>
<h2>Scenario</h2>
<p>Imagine you are working on a project called "WidgetApp." You have a branch named <code>feature/new-feature</code> that you want to rename to <code>feature/awesome-feature</code> to better reflect the actual functionality you're implementing.</p>
<p>This short guide will walk you through the process of renaming the branches</p>
<h2>Solution</h2>
<h3>1. Renaming Locally</h3>
<h4>Step 1: Checkout the Branch</h4>
<p>Before renaming the branch, make sure you are on the branch that you want to rename. Open your terminal and navigate to your project directory:</p>
<pre><code class="language-bash">cd /path/to/WidgetApp
</code></pre>
<p>Then, check out the branch you want to rename:</p>
<pre><code class="language-bash">git checkout feature/new-feature
</code></pre>
<h4>Step 2: Rename the Branch</h4>
<p>To rename the branch locally, use the following command:</p>
<pre><code class="language-bash">git branch -m feature/awesome-feature
</code></pre>
<p>This renames the current branch (&lt;code&gt;feature/new-feature&lt;/code&gt;) to &lt;code&gt;feature/awesome-feature&lt;/code&gt;.</p>
<h4>Step 3: Push the Renamed Branch</h4>
<p>After renaming the branch locally, you need to push the changes to the remote repository:</p>
<pre><code class="language-bash">git push origin feature/awesome-feature
</code></pre>
<h3>2. Renaming Remotely</h3>
<h4>Step 4: Delete the Old Remote Branch</h4>
<p>Before pushing the renamed branch, delete the old remote branch using:</p>
<pre><code class="language-bash">git push origin --delete feature/new-feature
</code></pre>
<h4>Step 5: Push the Renamed Branch</h4>
<p>Now, push the renamed branch to the remote repository:</p>
<pre><code class="language-bash">git push origin feature/awesome-feature
</code></pre>
<h3>3. Update Local Tracking</h3>
<h4>Step 6: Update Local Tracking</h4>
<p>Your local repository still might be tracking the old remote branch name. To update it, use:</p>
<pre><code>git fetch --prune
</code></pre>
<h3>Summary of Commands</h3>
<p>Here's a summary of the commands used in this scenario:</p>
<pre><code># Step 1
cd /path/to/WidgetApp
git checkout feature/new-feature

# Step 2
git branch -m feature/awesome-feature

# Step 3
git push origin feature/awesome-feature

# Step 4
git push origin --delete feature/new-feature

# Step 5
git push origin feature/awesome-feature

# Step 6
git fetch --prune
</code></pre>
<p>Renaming Git branches both locally and remotely can be essential for maintaining a clean and organized repository. By following the steps outlined in this guide, you should be able to  confidently rename your branches while ensuring that your changes are properly reflected in both your local and remote repositories. Always be cautious when performing these actions to avoid unintended data loss or conflicts.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Learning JavaScript the easy way: JavaScript Grammar</title>
      <link>https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-javascript-grammar/</link>
      <guid isPermaLink="true">https://unkletayo.dev/engineering/standalone/learning-javascript-the-easy-way-javascript-grammar/</guid>
      <description>Part 1 of Learning JavaScript the easy way. Case sensitivity, identifiers, comments, statements and expressions, written for beginners.</description>
      <pubDate>Wed, 10 Mar 2021 07:00:00 GMT</pubDate>
      <category>javascript</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>firstpost</category>
      <enclosure url="https://unkletayo.dev/cover/engineering/standalone/learning-javascript-the-easy-way-javascript-grammar.png" length="0" type="image/png"/>
      <content:encoded><![CDATA[<p>Hi, welcome to my first post on <a href="https://dev.to">dev.to</a>.</p>
<p>This post will be the first part of the "Learning JavaScript the easy way" series.</p>
<p>The decision to start this series came out of the desire to teach JavaScript concepts in the most understandable way to people who may be struggling with it and also get a deeper understanding of it.</p>
<p>This tutorial is best suited for beginners as it promises a smooth ride and a better understanding of JavaScript concepts from the ground up.</p>
<p>In this part, you will learn about JavaScript grammar, some of which include case-sensitivity, identifiers, comments, statements, and expressions.</p>
<p>Let's dive in 🚀🚀</p>
<p>##Javascript Grammar</p>
<p>Every language has its diction, be it a programming language or the native languages we use to communicate with ourselves and Javascript isn't an exception.</p>
<p>###Case Sensitivity
JavaScript is a &lt;i&gt;case-sensitive&lt;/i&gt; language. This implies that the keywords, variable, function, and many other identifiers must always have consistent capitalization. Therefore, a variable name like &lt;code&gt;user&lt;/code&gt; is significantly different from &lt;code&gt;User&lt;/code&gt;.</p>
<p>###Identifiers
An Identifier can simply be referred to as a name.
In JavaScript, identifiers are used to name variables, functions, parameters, or classes. But these identifiers have certain rules that must be conformed to:</p>
<ul>
<li>A JavaScript identifier must start with a letter, underscore (_), or a dollar sign ($). Subsequent characters can also be digits (0–9), digits, underscores, or dollar signs.</li>
</ul>
<p>The letters being referred to in this context are not limited to ASCII characters also but may extend to Unicode characters also.</p>
<p>It has become a convention among Javascript developers to use camel case for identifiers, this means that the first letter is lowercase, and each additional word starts with a capital letter. Although it is not mandatory as there are other conventions like &lt;code&gt;kebab case&lt;/code&gt;, &lt;code&gt;snake case&lt;/code&gt; that can also be used, they just have to abide by the identifier rule</p>
<p>The following are identifier names declared in camelcase</p>
<pre><code class="language-Js">greetingsFromNigeria
containsNumber
isString
routeHere
</code></pre>
<p>##Comments</p>
<p>They are added with the purpose of making the source code easier for humans to understand, and are generally ignored by compilers and interpreters.</p>
<p>Comments are unexecuted statements in Javascript (more on statements later).</p>
<p>A line of code that comes after double slashes // or block of codes that comes between /* and */ are treated as a comment in JavaScript.
For example</p>
<pre><code class="language-Js">let foo = 6; //  I will be executed
// let bar = 7;  I am not executed

/*
This code won't run.
Because it is a comment
*/
</code></pre>
<h2>Statements</h2>
<p>A statement is a piece of code that can be executed and performs some kind of action.</p>
<p>Although JavaScript doesn't require a statement to end with a semicolon(;), it is recommended to be used when ending a statement.</p>
<p>Personally, I think it makes code readable.
You can use a code block that begins with a left curly brace &lt;code&gt;({)&lt;/code&gt; and ends with the right curly brace &lt;code&gt;(})&lt;/code&gt; to combine multiple statements as follows:</p>
<p>For example,</p>
<pre><code class="language-Js">let today;
if (true){
today = 'Monday';
} else {
today = 'Tuesday';
}
</code></pre>
<h2>Expressions</h2>
<p>An expression is a piece of code that can be evaluated to produce a value.
For example, the following illustrates an expression that involves a and b:</p>
<pre><code class="language-Js">a + b 
</code></pre>
<h2>Reserved Keywords</h2>
<p>There are some keywords that are reserved in JavaScript these keywords cant be used to name variables some of which includes</p>
<p>&lt;code&gt;await&lt;/code&gt;,&lt;code&gt;break&lt;/code&gt;,&lt;code&gt;case&lt;/code&gt;,&lt;code&gt;catch&lt;/code&gt;,&lt;code&gt;default&lt;/code&gt;,&lt;code&gt;const&lt;/code&gt;,&lt;code&gt;continue&lt;/code&gt;,&lt;code&gt;debugger&lt;/code&gt;,&lt;code&gt;delete&lt;/code&gt;,&lt;code&gt;enum&lt;/code&gt;,&lt;code&gt;else&lt;/code&gt;,&lt;code&gt;export&lt;/code&gt;,&lt;code&gt;do&lt;/code&gt;,&lt;code&gt;extends&lt;/code&gt;,&lt;code&gt;false&lt;/code&gt;,&lt;code&gt;finally&lt;/code&gt;,&lt;code&gt;for&lt;/code&gt;,&lt;code&gt;function&lt;/code&gt;,&lt;code&gt;void&lt;/code&gt;,&lt;code&gt;static&lt;/code&gt;.</p>
<p>There are many other reserved words and keywords in JavaScript that aren't mentioned here, but the good thing is you don't have to memorize them all. However, if you wish to check out other reserved words you can find them <a href="https://www.w3schools.in/javascript-tutorial/keywords/%22">here</a>.</p>
<p>In the next part, we will be discussing &lt;i&gt;Variable&lt;/i&gt;, its declaration, hosting amongst many basic concepts.</p>
<p>As this is my first post, I would like the opinions of experts on how to make this better</p>
<p>Thank you for your time.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
